1 module sbylib.engine.tools.dscanner;
2 
3 import std;
4 import sbylib.engine.util : importPath;
5 
6 class DScanner {
7 static:
8 
9     Tuple!(string[], SysTime)[string] importListMemo;
10 
11     string[] importListRecursive(alias constraint = (string f) => true)(string file) {
12         string[] result = importList(file).filter!(constraint).array;
13         while (true) {
14             const n = result.length;
15             result = (result ~ result.map!(f => importList(f).filter!(constraint)).join).sort.uniq.array;
16             if (result.length == n) break;
17         }
18         return result;
19     }
20 
21     string[] importList(string file) 
22         out (r; r.all!(exists), r.to!string)
23     {
24         if (auto memo = file in importListMemo) {
25             if ((*memo)[1] == file.timeLastModified) {
26                 return (*memo)[0];
27             }
28         }
29 
30         auto result = execute("-i " ~ file ~ " " ~ importPath.map!(p => "-I" ~ p).join(" "))
31             .filter!(line => line.isValidPath)
32             .array;
33 
34         importListMemo[file] = tuple(result, file.timeLastModified);
35 
36         enum Head = "Could not resolve location of";
37         auto errors = result.filter!(r => r.startsWith(Head));
38         if (!errors.empty) {
39             throw new ResolveLocationException(format!"%s:\n%s\nimportPath=%s"
40                     (Head, errors.map!(e => e.split(Head).back).join("\n"), 
41                      importPath.join("\n")));
42         }
43 
44         return result;
45     }
46 
47     private string[] execute(string cmd) {
48         const result = executeShell("dscanner " ~ cmd);
49         if (result.status != 0) {
50             throw new Exception(result.output);
51         }
52         return result.output
53             .split("\n")
54             .filter!(s => s.length > 0)
55             .array;
56     }
57 }
58 
59 class ResolveLocationException : Exception {
60     mixin basicExceptionCtors;
61 }