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 }