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