1 module dlangide.workspace.project; 2 3 import dlangide.workspace.workspace; 4 import dlangide.workspace.projectsettings; 5 import dlangui.core.logger; 6 import dlangui.core.collections; 7 import dlangui.core.settings; 8 import std.algorithm; 9 import std.array : empty; 10 import std.file; 11 import std.path; 12 import std.process; 13 import std.utf; 14 15 string[] includePath; 16 17 /// return true if filename matches rules for workspace file names 18 bool isProjectFile(in string filename) pure nothrow { 19 return filename.baseName.equal("dub.json") || filename.baseName.equal("DUB.JSON") || filename.baseName.equal("package.json") || 20 filename.baseName.equal("dub.sdl") || filename.baseName.equal("DUB.SDL"); 21 } 22 23 string toForwardSlashSeparator(in string filename) pure nothrow { 24 char[] res; 25 foreach(ch; filename) { 26 if (ch == '\\') 27 res ~= '/'; 28 else 29 res ~= ch; 30 } 31 return cast(string)res; 32 } 33 34 /// project item 35 class ProjectItem { 36 protected Project _project; 37 protected ProjectItem _parent; 38 protected string _filename; 39 protected dstring _name; 40 41 this(string filename) { 42 _filename = buildNormalizedPath(filename); 43 _name = toUTF32(baseName(_filename)); 44 } 45 46 this() { 47 } 48 49 @property ProjectItem parent() { return _parent; } 50 51 @property Project project() { return _project; } 52 53 @property void project(Project p) { _project = p; } 54 55 @property string filename() { return _filename; } 56 57 @property dstring name() { return _name; } 58 59 @property string name8() { 60 return _name.toUTF8; 61 } 62 63 /// returns true if item is folder 64 @property const bool isFolder() { return false; } 65 /// returns child object count 66 @property int childCount() { return 0; } 67 /// returns child item by index 68 ProjectItem child(int index) { return null; } 69 70 void refresh() { 71 } 72 73 ProjectSourceFile findSourceFile(string projectFileName, string fullFileName) { 74 if (fullFileName.equal(_filename)) 75 return cast(ProjectSourceFile)this; 76 if (project && projectFileName.equal(project.absoluteToRelativePath(_filename))) 77 return cast(ProjectSourceFile)this; 78 return null; 79 } 80 81 @property bool isDSourceFile() { 82 if (isFolder) 83 return false; 84 return filename.endsWith(".d") || filename.endsWith(".dd") || filename.endsWith(".dd") || filename.endsWith(".di") || filename.endsWith(".dh") || filename.endsWith(".ddoc"); 85 } 86 87 @property bool isJsonFile() { 88 if (isFolder) 89 return false; 90 return filename.endsWith(".json") || filename.endsWith(".JSON"); 91 } 92 93 @property bool isDMLFile() { 94 if (isFolder) 95 return false; 96 return filename.endsWith(".dml") || filename.endsWith(".DML"); 97 } 98 99 @property bool isXMLFile() { 100 if (isFolder) 101 return false; 102 return filename.endsWith(".xml") || filename.endsWith(".XML"); 103 } 104 } 105 106 /// Project folder 107 class ProjectFolder : ProjectItem { 108 protected ObjectList!ProjectItem _children; 109 110 this(string filename) { 111 super(filename); 112 } 113 114 @property override const bool isFolder() { 115 return true; 116 } 117 @property override int childCount() { 118 return _children.count; 119 } 120 /// returns child item by index 121 override ProjectItem child(int index) { 122 return _children[index]; 123 } 124 void addChild(ProjectItem item) { 125 _children.add(item); 126 item._parent = this; 127 item._project = _project; 128 } 129 ProjectItem childByPathName(string path) { 130 for (int i = 0; i < _children.count; i++) { 131 if (_children[i].filename.equal(path)) 132 return _children[i]; 133 } 134 return null; 135 } 136 ProjectItem childByName(dstring s) { 137 for (int i = 0; i < _children.count; i++) { 138 if (_children[i].name.equal(s)) 139 return _children[i]; 140 } 141 return null; 142 } 143 144 override ProjectSourceFile findSourceFile(string projectFileName, string fullFileName) { 145 for (int i = 0; i < _children.count; i++) { 146 if (ProjectSourceFile res = _children[i].findSourceFile(projectFileName, fullFileName)) 147 return res; 148 } 149 return null; 150 } 151 152 bool loadDir(string path) { 153 string src = relativeToAbsolutePath(path); 154 if (exists(src) && isDir(src)) { 155 ProjectFolder existing = cast(ProjectFolder)childByPathName(src); 156 if (existing) { 157 if (existing.isFolder) 158 existing.loadItems(); 159 return true; 160 } 161 auto dir = new ProjectFolder(src); 162 addChild(dir); 163 Log.d(" added project folder ", src); 164 dir.loadItems(); 165 return true; 166 } 167 return false; 168 } 169 170 bool loadFile(string path) { 171 string src = relativeToAbsolutePath(path); 172 if (exists(src) && isFile(src)) { 173 ProjectItem existing = childByPathName(src); 174 if (existing) 175 return true; 176 auto f = new ProjectSourceFile(src); 177 addChild(f); 178 Log.d(" added project file ", src); 179 return true; 180 } 181 return false; 182 } 183 184 void loadItems() { 185 bool[string] loaded; 186 string path = _filename; 187 if (exists(path) && isFile(path)) 188 path = dirName(path); 189 foreach(e; dirEntries(path, SpanMode.shallow)) { 190 string fn = baseName(e.name); 191 if (e.isDir) { 192 loadDir(fn); 193 loaded[fn] = true; 194 } else if (e.isFile) { 195 loadFile(fn); 196 loaded[fn] = true; 197 } 198 } 199 // removing non-reloaded items 200 for (int i = _children.count - 1; i >= 0; i--) { 201 if (!(toUTF8(_children[i].name) in loaded)) { 202 _children.remove(i); 203 } 204 } 205 sortItems(); 206 } 207 208 /// predicate for sorting project items 209 static bool compareProjectItemsLess(ProjectItem item1, ProjectItem item2) { 210 return ((item1.isFolder && !item2.isFolder) || ((item1.isFolder == item2.isFolder) && (item1.name < item2.name))); 211 } 212 213 void sortItems() { 214 import std.algorithm.sorting : sort; 215 sort!compareProjectItemsLess(_children.asArray); 216 } 217 218 string relativeToAbsolutePath(string path) { 219 if (isAbsolute(path)) 220 return path; 221 string fn = _filename; 222 if (exists(fn) && isFile(fn)) 223 fn = dirName(fn); 224 return buildNormalizedPath(fn, path); 225 } 226 227 override void refresh() { 228 loadItems(); 229 } 230 } 231 232 233 /// Project source file 234 class ProjectSourceFile : ProjectItem { 235 this(string filename) { 236 super(filename); 237 } 238 /// file path relative to project directory 239 @property string projectFilePath() { 240 return project.absoluteToRelativePath(filename); 241 } 242 243 void setFilename(string filename) { 244 _filename = buildNormalizedPath(filename); 245 _name = toUTF32(baseName(_filename)); 246 } 247 } 248 249 class WorkspaceItem { 250 protected string _filename; 251 protected string _dir; 252 protected dstring _name; 253 protected dstring _originalName; 254 protected dstring _description; 255 256 this(string fname = null) { 257 filename = fname; 258 } 259 260 /// file name of workspace item 261 @property string filename() { return _filename; } 262 263 /// workspace item directory 264 @property string dir() { return _dir; } 265 266 /// file name of workspace item 267 @property void filename(string fname) { 268 if (fname.length > 0) { 269 _filename = buildNormalizedPath(fname); 270 _dir = dirName(filename); 271 } else { 272 _filename = null; 273 _dir = null; 274 } 275 } 276 277 /// name 278 @property dstring name() { return _name; } 279 280 @property string name8() { 281 return _name.toUTF8; 282 } 283 284 /// name 285 @property void name(dstring s) { _name = s; } 286 287 /// description 288 @property dstring description() { return _description; } 289 /// description 290 @property void description(dstring s) { _description = s; } 291 292 /// load 293 bool load(string fname) { 294 // override it 295 return false; 296 } 297 298 bool save(string fname = null) { 299 return false; 300 } 301 } 302 303 /// detect DMD source paths 304 string[] dmdSourcePaths() { 305 string[] res; 306 307 if(!includePath.empty){ 308 res ~= includePath; 309 } 310 311 version(Windows) { 312 import dlangui.core.files; 313 string dmdPath = findExecutablePath("dmd"); 314 if (dmdPath) { 315 string dmdDir = buildNormalizedPath(dirName(dmdPath), "..", "..", "src"); 316 res ~= absolutePath(buildNormalizedPath(dmdDir, "druntime", "import")); 317 res ~= absolutePath(buildNormalizedPath(dmdDir, "phobos")); 318 } 319 } else { 320 res ~= "/usr/include/dmd/druntime/import"; 321 res ~= "/usr/include/dmd/phobos"; 322 } 323 return res; 324 } 325 326 /// Stores info about project configuration 327 struct ProjectConfiguration { 328 /// name used to build the project 329 string name; 330 /// type, for libraries one can run tests, for apps - execute them 331 Type type; 332 333 /// How to display default configuration in ui 334 immutable static string DEFAULT_NAME = "default"; 335 /// Default project configuration 336 immutable static ProjectConfiguration DEFAULT = ProjectConfiguration(DEFAULT_NAME, Type.Default); 337 338 /// Type of configuration 339 enum Type { 340 Default, 341 Executable, 342 Library 343 } 344 345 private static Type parseType(string s) 346 { 347 switch(s) 348 { 349 case "executable": return Type.Executable; 350 case "library": return Type.Library; 351 case "dynamicLibrary": return Type.Library; 352 case "staticLibrary": return Type.Library; 353 default: return Type.Default; 354 } 355 } 356 357 /// parsing from setting file 358 static ProjectConfiguration[string] load(Setting s) 359 { 360 ProjectConfiguration[string] res = [DEFAULT_NAME: DEFAULT]; 361 Setting configs = s.objectByPath("configurations"); 362 if(configs is null || configs.type != SettingType.ARRAY) 363 return res; 364 365 foreach(conf; configs) { 366 if(!conf.isObject) continue; 367 Type t = Type.Default; 368 if(auto typeName = conf.getString("targetType")) 369 t = parseType(typeName); 370 if (string confName = conf.getString("name")) 371 res[confName] = ProjectConfiguration(confName, t); 372 } 373 return res; 374 } 375 } 376 377 /// DLANGIDE D project 378 class Project : WorkspaceItem { 379 protected Workspace _workspace; 380 protected bool _opened; 381 protected ProjectFolder _items; 382 protected ProjectSourceFile _mainSourceFile; 383 protected SettingsFile _projectFile; 384 protected ProjectSettings _settingsFile; 385 protected bool _isDependency; 386 protected bool _isSubproject; 387 protected bool _isEmbeddedSubproject; 388 protected dstring _baseProjectName; 389 protected string _dependencyVersion; 390 391 protected string[] _sourcePaths; 392 protected string[] _builderSourcePaths; 393 protected ProjectConfiguration[string] _configurations; 394 395 this(Workspace ws, string fname = null, string dependencyVersion = null) { 396 super(fname); 397 _workspace = ws; 398 399 if (_workspace) { 400 foreach(obj; _workspace.includePath.array) 401 includePath ~= obj.str; 402 } 403 404 _items = new ProjectFolder(fname); 405 _dependencyVersion = dependencyVersion; 406 _isDependency = _dependencyVersion.length > 0; 407 _projectFile = new SettingsFile(fname); 408 } 409 410 void setBaseProject(Project p) { 411 if (p) { 412 _isSubproject = true; 413 _isDependency = p._isDependency; 414 _baseProjectName = p._originalName; 415 _dependencyVersion = p._dependencyVersion; 416 } else { 417 _isSubproject = false; 418 } 419 } 420 421 void setSubprojectJson(Setting s) { 422 if (!_projectFile) 423 _projectFile = new SettingsFile(); 424 _isEmbeddedSubproject = true; 425 _projectFile.replaceSetting(s.clone); 426 } 427 428 @property ProjectSettings settings() { 429 if (!_settingsFile) { 430 _settingsFile = new ProjectSettings(settingsFileName); 431 _settingsFile.updateDefaults(); 432 _settingsFile.load(); 433 _settingsFile.save(); 434 } 435 return _settingsFile; 436 } 437 438 @property string settingsFileName() { 439 return buildNormalizedPath(dir, toUTF8(name) ~ ".settings"); 440 } 441 442 @property bool isDependency() { return _isDependency; } 443 @property string dependencyVersion() { return _dependencyVersion; } 444 445 /// returns project configurations 446 @property const(ProjectConfiguration[string]) configurations() const 447 { 448 return _configurations; 449 } 450 451 /// direct access to project file (json) 452 @property SettingsFile content() { return _projectFile; } 453 454 /// name 455 override @property dstring name() { 456 return super.name(); 457 } 458 459 /// name 460 override @property void name(dstring s) { 461 super.name(s); 462 _projectFile.setString("name", toUTF8(s)); 463 } 464 465 /// name 466 override @property dstring description() { 467 return super.description(); 468 } 469 470 /// name 471 override @property void description(dstring s) { 472 super.description(s); 473 _projectFile.setString("description", toUTF8(s)); 474 } 475 476 /// returns project's own source paths 477 @property string[] sourcePaths() { return _sourcePaths; } 478 /// returns project's own source paths 479 @property string[] builderSourcePaths() { 480 if (!_builderSourcePaths) { 481 _builderSourcePaths = dmdSourcePaths(); 482 } 483 return _builderSourcePaths; 484 } 485 486 /// returns first source folder for project or null if not found 487 ProjectFolder firstSourceFolder() { 488 for(int i = 0; i < _items.childCount; i++) { 489 if (_items.child(i).isFolder) 490 return cast(ProjectFolder)_items.child(i); 491 } 492 return null; 493 } 494 495 ProjectSourceFile findSourceFile(string projectFileName, string fullFileName) { 496 return _items ? _items.findSourceFile(projectFileName, fullFileName) : null; 497 } 498 499 private static void addUnique(ref string[] dst, string[] items) { 500 foreach(item; items) { 501 if (!canFind(dst, item)) 502 dst ~= item; 503 } 504 } 505 @property string[] importPaths() { 506 string[] res; 507 addUnique(res, sourcePaths); 508 addUnique(res, builderSourcePaths); 509 foreach(dep; _dependencies) { 510 addUnique(res, dep.sourcePaths); 511 } 512 return res; 513 } 514 515 string relativeToAbsolutePath(string path) { 516 if (isAbsolute(path)) 517 return path; 518 return buildNormalizedPath(_dir, path); 519 } 520 521 string absoluteToRelativePath(string path) { 522 if (!isAbsolute(path)) 523 return path; 524 return relativePath(path, _dir); 525 } 526 527 @property ProjectSourceFile mainSourceFile() { return _mainSourceFile; } 528 @property ProjectFolder items() { return _items; } 529 530 @property Workspace workspace() { return _workspace; } 531 532 @property void workspace(Workspace p) { _workspace = p; } 533 534 @property string defWorkspaceFile() { 535 return buildNormalizedPath(_filename.dirName, toUTF8(name) ~ WORKSPACE_EXTENSION); 536 } 537 538 @property bool isExecutable() { 539 // TODO: use targetType 540 return true; 541 } 542 543 /// return executable file name, or null if it's library project or executable is not found 544 @property string executableFileName() { 545 if (!isExecutable) 546 return null; 547 string exename = toUTF8(name); 548 exename = _projectFile.getString("targetName", exename); 549 // TODO: use targetName 550 version (Windows) { 551 exename = exename ~ ".exe"; 552 } 553 string targetPath = _projectFile.getString("targetPath", null); 554 string exePath; 555 if (targetPath.length) 556 exePath = buildNormalizedPath(_filename.dirName, targetPath, exename); // int $targetPath directory 557 else 558 exePath = buildNormalizedPath(_filename.dirName, exename); // in project directory 559 return exePath; 560 } 561 562 /// working directory for running and debugging project 563 @property string workingDirectory() { 564 // TODO: get from settings 565 return _filename.dirName; 566 } 567 568 /// commandline parameters for running and debugging project 569 @property string runArgs() { 570 // TODO: get from settings 571 return null; 572 } 573 574 @property bool runInExternalConsole() { 575 // TODO 576 return settings.runInExternalConsole; 577 } 578 579 ProjectFolder findItems(string[] srcPaths) { 580 auto folder = new ProjectFolder(_filename); 581 folder.project = this; 582 foreach(customPath; srcPaths) { 583 string path = relativeToAbsolutePath(customPath); 584 if (folder.loadDir(path)) 585 _sourcePaths ~= path; 586 } 587 return folder; 588 } 589 590 void refresh() { 591 for (int i = _items._children.count - 1; i >= 0; i--) { 592 if (_items._children[i].isFolder) 593 _items._children[i].refresh(); 594 } 595 } 596 597 void findMainSourceFile() { 598 string n = toUTF8(name); 599 string[] mainnames = ["app.d", "main.d", n ~ ".d"]; 600 foreach(sname; mainnames) { 601 _mainSourceFile = findSourceFileItem(buildNormalizedPath(_dir, "src", sname)); 602 if (_mainSourceFile) 603 break; 604 _mainSourceFile = findSourceFileItem(buildNormalizedPath(_dir, "source", sname)); 605 if (_mainSourceFile) 606 break; 607 } 608 } 609 610 /// tries to find source file in project, returns found project source file item, or null if not found 611 ProjectSourceFile findSourceFileItem(ProjectItem dir, string filename, bool fullFileName=true) { 612 foreach(i; 0 .. dir.childCount) { 613 ProjectItem item = dir.child(i); 614 if (item.isFolder) { 615 ProjectSourceFile res = findSourceFileItem(item, filename, fullFileName); 616 if (res) 617 return res; 618 } else { 619 auto res = cast(ProjectSourceFile)item; 620 if(res) 621 { 622 if(fullFileName && res.filename.equal(filename)) 623 return res; 624 else if (!fullFileName && res.filename.endsWith(filename)) 625 return res; 626 } 627 } 628 } 629 return null; 630 } 631 632 ProjectSourceFile findSourceFileItem(string filename, bool fullFileName=true) { 633 return findSourceFileItem(_items, filename, fullFileName); 634 } 635 636 protected Project[] _subPackages; 637 638 /// add item to string array ignoring duplicates 639 protected static void addUnique(ref string[] list, string item) { 640 foreach(s; list) 641 if (s == item) 642 return; 643 list ~= item; 644 } 645 646 /// add item to string array ignoring duplicates 647 protected void addRelativePathIfExists(ref string[] list, string item) { 648 item = relativeToAbsolutePath(item); 649 if (item.exists && item.isDir) 650 addUnique(list, item); 651 } 652 653 /// find source paths for project 654 protected string[] findSourcePaths() { 655 string[] res; 656 res.assumeSafeAppend; 657 string[] srcPaths = _projectFile.getStringArray("sourcePaths"); 658 foreach(s; srcPaths) 659 addRelativePathIfExists(res, s); 660 Setting configs = _projectFile.objectByPath("configurations"); 661 if (configs) { 662 for (int i = 0; i < configs.length; i++) { 663 Setting s = configs[i]; 664 if (s) { 665 string[] paths = s.getStringArray("sourcePaths"); 666 foreach(path; paths) 667 addRelativePathIfExists(res, path); 668 } 669 } 670 } 671 if (!res.length) { 672 addRelativePathIfExists(res, "src"); 673 addRelativePathIfExists(res, "source"); 674 } 675 676 return res; 677 } 678 679 void processSubpackages() { 680 import dlangui.core.files; 681 _subPackages.length = 0; 682 Setting subPackages = _projectFile.settingByPath("subPackages", SettingType.ARRAY, false); 683 if (subPackages) { 684 string p = _projectFile.filename.dirName; 685 for(int i = 0; i < subPackages.length; i++) { 686 Setting sp = subPackages[i]; 687 if (sp.isString) { 688 // string 689 string path = convertPathDelimiters(sp.str); 690 string relative = relativePath(path, p); 691 path = buildNormalizedPath(absolutePath(relative, p)); 692 //Log.d("Subproject path: ", path); 693 string fn = DubPackageFinder.findPackageFile(path); 694 //Log.d("Subproject file: ", fn); 695 Project prj = new Project(_workspace, fn); 696 prj.setBaseProject(this); 697 if (prj.load()) { 698 Log.d("Loaded subpackage from file: ", fn); 699 _subPackages ~= prj; 700 if (_workspace) 701 _workspace.addDependencyProject(prj); 702 } else { 703 Log.w("Failed to load subpackage from file: ", fn); 704 } 705 } else if (sp.isObject) { 706 // object - file inside base project dub.json 707 Log.d("Subpackage is JSON object"); 708 string subname = sp.getString("name"); 709 if (subname) { 710 Project prj = new Project(_workspace, _filename ~ "@" ~ subname); 711 prj.setBaseProject(this); 712 prj.setSubprojectJson(sp); 713 bool res = prj.processLoadedProject(); 714 if (res) { 715 Log.d("Added embedded subpackage ", subname); 716 _subPackages ~= prj; 717 if (_workspace) 718 _workspace.addDependencyProject(prj); 719 } else { 720 Log.w("Error while processing embedded subpackage"); 721 } 722 } 723 } 724 } 725 } 726 } 727 728 /// parse data from _projectFile after loading 729 bool processLoadedProject() { 730 // 731 _mainSourceFile = null; 732 try { 733 _name = toUTF32(_projectFile.getString("name")); 734 _originalName = _name; 735 if (_baseProjectName) { 736 _name = _baseProjectName ~ ":" ~ _name; 737 } 738 if (_isDependency) { 739 _name ~= "-"d; 740 _name ~= toUTF32(_dependencyVersion.startsWith("~") ? _dependencyVersion[1..$] : _dependencyVersion); 741 } 742 _description = toUTF32(_projectFile.getString("description")); 743 Log.d(" project name: ", _name); 744 Log.d(" project description: ", _description); 745 746 processSubpackages(); 747 748 string[] srcPaths = findSourcePaths(); 749 _sourcePaths = null; 750 _items = findItems(srcPaths); 751 findMainSourceFile(); 752 753 Log.i("Project source paths: ", sourcePaths); 754 Log.i("Builder source paths: ", builderSourcePaths); 755 if (!_isDependency) 756 loadSelections(); 757 758 _configurations = ProjectConfiguration.load(_projectFile); 759 Log.i("Project configurations: ", _configurations); 760 761 762 } catch (Exception e) { 763 Log.e("Cannot read project file", e); 764 return false; 765 } 766 _items.loadFile(filename); 767 return true; 768 } 769 770 override bool load(string fname = null) { 771 if (!_projectFile) 772 _projectFile = new SettingsFile(); 773 if (fname.length > 0) 774 filename = fname; 775 if (!_projectFile.load(_filename)) { 776 Log.e("failed to load project from file ", _filename); 777 return false; 778 } 779 Log.d("Reading project from file ", _filename); 780 return processLoadedProject(); 781 782 } 783 784 override bool save(string fname = null) { 785 if (_isEmbeddedSubproject) 786 return false; 787 if (fname !is null) 788 filename = fname; 789 assert(filename !is null); 790 return _projectFile.save(filename, true); 791 } 792 793 protected Project[] _dependencies; 794 @property Project[] dependencies() { return _dependencies; } 795 796 Project findDependencyProject(string filename) { 797 foreach(dep; _dependencies) { 798 if (dep.filename.equal(filename)) 799 return dep; 800 } 801 return null; 802 } 803 804 bool loadSelections() { 805 Project[] newdeps; 806 _dependencies.length = 0; 807 auto finder = new DubPackageFinder; 808 scope(exit) destroy(finder); 809 SettingsFile selectionsFile = new SettingsFile(buildNormalizedPath(_dir, "dub.selections.json")); 810 if (!selectionsFile.load()) { 811 _dependencies = newdeps; 812 return false; 813 } 814 Setting versions = selectionsFile.objectByPath("versions"); 815 if (!versions.isObject) { 816 _dependencies = newdeps; 817 return false; 818 } 819 string[string] versionMap = versions.strMap; 820 foreach(packageName, packageVersion; versionMap) { 821 string fn = finder.findPackage(packageName, packageVersion); 822 Log.d("dependency ", packageName, " ", packageVersion, " : ", fn ? fn : "NOT FOUND"); 823 if (fn) { 824 Project p = findDependencyProject(fn); 825 if (p) { 826 Log.d("Found existing dependency project ", fn); 827 newdeps ~= p; 828 continue; 829 } 830 p = new Project(_workspace, fn, packageVersion); 831 if (p.load()) { 832 newdeps ~= p; 833 if (_workspace) 834 _workspace.addDependencyProject(p); 835 } else { 836 Log.e("cannot load dependency package ", packageName, " ", packageVersion, " from file ", fn); 837 destroy(p); 838 } 839 } 840 } 841 _dependencies = newdeps; 842 return true; 843 } 844 } 845 846 class DubPackageFinder { 847 string systemDubPath; 848 string userDubPath; 849 string tempPath; 850 this() { 851 version(Windows){ 852 systemDubPath = buildNormalizedPath(environment.get("ProgramData"), "dub", "packages"); 853 userDubPath = buildNormalizedPath(environment.get("APPDATA"), "dub", "packages"); 854 tempPath = buildNormalizedPath(environment.get("TEMP"), "dub", "packages"); 855 } else version(Posix){ 856 systemDubPath = "/var/lib/dub/packages"; 857 userDubPath = buildNormalizedPath(environment.get("HOME"), ".dub", "packages"); 858 if(!userDubPath.isAbsolute) 859 userDubPath = buildNormalizedPath(getcwd(), userDubPath); 860 tempPath = "/tmp/packages"; 861 } 862 } 863 864 /// find package file (dub.json, package.json) in specified dir; returns absoulute path to found file or null if not found 865 static string findPackageFile(string pathName) { 866 string fn = buildNormalizedPath(pathName, "dub.json"); 867 if (fn.exists && fn.isFile) 868 return fn; 869 fn = buildNormalizedPath(pathName, "dub.sdl"); 870 if (fn.exists && fn.isFile) 871 return fn; 872 fn = buildNormalizedPath(pathName, "package.json"); 873 if (fn.exists && fn.isFile) 874 return fn; 875 return null; 876 } 877 878 protected string findPackage(string packageDir, string packageName, string packageVersion) { 879 string fullName = packageVersion.startsWith("~") ? packageName ~ "-" ~ packageVersion[1..$] : packageName ~ "-" ~ packageVersion; 880 string pathName = absolutePath(buildNormalizedPath(packageDir, fullName)); 881 if (pathName.exists && pathName.isDir) { 882 string fn = findPackageFile(pathName); 883 if (fn) 884 return fn; 885 // new DUB support - with package subdirectory 886 fn = findPackageFile(buildNormalizedPath(pathName, packageName)); 887 if (fn) 888 return fn; 889 } 890 return null; 891 } 892 893 string findPackage(string packageName, string packageVersion) { 894 string res = null; 895 res = findPackage(userDubPath, packageName, packageVersion); 896 if (res) 897 return res; 898 res = findPackage(systemDubPath, packageName, packageVersion); 899 return res; 900 } 901 } 902 903 bool isValidProjectName(in string s) pure { 904 if (s.empty) 905 return false; 906 return reduce!q{ a && (b == '_' || b == '-' || std.ascii.isAlphaNum(b)) }(true, s); 907 } 908 909 bool isValidModuleName(in string s) pure { 910 if (s.empty) 911 return false; 912 return reduce!q{ a && (b == '_' || std.ascii.isAlphaNum(b)) }(true, s); 913 } 914 915 bool isValidFileName(in string s) pure { 916 if (s.empty) 917 return false; 918 return reduce!q{ a && (b == '_' || b == '.' || b == '-' || std.ascii.isAlphaNum(b)) }(true, s); 919 } 920 921 unittest { 922 assert(!isValidProjectName("")); 923 assert(isValidProjectName("project")); 924 assert(isValidProjectName("cool_project")); 925 assert(isValidProjectName("project-2")); 926 assert(!isValidProjectName("project.png")); 927 assert(!isValidProjectName("[project]")); 928 assert(!isValidProjectName("<project/>")); 929 assert(!isValidModuleName("")); 930 assert(isValidModuleName("module")); 931 assert(isValidModuleName("awesome_module2")); 932 assert(!isValidModuleName("module-2")); 933 assert(!isValidModuleName("module.png")); 934 assert(!isValidModuleName("[module]")); 935 assert(!isValidModuleName("<module>")); 936 assert(!isValidFileName("")); 937 assert(isValidFileName("file")); 938 assert(isValidFileName("file_2")); 939 assert(isValidFileName("file-2")); 940 assert(isValidFileName("file.txt")); 941 assert(!isValidFileName("[file]")); 942 assert(!isValidFileName("<file>")); 943 } 944 945 class EditorBookmark { 946 string file; 947 string fullFilePath; 948 string projectFilePath; 949 int line; 950 string projectName; 951 }