1 /**
2 Copyright: Copyright (c) 2017, Joakim Brännström. All rights reserved.
3 License: MPL-2
4 Author: Joakim Brännström (joakim.brannstrom@gmx.com)
5 
6 This Source Code Form is subject to the terms of the Mozilla Public License,
7 v.2.0. If a copy of the MPL was not distributed with this file, You can obtain
8 one at http://mozilla.org/MPL/2.0/.
9 
10 This is a handy range to iterate over either all files from the user OR all
11 files in a compilation database.
12 */
13 module dextool.compilation_db.user_filerange;
14 
15 import logger = std.experimental.logger;
16 import std.algorithm : map, joiner;
17 import std.array : empty, appender, array;
18 import std.range : isInputRange, ElementType, only;
19 import std.typecons : tuple, Nullable;
20 import std.exception : collectException;
21 
22 import dextool.compilation_db : CompileCommand, CompileCommandFilter,
23     CompileCommandDB, parseFlag, ParseFlags, Compiler, SystemIncludePath;
24 import dextool.type : Path, AbsolutePath;
25 
26 @safe:
27 
28 struct SimpleRange(T) {
29     private T[] values;
30 
31     this(T[] values) {
32         this.values = values;
33     }
34 
35     T front() {
36         assert(!empty, "Can't get front of an empty range");
37         return values[0];
38     }
39 
40     void popFront() @safe pure nothrow @nogc {
41         assert(!empty, "Can't pop front of an empty range");
42         values = values[1 .. $];
43     }
44 
45     bool empty() @safe pure nothrow const @nogc {
46         import std.array : empty;
47 
48         return values.empty;
49     }
50 
51     size_t length() @safe pure nothrow const @nogc {
52         return values.length;
53     }
54 
55     typeof(this) save() @safe pure nothrow {
56         return typeof(this)(values);
57     }
58 
59     static SimpleRange!T make(T[] values) {
60         return SimpleRange!T(values);
61     }
62 }
63 
64 alias CompileCommandsRange = SimpleRange!CompileCommand;
65 
66 /// Returns: a range over all files in the database.
67 CompileCommandsRange fileRange(CompileCommandDB db) {
68     return CompileCommandsRange(db.payload);
69 }
70 
71 CompileCommandsRange fileRange(Path[] files, Compiler compiler) {
72     import std.file : getcwd;
73 
74     return CompileCommandsRange(files.map!(a => CompileCommand(a, AbsolutePath(a),
75             AbsolutePath(Path(getcwd)), CompileCommand.Command([compiler]),
76             Path.init, AbsolutePath.init)).array);
77 }
78 
79 /// The flags in the CompileCommand are extracted and parsed.
80 struct ParsedCompileCommand {
81     CompileCommand cmd;
82     ParseFlags flags;
83 }
84 
85 alias ParsedCompileCommandRange = SimpleRange!ParsedCompileCommand;
86 
87 /// Returns: a range over all files in the range where the flags have been parsed.
88 auto parse(RangeT)(RangeT r, CompileCommandFilter ccFilter) @safe nothrow 
89         if (is(ElementType!RangeT == CompileCommand)) {
90     return r.map!(a => ParsedCompileCommand(a, parseFlag(a, ccFilter)));
91 }
92 
93 /// Returns: a range wherein the system includes for the compiler has been
94 /// deduced and added to the `flags` data.
95 auto addSystemIncludes(RangeT)(RangeT r) @safe nothrow 
96         if (is(ElementType!RangeT == ParsedCompileCommand)) {
97     static SystemIncludePath[] deduce(CompileCommand cmd, Compiler compiler) @safe nothrow {
98         import dextool.compilation_db.system_compiler : deduceSystemIncludes;
99 
100         try {
101             return deduceSystemIncludes(cmd, compiler);
102         } catch (Exception e) {
103             logger.info(e.msg).collectException;
104         }
105         return SystemIncludePath[].init;
106     }
107 
108     return r.map!((a) {
109         a.flags.systemIncludes = deduce(a.cmd, a.flags.compiler);
110         return a;
111     });
112 }
113 
114 /// Return: add a compiler to the `flags` data if it is missing.
115 auto addCompiler(RangeT)(RangeT r, Compiler compiler) @safe nothrow 
116         if (is(ElementType!RangeT == ParsedCompileCommand)) {
117     ParsedCompileCommand add(ParsedCompileCommand p, Compiler compiler) @safe nothrow {
118         if (p.flags.compiler.empty) {
119             p.flags.compiler = compiler;
120         }
121         return p;
122     }
123 
124     return r.map!(a => add(a, compiler));
125 }
126 
127 /// Return: replace the compiler in `flags` with `compiler` if `compiler` is
128 /// NOT empty.
129 auto replaceCompiler(RangeT)(RangeT r, Compiler compiler) @safe nothrow 
130         if (is(ElementType!RangeT == ParsedCompileCommand)) {
131     return r.map!((a) {
132         if (!compiler.empty)
133             a.flags.compiler = compiler;
134         return a;
135     });
136 }
137 
138 struct LimitFileRange {
139     /// Files not found in the compile_commands database;
140     string[] missingFiles;
141 
142     ParsedCompileCommand[] commands;
143 
144     bool isMissingFilesEmpty() {
145         return missingFiles.empty;
146     }
147 
148     /// Returns: a range over all files that where found in the database.
149     ParsedCompileCommandRange range() {
150         return ParsedCompileCommandRange.make(commands);
151     }
152 }
153 
154 /// Returns: a struct which has extracted `onlyTheseFiles` into either using
155 /// the matching `ParsedCompileCommand` or missing.
156 LimitFileRange limitFileRange(ParsedCompileCommand[] db, string[] onlyTheseFiles) {
157     import dextool.compilation_db;
158 
159     auto missing = appender!(string[])();
160     auto app = appender!(ParsedCompileCommand[])();
161     foreach (a; onlyTheseFiles.map!(a => tuple!("file", "result")(a, find(db, a)))) {
162         if (a.result.isNull) {
163             missing.put(a.file);
164         } else {
165             app.put(a.result.get);
166         }
167     }
168 
169     return LimitFileRange(missing.data, app.data);
170 }
171 
172 LimitFileRange limitOrAllRange(T)(ParsedCompileCommand[] db, T[] onlyTheseFiles) {
173     if (onlyTheseFiles.empty)
174         return LimitFileRange(null, db);
175     return limitFileRange(db, onlyTheseFiles.map!(a => cast(string) a).array);
176 }
177 
178 /// Returns: prepend all CompileCommands parsed flags with `flags`.
179 auto prependFlags(RangeT)(RangeT r, string[] flags)
180         if (isInputRange!RangeT && is(ElementType!RangeT == ParsedCompileCommand)) {
181     return r.map!((a) { a.flags.prependCflags(flags); return a; });
182 }
183 
184 /** Find a best matching compile_command in the database against the path
185  * pattern `glob`.
186  *
187  * When searching for the compile command for a file, the compilation db can
188  * return several commands, as the file may have been compiled with different
189  * options in different parts of the project.
190  *
191  * Params:
192  *  glob = glob pattern to find a matching file in the DB against
193  */
194 Nullable!ParsedCompileCommand find(ParsedCompileCommand[] db, string glob) @safe {
195     import dextool.compilation_db : isMatch;
196 
197     foreach (a; db) {
198         if (isMatch(a.cmd, glob))
199             return typeof(return)(a);
200     }
201     return typeof(return).init;
202 }