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 }