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 }