1 module imports;
2 
3 import model;
4 import std.algorithm;
5 import std.array;
6 import std.range;
7 import std.typecons;
8 
9 version (unittest) import unit_threaded;
10 
11 auto mutualDependencies(const string[] args)
12 {
13     string[][string] importedModules;
14 
15     foreach (arg; args)
16         with (readImports(arg))
17             importedModules[client] ~= suppliers;
18     return importedModules.byKeyValue
19         .map!(pair => pair.value.map!(supplier => Dependency(pair.key, supplier)))
20         .joiner
21         .filter!(dependency => dependency.supplier.toString in importedModules);
22 }
23 
24 auto readImports(string file)
25 {
26     import std.file : readText;
27     import std.path : baseName, stripExtension;
28 
29     const input = file.readText;
30     auto captures = moduleDeclaration(input);
31     const client = captures
32         ? captures["fullyQualifiedName"].toFullyQualifiedName
33         : file.baseName.stripExtension;
34     const suppliers = importDeclarations(input)
35         .map!(captures => captures["fullyQualifiedName"].toFullyQualifiedName)
36         .array;
37 
38     return tuple!("client", "suppliers")(client, suppliers);
39 }
40 
41 auto moduleDeclaration(R)(R input)
42 {
43     import std.regex : matchFirst, regex;
44 
45     // TODO: skip comments, string literals
46     enum pattern = regex(`\bmodule\s+` ~ fullyQualifiedName ~ `\s*;`);
47 
48     return input.matchFirst(pattern);
49 }
50 
51 @("match module declaration")
52 unittest
53 {
54     auto captures = moduleDeclaration("module bar.baz;");
55 
56     captures.shouldBeTrue;
57     captures["fullyQualifiedName"].should.be == "bar.baz";
58 }
59 
60 @("match module declaration with white space")
61 unittest
62 {
63     auto captures = moduleDeclaration("module bar . baz\n;");
64 
65     captures.shouldBeTrue;
66     captures["fullyQualifiedName"].should.be == "bar . baz";
67 }
68 
69 auto importDeclarations(R)(R input)
70 {
71     import std.regex : matchAll, regex;
72 
73     // TODO: skip comments, string literals
74     enum pattern = regex(`\bimport\s+(\w+\s*=\s*)?` ~ fullyQualifiedName ~ `[^;]*;`);
75 
76     return input.matchAll(pattern);
77 }
78 
79 @("match import declaration")
80 unittest
81 {
82     auto match = importDeclarations("import bar.baz;");
83 
84     match.shouldBeTrue;
85     match.map!`a["fullyQualifiedName"]`.shouldEqual(["bar.baz"]);
86 }
87 
88 @("match import declaration with white space")
89 unittest
90 {
91     auto match = importDeclarations("import bar . baz\n;");
92 
93     match.shouldBeTrue;
94     match.map!`a["fullyQualifiedName"]`.shouldEqual(["bar . baz"]);
95 }
96 
97 @("match renamed import")
98 unittest
99 {
100     auto match = importDeclarations("import foo = bar.baz;");
101 
102     match.shouldBeTrue;
103     match.map!`a["fullyQualifiedName"]`.shouldEqual(["bar.baz"]);
104 }
105 
106 enum fullyQualifiedName = `(?P<fullyQualifiedName>\w+(\s*\.\s*\w+)*)`;
107 
108 string toFullyQualifiedName(string text)
109 {
110     import std..string : join, strip;
111 
112     return text.splitter('.').map!strip.join('.');
113 }
114 
115 @("convert text to fully-qualified name")
116 unittest
117 {
118     "bar . baz".toFullyQualifiedName.should.be == "bar.baz";
119 }