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