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 9 10 /** Converts a `Group` with alternating file names and contents to an array of 11 `InputFile`s. 12 */ 13 @property InputFile[] filesFromGroup(alias FILES_GROUP)() 14 { 15 static assert(FILES_GROUP.expand.length % 2 == 0); 16 auto ret = new InputFile[FILES_GROUP.expand.length / 2]; 17 foreach (i, F; FILES_GROUP.expand) { 18 static if (i % 2 == 0) { 19 ret[i / 2].name = FILES_GROUP.expand[i+1]; 20 ret[i / 2].contents = FILES_GROUP.expand[i]; 21 } 22 } 23 return ret; 24 } 25 26 /** Using the file name of a string import Diet file, returns a list of all 27 required files. 28 29 These files recursively include all imports or extension templates that 30 are used. The type of the list is `InputFile[]`. 31 */ 32 template collectFiles(string root_file) 33 { 34 import diet.internal..string : stripUTF8BOM; 35 private static immutable contents = stripUTF8BOM(import(root_file)); 36 enum collectFiles = collectFiles!(root_file, contents); 37 } 38 /// ditto 39 template collectFiles(string root_file, alias root_contents) 40 { 41 import std.algorithm.searching : canFind; 42 enum baseFiles = collectReferencedFiles!(root_file, root_contents); 43 static if (baseFiles.canFind!(f => f.name == root_file)) 44 enum collectFiles = baseFiles; 45 else enum collectFiles = InputFile(root_file, root_contents) ~ baseFiles; 46 } 47 48 /// Encapsulates a single input file. 49 struct InputFile { 50 string name; 51 string contents; 52 } 53 54 /** Helper template to aggregate a list of compile time values. 55 56 This is similar to `AliasSeq`, but does not auto-expand. 57 */ 58 template Group(A...) { 59 import std.typetuple; 60 alias expand = TypeTuple!A; 61 } 62 63 /** Returns a mixin string that makes all passed symbols available in the 64 mixin's scope. 65 */ 66 template localAliasesMixin(int i, ALIASES...) 67 { 68 import std.traits : hasUDA; 69 static if (i < ALIASES.length) { 70 import std.conv : to; 71 static if (hasUDA!(ALIASES[i], DietTraitsAttribute)) enum string localAliasesMixin = localAliasesMixin!(i+1); 72 else enum string localAliasesMixin = "alias ALIASES["~i.to!string~"] "~__traits(identifier, ALIASES[i])~";\n" 73 ~localAliasesMixin!(i+1, ALIASES); 74 } else { 75 enum string localAliasesMixin = ""; 76 } 77 } 78 79 private template collectReferencedFiles(string file_name, alias file_contents) 80 { 81 import std.path : extension; 82 83 enum references = collectReferences(file_contents); 84 template impl(size_t i) { 85 static if (i < references.length) { 86 enum rfiles = impl!(i+1); 87 static if (__traits(compiles, import(references[i]))) { 88 enum ifiles = collectFiles!(references[i]); 89 enum impl = merge(ifiles, rfiles); 90 } else static if (__traits(compiles, import(references[i] ~ extension(file_name)))) { 91 enum ifiles = collectFiles!(references[i] ~ extension(file_name)); 92 enum impl = merge(ifiles, rfiles); 93 } else enum impl = rfiles; 94 } else enum InputFile[] impl = []; 95 } 96 alias collectReferencedFiles = impl!0; 97 } 98 99 private string[] collectReferences(string content) 100 { 101 import std..string : strip, stripLeft, splitLines; 102 import std.algorithm.searching : startsWith; 103 104 string[] ret; 105 foreach (i, ln; content.stripLeft().splitLines()) { 106 // FIXME: this produces false-positives when a text paragraph is used: 107 // p. 108 // This is some text. 109 // import oops, this is also just text. 110 ln = ln.stripLeft(); 111 if (i == 0 && ln.startsWith("extends ")) 112 ret ~= ln[8 .. $].strip(); 113 else if (ln.startsWith("include ")) 114 ret ~= ln[8 .. $].strip(); 115 } 116 return ret; 117 } 118 119 private InputFile[] merge(InputFile[] a, InputFile[] b) 120 { 121 import std.algorithm.searching : canFind; 122 auto ret = a; 123 foreach (f; b) 124 if (!a.canFind!(g => g.name == f.name)) 125 ret ~= f; 126 return ret; 127 }