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 static SimpleRange!T make(T[] values) { 56 return SimpleRange!T(values); 57 } 58 } 59 60 alias CompileCommandsRange = SimpleRange!CompileCommand; 61 62 /// Returns: a range over all files in the database. 63 CompileCommandsRange fileRange(CompileCommandDB db) { 64 return CompileCommandsRange(db.payload); 65 } 66 67 CompileCommandsRange fileRange(Path[] files, Compiler compiler) { 68 import std.file : getcwd; 69 70 return CompileCommandsRange(files.map!(a => CompileCommand(a, AbsolutePath(a), 71 AbsolutePath(Path(getcwd)), CompileCommand.Command([compiler]), 72 Path.init, AbsolutePath.init)).array); 73 } 74 75 /// The flags in the CompileCommand are extracted and parsed. 76 struct ParsedCompileCommand { 77 CompileCommand cmd; 78 ParseFlags flags; 79 } 80 81 alias ParsedCompileCommandRange = SimpleRange!ParsedCompileCommand; 82 83 /// Returns: a range over all files in the range where the flags have been parsed. 84 auto parse(RangeT)(RangeT r, CompileCommandFilter ccFilter) @safe nothrow 85 if (is(ElementType!RangeT == CompileCommand)) { 86 return r.map!(a => ParsedCompileCommand(a, parseFlag(a, ccFilter))); 87 } 88 89 /// Returns: a range wherein the system includes for the compiler has been 90 /// deduced and added to the `flags` data. 91 auto addSystemIncludes(RangeT)(RangeT r) @safe nothrow 92 if (is(ElementType!RangeT == ParsedCompileCommand)) { 93 static SystemIncludePath[] deduce(CompileCommand cmd, Compiler compiler) @safe nothrow { 94 import dextool.compilation_db.system_compiler : deduceSystemIncludes; 95 96 try { 97 return deduceSystemIncludes(cmd, compiler); 98 } catch (Exception e) { 99 logger.info(e.msg).collectException; 100 } 101 return SystemIncludePath[].init; 102 } 103 104 return r.map!((a) { 105 a.flags.systemIncludes = deduce(a.cmd, a.flags.compiler); 106 return a; 107 }); 108 } 109 110 /// Return: add a compiler to the `flags` data if it is missing. 111 auto addCompiler(RangeT)(RangeT r, Compiler compiler) @safe nothrow 112 if (is(ElementType!RangeT == ParsedCompileCommand)) { 113 ParsedCompileCommand add(ParsedCompileCommand p, Compiler compiler) @safe nothrow { 114 if (p.flags.compiler.empty) { 115 p.flags.compiler = compiler; 116 } 117 return p; 118 } 119 120 return r.map!(a => add(a, compiler)); 121 } 122 123 /// Return: replace the compiler in `flags` with `compiler` if `compiler` is 124 /// NOT empty. 125 auto replaceCompiler(RangeT)(RangeT r, Compiler compiler) @safe nothrow 126 if (is(ElementType!RangeT == ParsedCompileCommand)) { 127 return r.map!((a) { 128 if (!compiler.empty) 129 a.flags.compiler = compiler; 130 return a; 131 }); 132 } 133 134 struct LimitFileRange { 135 /// Files not found in the compile_commands database; 136 string[] missingFiles; 137 138 ParsedCompileCommand[] commands; 139 140 bool isMissingFilesEmpty() { 141 return missingFiles.empty; 142 } 143 144 /// Returns: a range over all files that where found in the database. 145 ParsedCompileCommandRange range() { 146 return ParsedCompileCommandRange.make(commands); 147 } 148 } 149 150 /// Returns: a struct which has extracted `onlyTheseFiles` into either using 151 /// the matching `ParsedCompileCommand` or missing. 152 LimitFileRange limitFileRange(ParsedCompileCommand[] db, string[] onlyTheseFiles) { 153 import dextool.compilation_db; 154 155 auto missing = appender!(string[])(); 156 auto app = appender!(ParsedCompileCommand[])(); 157 foreach (a; onlyTheseFiles.map!(a => tuple!("file", "result")(a, find(db, a)))) { 158 if (a.result.isNull) { 159 missing.put(a.file); 160 } else { 161 app.put(a.result.get); 162 } 163 } 164 165 return LimitFileRange(missing.data, app.data); 166 } 167 168 LimitFileRange limitOrAllRange(T)(ParsedCompileCommand[] db, T[] onlyTheseFiles) { 169 if (onlyTheseFiles.empty) 170 return LimitFileRange(null, db); 171 return limitFileRange(db, onlyTheseFiles.map!(a => cast(string) a).array); 172 } 173 174 /// Returns: prepend all CompileCommands parsed flags with `flags`. 175 auto prependFlags(RangeT)(RangeT r, string[] flags) 176 if (isInputRange!RangeT && is(ElementType!RangeT == ParsedCompileCommand)) { 177 return r.map!((a) { a.flags.prependCflags(flags); return a; }); 178 } 179 180 /** Find a best matching compile_command in the database against the path 181 * pattern `glob`. 182 * 183 * When searching for the compile command for a file, the compilation db can 184 * return several commands, as the file may have been compiled with different 185 * options in different parts of the project. 186 * 187 * Params: 188 * glob = glob pattern to find a matching file in the DB against 189 */ 190 Nullable!ParsedCompileCommand find(ParsedCompileCommand[] db, string glob) @safe { 191 import dextool.compilation_db : isMatch; 192 193 foreach (a; db) { 194 if (isMatch(a.cmd, glob)) 195 return typeof(return)(a); 196 } 197 return typeof(return).init; 198 }