UNPKG

5.06 kBJavaScriptView Raw
1var log = require("phnq_log").create(__filename);
2var phnq_core = require("phnq_core");
3var fs = require("fs");
4var cdn = require("./cdn");
5var _path = require("path");
6var Widget = require("./widget");
7var _ = require("underscore");
8var config = require("./config");
9var aggregator = require("./aggregator");
10
11var instance = null;
12
13module.exports =
14{
15 appRoot: _path.dirname(process.argv[1]),
16
17 instance: function()
18 {
19 return instance || (instance = new WidgetManager());
20 }
21};
22
23var WidgetManager = phnq_core.clazz(
24{
25 init: function()
26 {
27 this.scanPaths = [];
28 this.widgets = null;
29 this.lastScanMillis = 0;
30 this.watched = {};
31 this.lessKeys = null;
32 },
33
34 addScanPath: function(path)
35 {
36 path = _path.join(module.exports.appRoot, path);
37 try
38 {
39 fs.statSync(path);
40 log.debug("Added widget scan path: ", path);
41 this.scanPaths.push(path);
42 }
43 catch(ex)
44 {
45 log.debug("Error adding widget scan path: ", ex.toString());
46 }
47 },
48
49 getAllTypes: function()
50 {
51 return _.keys(this.widgets);
52 },
53
54 getWidget: function(type)
55 {
56 this.scan();
57 return this.widgets[type];
58 },
59
60 watch: function(path)
61 {
62 if(!this.watched[path])
63 {
64 var _this = this;
65 this.watched[path] = true;
66 fs.watch(path, {persistent:false}, function()
67 {
68 _this.widgets = null;
69 });
70 }
71 },
72
73 getTestCode: function(baseUrl)
74 {
75 var buf = [];
76 this.scan();
77 _.each(this.widgets, function(widget)
78 {
79 buf.push(widget.getTestCode(baseUrl));
80 });
81 return buf.join("");
82 },
83
84 sortDependencies: function(types)
85 {
86 var _this = this;
87
88 types.sort(function(t1, t2)
89 {
90 var w1 = _this.getWidget(t1);
91 var w2 = _this.getWidget(t2);
92 if(!w1 || !w2)
93 {
94 return 0;
95 }
96 var d1on2 = w1.dependsOnType(w2.type);
97 var d2on1 = w2.dependsOnType(w1.type);
98
99 if(d1on2 && d2on1)
100 {
101 var w1Deps = w1.getDependencies();
102 var w2Deps = w2.getDependencies();
103 log.debug("Circular dependency detected: ", w1.type, w1Deps, w2.type, w2Deps);
104 return 0;
105 }
106
107 if(d1on2)
108 {
109 return 1;
110 }
111 else if(d2on1)
112 {
113 return -1;
114 }
115 else
116 {
117 return 0;
118 }
119 });
120
121 return types;
122 },
123
124 scan: function()
125 {
126 if(this.widgets)
127 return;
128
129 var nowMillis = new Date().getTime();
130
131 if((nowMillis - this.lastScanMillis) < 2000)
132 return;
133
134 log.info("Scanning for widgets...");
135
136 this.lastScanMillis = nowMillis;
137
138 if(this.scanPaths.length == 0)
139 this.addScanPath("widgets");
140
141 var _this = this;
142
143 this.widgets = {};
144 this.lessKeys = [];
145 aggregator.clear();
146 cdn.needsSync = true;
147
148 var paths = this.scanPaths.slice(0).reverse();
149 paths.push(_path.join(__dirname, "../widgets"));
150 paths = _.uniq(paths); // in case we're running app.js in this package
151
152 _.each(_.uniq(paths), function(path)
153 {
154 _this.addWidgetsAtPath(path, path);
155 });
156
157 _.each(this.widgets, function(widget, type)
158 {
159 var script = widget.getScript();
160 if(script)
161 aggregator.registerString("widget_"+type+"_script", script);
162
163 var style = widget.getStyle();
164 if(style)
165 aggregator.registerString("widget_"+type+"_style", style);
166 });
167
168 var buf = [];
169 buf.push(".wph {visibility: hidden; width: 1px}\n");
170 buf.push(".loadError { padding: 5px; margin: 5px; background: #c00; color: #fff; }\n");
171 aggregator.registerString("widgetshell_head_style", buf.join(""));
172
173 aggregator.registerString("client_boot", aggregator.getClientBoot(true));
174 },
175
176 addWidgetsAtPath: function(path, basePath)
177 {
178 var _this = this;
179
180 _this.watch(path);
181
182 var names = fs.readdirSync(path);
183
184 _.each(names, function(name)
185 {
186 var f = _path.join(path, name);
187 var stat = fs.statSync(f);
188
189 if(stat && stat.isDirectory())
190 {
191 if(name != "_i18n" && name != "_static")
192 {
193 _this.addWidgetsAtPath(f, basePath);
194 }
195 }
196 else
197 {
198 if(name.match(/.*\.less$/))
199 {
200 var lessKey = "less_"+_path.relative(basePath, f);
201 aggregator.registerString(lessKey, fs.readFileSync(f, "UTF-8"));
202 _this.lessKeys.push(lessKey);
203 }
204 else
205 {
206 var m = /[^\.]*\.(ejs|js|css)$/.exec(name.replace(/\.html$/, ".html.ejs"));
207 if(m)
208 {
209 var type = _path.basename(_path.dirname(f));
210 if(!type.match(/\./))
211 type = _path.relative(basePath, _path.dirname(f)).split("/").join(".");
212
213 _this.watch(f);
214
215 var filename = _path.basename(f);
216
217 var ext = m[1];
218 var widget = _this.widgets[type] || (_this.widgets[type] = new Widget(type, _path.dirname(f)));
219 var partialMatch = /^_([^.]*).html(\.ejs)?/.exec(filename);
220 var handlerMatch = /^_([^.]*)\.js/.exec(filename);
221 var testMatch = /^([^.]*)\.test\.js/.exec(filename);
222 if(partialMatch)
223 {
224 widget.partials[partialMatch[1]] = f;
225 }
226 else if(handlerMatch)
227 {
228 widget.remoteHandlerFile = f;
229 }
230 else if(testMatch)
231 {
232 widget.tests[testMatch[1]] = f;
233 }
234 else
235 {
236 widget[ext+"File"] = f;
237
238 widget[ext+"Files"] = widget[ext+"Files"] || [];
239 widget[ext+"Files"].push(f);
240 }
241 }
242 }
243 }
244 });
245 }
246});