1 /** Contains common definitions and logic to collect input dependencies. 2 3 This module is typically only used by generator implementations. 4 */ 5 module diet.input; 6 7 import diet.traits : DietTraitsAttribute; 8 import diet.internal.string; 9 import memutils.vector; 10 import memutils.scoped; 11 12 @safe: 13 nothrow: 14 15 /** Converts a `Group` with alternating file names and contents to an array of 16 `InputFile`s. 17 */ 18 @property InputFile[] filesFromGroup(alias FILES_GROUP)() 19 { 20 static assert(FILES_GROUP.expand.length % 2 == 0); 21 auto ret = Array!InputFile(FILES_GROUP.expand.length / 2); 22 foreach (i, F; FILES_GROUP.expand) { 23 static if (i % 2 == 0) { 24 ret[i / 2].name = FILES_GROUP.expand[i+1]; 25 ret[i / 2].contents = FILES_GROUP.expand[i]; 26 } 27 } 28 return ret[].copy(); 29 } 30 31 bool canFindFile(T, U)(T baseFiles, U file) { 32 foreach(baseFile; baseFiles) { 33 if (baseFile.name[] == file) { 34 return true; 35 } 36 } 37 return false; 38 } 39 /** Using the file name of a string import Diet file, returns a list of all 40 required files. 41 42 These files recursively include all imports or extension templates that 43 are used. The type of the list is `InputFile[]`. 44 */ 45 template collectFiles(string root_file) 46 { 47 import diet.internal.string : stripUTF8BOM; 48 private static immutable contents = stripUTF8BOM(import(root_file)); 49 enum collectFiles = collectFiles!(root_file, contents); 50 } 51 /// ditto 52 template collectFiles(string root_file, alias root_contents) 53 { 54 enum baseFiles = collectReferencedFiles!(root_file, root_contents); 55 static if (baseFiles.canFindFile(root_file)) 56 enum collectFiles = baseFiles; 57 else enum collectFiles = baseFiles ~ InputFile(root_file, root_contents); 58 59 } 60 61 /// Encapsulates a single input file. 62 struct InputFile { 63 string name; 64 string contents; 65 } 66 67 /** Helper template to aggregate a list of compile time values. 68 69 This is similar to `AliasSeq`, but does not auto-expand. 70 */ 71 template Group(A...) { 72 import std.typetuple; 73 alias expand = TypeTuple!A; 74 } 75 76 /** Returns a mixin string that makes all passed symbols available in the 77 mixin's scope. 78 */ 79 template localAliasesMixin(int i, ALIASES...) 80 { 81 import std.traits : hasUDA; 82 static if (i < ALIASES.length) { 83 import std.conv : to; 84 static if (hasUDA!(ALIASES[i], DietTraitsAttribute)) enum string localAliasesMixin = localAliasesMixin!(i+1); 85 else enum string localAliasesMixin = "alias ALIASES["~i.to!string~"] "~__traits(identifier, ALIASES[i])~";\n" 86 ~localAliasesMixin!(i+1, ALIASES); 87 } else { 88 enum string localAliasesMixin = ""; 89 } 90 } 91 92 string ctExtension(string file_name) { 93 Vector!char ext; 94 bool is_ext; 95 foreach(c; file_name) { 96 if (c == '.') { 97 is_ext = true; 98 ext.clear(); 99 } 100 if (is_ext) { 101 ext ~= c; 102 } 103 } 104 return ext[].copy(); 105 } 106 107 private template collectReferencedFiles(string file_name, alias file_contents) 108 { 109 110 enum references = collectReferences(file_contents); 111 template impl(size_t i) { 112 static if (i < references.length) { 113 enum rfiles = impl!(i+1); 114 static if (__traits(compiles, import(references[i]))) { 115 enum ifiles = collectFiles!(references[i]); 116 enum impl = merge(ifiles, rfiles); 117 } else static if (__traits(compiles, import(references[i] ~ ctExtension(file_name)))) { 118 enum ifiles = collectFiles!(references[i] ~ ctExtension(file_name)); 119 enum impl = merge(ifiles, rfiles); 120 } else enum impl = rfiles; 121 } else enum InputFile[] impl = []; 122 } 123 alias collectReferencedFiles = impl!0; 124 } 125 126 private string[] collectReferences(string content) 127 { 128 Vector!string ret; 129 // content.stripLeft().splitLines() 130 Vector!char stripped_content; 131 stripped_content[] = ctstripLeft(content); 132 Vector!string line_broken_content = ctsplit(stripped_content[], '\n'); 133 134 foreach (k, ln; line_broken_content[]) { 135 // FIXME: this produces false-positives when a text paragraph is used: 136 // p. 137 // This is some text. 138 // import oops, this is also just text. 139 ln = ln.ctstripLeft(); 140 if (k == 0 && ln.ctstartsWith("extends ")) 141 ret ~= ln[8 .. $].ctstrip(); 142 else if (ln.ctstartsWith("include ")) 143 ret ~= ln[8 .. $].ctstrip(); 144 } 145 return ret[].copy(); 146 } 147 148 private InputFile[] merge(T, U)(ref T a, ref U b) 149 { 150 auto ret = Vector!InputFile(a); 151 foreach (f; b[]) { 152 bool found; 153 foreach (f2; a[]) { 154 if (f2.name[] == f.name[]) { 155 found = true; 156 break; 157 } 158 } 159 if (!found) 160 ret ~= f; 161 162 } 163 return ret[].copy(); 164 } 165 166 version(DietUseLive) 167 { 168 /** 169 Runtime equivalent of collectFiles. This version uses std.file to read files 170 from the appropriate directory. Note that for collectFiles, the directory to 171 use is passed on the command line, whereas in this case, we must receive the 172 directory containing the files from the caller. 173 174 Params: 175 file = The root file that will be used to find all referenced files. 176 source_directories = Optional base directory list from which all files will be searched. 177 178 Returns: 179 An array of InputFile structs containing the list of files that are 180 referenced from the root file, and their contents. 181 */ 182 InputFile[] rtGetInputs(string file, string[] source_directories...) 183 { 184 // for each of the files, import the file, get all the references, and 185 // continually import files until we have them all. 186 import std.range; 187 import std.file : readText, exists; 188 import diet.internal.string : stripUTF8BOM; 189 import std.algorithm : canFind; 190 import std.path : buildPath, extension; 191 192 if (!source_directories.length) 193 source_directories = [""]; 194 195 auto ext = extension(file); 196 string[] filesToProcess = [file]; 197 Vector!InputFile result; 198 199 void addFile(string fname) { 200 if (!filesToProcess.canFind(fname) && !result.canFind!(g => g.name == fname)) 201 filesToProcess ~= fname; 202 } 203 204 next_file: 205 while (filesToProcess.length) { 206 auto nextFile = filesToProcess.front; 207 filesToProcess.popFront(); 208 foreach (dir; source_directories) { 209 if (exists(buildPath(dir, nextFile))) { 210 auto newInput = InputFile(nextFile, stripUTF8BOM(readText(buildPath(dir, nextFile)))); 211 result ~= newInput; 212 foreach (f; collectReferences(newInput.contents)) 213 addFile(f); 214 continue next_file; 215 } 216 217 if (exists(buildPath(dir, nextFile ~ ext))) { 218 addFile(nextFile ~ ext); 219 continue next_file; 220 } 221 } 222 throw new Exception("Cannot find necessary file " ~ nextFile ~ " to parse strings for " ~ file); 223 } 224 225 assert(result.length > 0); 226 return result[].copy(); 227 } 228 }