1 module sbylib.engine.project.project; 2 3 import std; 4 import std : replace; // TODO: is this a compilier's bug??? 5 import sbylib.event; 6 import sbylib.engine.project.metainfo; 7 import sbylib.engine.project.moduleunit; 8 import sbylib.engine.project.modulestatus; 9 import sbylib.engine.compiler : CompileErrorException; 10 import sbylib.engine.util : resourcePath; 11 12 class Project { 13 14 private alias VModule = Module!(void); 15 alias global this; 16 17 VModule[string] moduleList; 18 Variant[string] global; 19 private ModuleStatusList status; 20 21 this() { 22 status = new ModuleStatusList(); 23 this.refreshPackage(); 24 this.loadAll(); 25 } 26 27 ~this() { 28 foreach (mod; moduleList.values) { 29 mod.destroy(); 30 } 31 } 32 33 private void loadAll() { 34 foreach (file; this.projectFiles) { 35 this.loadSingle(file); 36 } 37 } 38 39 private void loadSingle(string file) { 40 auto mod = new VModule(this, status, file.absolutePath.buildNormalizedPath); 41 moduleList[mod.path] = mod; 42 43 auto modified = mod.path.hasModified; 44 foreach (d; mod.dependencies) 45 modified = modified | d.hasModified; 46 foreach (f; mod.inputFiles) 47 modified = modified | f.hasModified; 48 49 when(modified).once({ 50 this.loadSingle(file); 51 }); 52 } 53 54 auto get(T)(string name) { 55 if (name !in this) return null; 56 return this[name].get!T; 57 } 58 59 string[] projectFiles() { 60 enforce(MetaInfo().projectDirectory.exists, 61 format!`"%s" does not exists.`(MetaInfo().projectDirectory)); 62 return MetaInfo().projectDirectory.dirEntries(SpanMode.breadth) 63 .filter!(entry => entry.isFile) 64 .filter!(entry => entry.baseName != "package.d") 65 .filter!(entry => entry.isModule) 66 .map!(entry => cast(string)entry) 67 .array; 68 } 69 70 void refreshPackage() { 71 import std.file : write; 72 73 auto projectName = MetaInfo().projectDirectory; 74 if (!projectName.exists) 75 mkdirRecurse(projectName); 76 77 foreach (entry; projectName.dirEntries(SpanMode.breadth)) { 78 if (entry.isFile) continue; 79 80 const fileName = entry.buildPath("package.d"); 81 82 auto dirImportList = entry 83 .dirEntries(SpanMode.shallow) 84 .filter!(e => e.isDir) 85 .map!(e => e.relativePath(projectName)) 86 .map!(p => p.replace("/", ".")) 87 .map!(name => name.format!"import %s;") 88 .array; 89 auto fileImportList = entry 90 .dirEntries(SpanMode.shallow) 91 .filter!(e => e.isFile) 92 .map!(e => e.relativePath(projectName)) 93 .filter!(p => p.extension == ".d") 94 .filter!(p => p.baseName != "package.d") 95 .map!(e => e.stripExtension.replace("/", ".")) 96 .map!(name => name.format!"import %s;") 97 .array; 98 99 const moduleName = entry.replace("/", "."); 100 const content = resourcePath("package.d").readText 101 .replace("${moduleName}", moduleName) 102 .replace("${importList}", (dirImportList ~ fileImportList).join("\n")); 103 104 fileName.write(content); 105 } 106 } 107 108 // name -> path 109 string findPath(string name) { 110 auto findResult = moduleList.values.find!(mod => mod.name == name); 111 enforce(!findResult.empty || moduleList.values.any!(m => status[m.path] != ModuleStatus.Compiling), format!"Module '%s' is not found."(name)); 112 return findResult.empty ? "" : findResult.front.path; 113 } 114 115 private auto findLoadedModule(string modName) { 116 auto result = moduleList.values 117 .find!(m => m.name == modName); 118 return result; 119 } 120 } 121 122 // TODO: implement seriously 123 private bool isModule(string file) 124 in (file.exists) 125 { 126 return readText(file).split("\n") 127 .map!(chomp) 128 .filter!(line => line.startsWith("//") is false) 129 .filter!(line => line.canFind("mixin")) 130 .filter!(line => line.canFind("Register")) 131 .empty is false; 132 }