1 module dlangide.workspace.project; 2 3 import dlangide.workspace.workspace; 4 import dlangui.core.logger; 5 import dlangui.core.collections; 6 import dlangui.core.settings; 7 import std.path; 8 import std.file; 9 import std.json; 10 import std.utf; 11 import std.algorithm; 12 13 /// return true if filename matches rules for workspace file names 14 bool isProjectFile(string filename) { 15 return filename.baseName.equal("dub.json") || filename.baseName.equal("package.json"); 16 } 17 18 string toForwardSlashSeparator(string filename) { 19 char[] res; 20 foreach(ch; filename) { 21 if (ch == '\\') 22 res ~= '/'; 23 else 24 res ~= ch; 25 } 26 return cast(string)res; 27 } 28 29 /// project item 30 class ProjectItem { 31 protected Project _project; 32 protected ProjectItem _parent; 33 protected string _filename; 34 protected dstring _name; 35 36 this(string filename) { 37 _filename = buildNormalizedPath(filename); 38 _name = toUTF32(baseName(_filename)); 39 } 40 41 this() { 42 } 43 44 @property ProjectItem parent() { 45 return _parent; 46 } 47 48 @property Project project() { 49 return _project; 50 } 51 52 @property void project(Project p) { 53 _project = p; 54 } 55 56 @property string filename() { 57 return _filename; 58 } 59 60 @property dstring name() { 61 return _name; 62 } 63 64 /// returns true if item is folder 65 @property bool isFolder() { 66 return false; 67 } 68 /// returns child object count 69 @property int childCount() { 70 return 0; 71 } 72 /// returns child item by index 73 ProjectItem child(int index) { 74 return null; 75 } 76 } 77 78 /// Project folder 79 class ProjectFolder : ProjectItem { 80 protected ObjectList!ProjectItem _children; 81 82 this(string filename) { 83 super(filename); 84 } 85 86 @property override bool isFolder() { 87 return true; 88 } 89 @property override int childCount() { 90 return _children.count; 91 } 92 /// returns child item by index 93 override ProjectItem child(int index) { 94 return _children[index]; 95 } 96 void addChild(ProjectItem item) { 97 _children.add(item); 98 item._parent = this; 99 item._project = _project; 100 } 101 bool loadDir(string path) { 102 string src = relativeToAbsolutePath(path); 103 if (exists(src) && isDir(src)) { 104 ProjectFolder dir = new ProjectFolder(src); 105 addChild(dir); 106 Log.d(" added project folder ", src); 107 dir.loadItems(); 108 return true; 109 } 110 return false; 111 } 112 bool loadFile(string path) { 113 string src = relativeToAbsolutePath(path); 114 if (exists(src) && isFile(src)) { 115 ProjectSourceFile f = new ProjectSourceFile(src); 116 addChild(f); 117 Log.d(" added project file ", src); 118 return true; 119 } 120 return false; 121 } 122 void loadItems() { 123 foreach(e; dirEntries(_filename, SpanMode.shallow)) { 124 string fn = baseName(e.name); 125 if (e.isDir) { 126 loadDir(fn); 127 } else if (e.isFile) { 128 loadFile(fn); 129 } 130 } 131 } 132 string relativeToAbsolutePath(string path) { 133 if (isAbsolute(path)) 134 return path; 135 return buildNormalizedPath(_filename, path); 136 } 137 } 138 139 /// Project source file 140 class ProjectSourceFile : ProjectItem { 141 this(string filename) { 142 super(filename); 143 } 144 } 145 146 class WorkspaceItem { 147 protected string _filename; 148 protected string _dir; 149 protected dstring _name; 150 protected dstring _description; 151 152 this(string fname = null) { 153 filename = fname; 154 } 155 156 /// file name of workspace item 157 @property string filename() { 158 return _filename; 159 } 160 161 /// workspace item directory 162 @property string dir() { 163 return _dir; 164 } 165 166 /// file name of workspace item 167 @property void filename(string fname) { 168 if (fname.length > 0) { 169 _filename = buildNormalizedPath(fname); 170 _dir = dirName(filename); 171 } else { 172 _filename = null; 173 _dir = null; 174 } 175 } 176 177 /// name 178 @property dstring name() { 179 return _name; 180 } 181 182 /// name 183 @property void name(dstring s) { 184 _name = s; 185 } 186 187 /// name 188 @property dstring description() { 189 return _description; 190 } 191 192 /// name 193 @property void description(dstring s) { 194 _description = s; 195 } 196 197 /// load 198 bool load(string fname) { 199 // override it 200 return false; 201 } 202 203 bool save(string fname = null) { 204 return false; 205 } 206 } 207 208 /// detect DMD source paths 209 string[] dmdSourcePaths() { 210 string[] res; 211 version(Windows) { 212 import dlangui.core.files; 213 string dmdPath = findExecutablePath("dmd"); 214 if (dmdPath) { 215 string dmdDir = buildNormalizedPath(dirName(dmdPath), "..", "..", "src"); 216 res ~= absolutePath(buildNormalizedPath(dmdDir, "druntime", "import")); 217 res ~= absolutePath(buildNormalizedPath(dmdDir, "phobos")); 218 } 219 } else { 220 res ~= "/usr/include/dmd/druntime/import"; 221 res ~= "/usr/include/dmd/phobos"; 222 } 223 return res; 224 } 225 226 /// DLANGIDE D project 227 class Project : WorkspaceItem { 228 protected Workspace _workspace; 229 protected bool _opened; 230 protected ProjectFolder _items; 231 protected ProjectSourceFile _mainSourceFile; 232 protected SettingsFile _projectFile; 233 234 protected string[] _sourcePaths; 235 protected string[] _builderSourcePaths; 236 237 238 this(string fname = null) { 239 super(fname); 240 _items = new ProjectFolder(fname); 241 } 242 243 /// returns project's own source paths 244 @property string[] sourcePaths() { return _sourcePaths; } 245 /// returns project's own source paths 246 @property string[] builderSourcePaths() { 247 if (!_builderSourcePaths) { 248 _builderSourcePaths = dmdSourcePaths(); 249 } 250 return _builderSourcePaths; 251 } 252 253 string relativeToAbsolutePath(string path) { 254 if (isAbsolute(path)) 255 return path; 256 return buildNormalizedPath(_dir, path); 257 } 258 259 @property ProjectSourceFile mainSourceFile() { return _mainSourceFile; } 260 @property ProjectFolder items() { 261 return _items; 262 } 263 264 @property Workspace workspace() { 265 return _workspace; 266 } 267 268 @property void workspace(Workspace p) { 269 _workspace = p; 270 } 271 272 @property string defWorkspaceFile() { 273 return buildNormalizedPath(_filename.dirName, toUTF8(name) ~ WORKSPACE_EXTENSION); 274 } 275 276 ProjectFolder findItems(string[] srcPaths) { 277 ProjectFolder folder = new ProjectFolder(_filename); 278 folder.project = this; 279 string path = relativeToAbsolutePath("src"); 280 if (folder.loadDir(path)) 281 _sourcePaths ~= path; 282 path = relativeToAbsolutePath("source"); 283 if (folder.loadDir(path)) 284 _sourcePaths ~= path; 285 foreach(customPath; srcPaths) { 286 path = relativeToAbsolutePath(customPath); 287 foreach(existing; _sourcePaths) 288 if (path.equal(existing)) 289 continue; // already exists 290 if (folder.loadDir(path)) 291 _sourcePaths ~= path; 292 } 293 return folder; 294 } 295 296 void findMainSourceFile() { 297 string n = toUTF8(name); 298 string[] mainnames = ["app.d", "main.d", n ~ ".d"]; 299 foreach(sname; mainnames) { 300 _mainSourceFile = findSourceFileItem(buildNormalizedPath(_dir, "src", sname)); 301 if (_mainSourceFile) 302 break; 303 _mainSourceFile = findSourceFileItem(buildNormalizedPath(_dir, "source", sname)); 304 if (_mainSourceFile) 305 break; 306 } 307 } 308 309 /// tries to find source file in project, returns found project source file item, or null if not found 310 ProjectSourceFile findSourceFileItem(ProjectItem dir, string filename, bool fullFileName=true) { 311 for (int i = 0; i < dir.childCount; i++) { 312 ProjectItem item = dir.child(i); 313 if (item.isFolder) { 314 ProjectSourceFile res = findSourceFileItem(item, filename, fullFileName); 315 if (res) 316 return res; 317 } else { 318 ProjectSourceFile res = cast(ProjectSourceFile)item; 319 if(res) 320 { 321 if(fullFileName && res.filename.equal(filename)) 322 return res; 323 else if (!fullFileName && res.filename.endsWith(filename)) 324 return res; 325 } 326 } 327 } 328 return null; 329 } 330 331 ProjectSourceFile findSourceFileItem(string filename, bool fullFileName=true) { 332 return findSourceFileItem(_items, filename, fullFileName); 333 } 334 335 override bool load(string fname = null) { 336 if (!_projectFile) 337 _projectFile = new SettingsFile(); 338 _mainSourceFile = null; 339 if (fname.length > 0) 340 filename = fname; 341 if (!_projectFile.load(_filename)) { 342 Log.e("failed to load project from file ", _filename); 343 return false; 344 } 345 Log.d("Reading project from file ", _filename); 346 347 try { 348 _name = toUTF32(_projectFile.getString("name")); 349 _description = toUTF32(_projectFile.getString("description")); 350 Log.d(" project name: ", _name); 351 Log.d(" project description: ", _description); 352 string[] srcPaths = _projectFile.getStringArray("sourcePaths"); 353 _items = findItems(srcPaths); 354 findMainSourceFile(); 355 356 Log.i("Project source paths: ", sourcePaths); 357 Log.i("Builder source paths: ", builderSourcePaths); 358 359 } catch (JSONException e) { 360 Log.e("Cannot parse json", e); 361 return false; 362 } catch (Exception e) { 363 Log.e("Cannot read project file", e); 364 return false; 365 } 366 return true; 367 } 368 } 369