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