1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 | const _ = require('underscore');
|
8 | const path = require('path');
|
9 | const utils = require('./utils');
|
10 | const debug = require('debug')('firedoc:local');
|
11 | const MarkdownIt = require('markdown-it');
|
12 | const Handlebars = require('handlebars');
|
13 | const md = new MarkdownIt();
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 | var Locals = {
|
20 |
|
21 | |
22 |
|
23 |
|
24 | context: null,
|
25 |
|
26 | |
27 |
|
28 |
|
29 | options: {},
|
30 |
|
31 | |
32 |
|
33 |
|
34 | ast: {},
|
35 |
|
36 | |
37 |
|
38 |
|
39 | get project () {
|
40 | var root;
|
41 | if (path.isAbsolute(this.options.dest)) {
|
42 | root = this.options.dest;
|
43 | } else {
|
44 | root = path.join(process.cwd(), this.options.dest || '');
|
45 | }
|
46 | var proj = this.ast.project;
|
47 | proj.root = root;
|
48 | proj.base = this.options.http ? '' : proj.root;
|
49 | proj.assets = path.join(root, '/assets');
|
50 | return proj;
|
51 | },
|
52 |
|
53 | |
54 |
|
55 |
|
56 | get i18n () {
|
57 | try {
|
58 | var defaults = require(this.options.theme + '/i18n/en.json');
|
59 | var extra = {};
|
60 | if (this.options.lang) {
|
61 | extra = require(this.options.theme + '/i18n/' + this.options.lang + '.json');
|
62 | }
|
63 | var ret = _.extend(defaults, extra);
|
64 | ret.LANG = this.options.lang || 'en';
|
65 | return ret;
|
66 | } catch (e) {
|
67 | return {};
|
68 | }
|
69 | },
|
70 |
|
71 | |
72 |
|
73 |
|
74 | get modules () {
|
75 | var self = this;
|
76 | return Object.keys(self.ast.modules).map(
|
77 | function (name) {
|
78 | var mod = self.ast.modules[name];
|
79 | if (self.options.withSrc) {
|
80 | mod = self.context.addFoundAt(mod);
|
81 | }
|
82 | var description = utils.localize(mod.description, self.options.lang);
|
83 | mod.description = self.parseCode(self.markdown(description));
|
84 | if (mod.example) {
|
85 | if (!_.isArray(mod.example)) {
|
86 | mod.example = [mod.example];
|
87 | }
|
88 | mod.example = mod.example.map(function (v) {
|
89 | return self.parseCode(self.markdown(v.trim()))
|
90 | }).join('');
|
91 | }
|
92 | mod.members = mod.members || [];
|
93 | mod.project = self.project;
|
94 | mod.globals = self;
|
95 | mod.i18n = self.i18n;
|
96 | return mod;
|
97 | }
|
98 | );
|
99 | },
|
100 |
|
101 | |
102 |
|
103 |
|
104 | get classes () {
|
105 | var self = this;
|
106 | return Object.keys(self.ast.classes).map(
|
107 | function (name) {
|
108 | var clazz = self.ast.classes[name];
|
109 | if (self.options.withSrc) {
|
110 | clazz = self.context.addFoundAt(clazz);
|
111 | }
|
112 | clazz = self.appendClassToModule(clazz);
|
113 | var description = utils.localize(clazz.description, self.options.lang);
|
114 | clazz.description = self.parseCode(self.markdown(description));
|
115 | if (clazz.example) {
|
116 | if (!_.isArray(clazz.example)) {
|
117 | clazz.example = [clazz.example];
|
118 | }
|
119 | clazz.example = clazz.example.map(function (v) {
|
120 | return self.parseCode(self.markdown(v.trim()))
|
121 | }).join('');
|
122 | }
|
123 | clazz.members = clazz.members || [];
|
124 | clazz.project = self.project;
|
125 | clazz.globals = self;
|
126 | clazz.i18n = self.i18n;
|
127 | clazz.inheritance = self.getInheritanceTree(clazz);
|
128 | if (clazz.isConstructor) {
|
129 | clazz.constructor = self.buildMember(clazz, true);
|
130 | }
|
131 | return clazz;
|
132 | }
|
133 | );
|
134 | },
|
135 |
|
136 | |
137 |
|
138 |
|
139 | get files () {
|
140 | var self = this;
|
141 | return Object.keys(self.ast.files).map(
|
142 | function (name) {
|
143 | var file = self.ast.files[name];
|
144 | file.i18n = self.i18n;
|
145 | return file;
|
146 | }
|
147 | );
|
148 | },
|
149 |
|
150 | |
151 |
|
152 |
|
153 |
|
154 | initMarkdownRulers: function () {
|
155 | var ast = this.ast;
|
156 | var options = this.options;
|
157 | md.renderer.rules.link_open = function (tokens, idx, ops, env, self) {
|
158 | var token = tokens[idx];
|
159 | if (token && _.isArray(token.attrs)) {
|
160 | token.attrs = token.attrs.map(function (attr, idx) {
|
161 | if (attr[0] === 'href' &&
|
162 | /^https?:\/\
|
163 | var target = ast.namespacesMap[attr[1]];
|
164 | var ext = options.markdown ? '.md' : '.html';
|
165 | if (target && target.parent && target.itemtype) {
|
166 | var url = target.parent.type + '/' + target.parent.name + ext +
|
167 | '#' + target.itemtype + '_' + target.name;
|
168 | attr[1] = url;
|
169 | }
|
170 | }
|
171 | return attr;
|
172 | });
|
173 | }
|
174 | return self.renderToken(tokens, idx, options);
|
175 | };
|
176 | },
|
177 |
|
178 | |
179 |
|
180 |
|
181 |
|
182 |
|
183 |
|
184 | addFoundAt: function (a) {
|
185 | var self = this;
|
186 | var ext = this.options.markdown ? '.md' : '.html';
|
187 | if (a.file && a.line && !this.options.nocode) {
|
188 | a.foundAt = '../files/' + utils.filterFileName(a.file) + ext + '#l' + a.line;
|
189 | if (a.path) {
|
190 | a.foundAt = a.path + '#l' + a.line;
|
191 | }
|
192 | }
|
193 | return a;
|
194 | },
|
195 |
|
196 | |
197 |
|
198 |
|
199 |
|
200 |
|
201 |
|
202 |
|
203 |
|
204 | getMethodName: function (name, params) {
|
205 | return name + '(' + (params || []).map(function (v) {
|
206 | return v.name;
|
207 | }).join(', ') + ')';
|
208 | },
|
209 |
|
210 | |
211 |
|
212 |
|
213 |
|
214 |
|
215 |
|
216 |
|
217 | parseCode: function (html) {
|
218 | html = html || '';
|
219 | html = html.replace(/<pre><code>/g, '<pre class="code prettyprint"><code>\n');
|
220 |
|
221 | html = html.replace(/'/g, '\'');
|
222 | return _.unescape(html);
|
223 | },
|
224 |
|
225 | |
226 |
|
227 |
|
228 |
|
229 |
|
230 |
|
231 |
|
232 | markdown: function (data) {
|
233 | var self = this;
|
234 | if (this.options.markdown) {
|
235 | return data;
|
236 | }
|
237 | var html = _.unescape(md.render(data || ''));
|
238 |
|
239 | if (this.options.helpers || (html.indexOf('{{#crossLink') > -1)) {
|
240 | try {
|
241 |
|
242 |
|
243 |
|
244 | html = (Handlebars.compile(html))({});
|
245 | } catch (hError) {
|
246 | console.error(hError.stack);
|
247 |
|
248 | html = html.replace(/\\{/g, '{').replace(/\\}/g, '}');
|
249 | console.warn('Failed to parse Handlebars, probably an unknown helper, skiped');
|
250 | }
|
251 | }
|
252 | return html;
|
253 | },
|
254 |
|
255 | |
256 |
|
257 |
|
258 |
|
259 |
|
260 |
|
261 |
|
262 | appendClassToModule: function (clazz) {
|
263 | var mod = this.ast.modules[clazz.module];
|
264 | if (mod) {
|
265 | if (!_.isArray(mod.classes)) mod.classes = [];
|
266 | mod.classes.push(clazz);
|
267 | }
|
268 | return clazz;
|
269 | },
|
270 |
|
271 | |
272 |
|
273 |
|
274 |
|
275 |
|
276 |
|
277 | getInheritanceTree: function (clazz) {
|
278 | var children = [];
|
279 | this.ast.inheritedMembers.forEach(function (inherit) {
|
280 | var at = inherit.indexOf(clazz.name);
|
281 | if (at > -1 && at < inherit.length) {
|
282 | var curr = children;
|
283 | for (var i = at + 1; i < inherit.length; i++) {
|
284 | var name = inherit[i];
|
285 | var temp = {'name': name, 'children': []};
|
286 | var needNewChild = true;
|
287 | var pos;
|
288 |
|
289 | for (pos = 0; pos < curr.length; pos++) {
|
290 | if (curr[pos].name === name) {
|
291 | needNewChild = false;
|
292 | curr = curr[pos].children;
|
293 | break;
|
294 | }
|
295 | }
|
296 | if (needNewChild) {
|
297 | if (inherit.length - 1 === i) {
|
298 | delete temp.children;
|
299 | }
|
300 | curr.push(temp);
|
301 | if (temp.children) {
|
302 | curr = curr[curr.length - 1].children;
|
303 | }
|
304 | }
|
305 | }
|
306 | }
|
307 | });
|
308 | return children;
|
309 | },
|
310 |
|
311 | |
312 |
|
313 |
|
314 |
|
315 |
|
316 |
|
317 |
|
318 |
|
319 |
|
320 | buildMember: function (member, forceBeMethod ,parent) {
|
321 | var self = this;
|
322 | if (self.options.withSrc) {
|
323 | member = self.addFoundAt(member);
|
324 | }
|
325 | var description = utils.localize(member.description || '', this.options.lang);
|
326 | member.description = self.parseCode(self.markdown(description));
|
327 | member.hasAccessType = !!member.access;
|
328 | member.readonly = member.readonly === '';
|
329 | member.final = member.final === '';
|
330 | member.static = member.static === '';
|
331 | member.type = member.type || 'Unknown';
|
332 | member.config = member.itemtype === 'config';
|
333 | member.i18n = self.i18n;
|
334 |
|
335 |
|
336 |
|
337 | if (!member.class && member.module) {
|
338 | member.parent = self.ast.modules[member.module];
|
339 | } else {
|
340 | member.parent = self.ast.classes[member.class];
|
341 | }
|
342 |
|
343 | if (this.options.markdown) {
|
344 | member.markdownLink = utils.markdownLink(member.itemtype + ':' + member.name);
|
345 | }
|
346 | if (member.example) {
|
347 | if (!_.isArray(member.example)) {
|
348 | member.example = [member.example];
|
349 | }
|
350 | member.example = member.example.map(function (v) {
|
351 | return self.parseCode(self.markdown(v.trim()))
|
352 | }).join('');
|
353 | }
|
354 | if (parent) {
|
355 | var classMod = member.submodule || member.module;
|
356 | var parentMod = parent.submodule || parent.module;
|
357 | if (classMod !== parentMod) {
|
358 | member.providedBy = classMod;
|
359 | }
|
360 | }
|
361 | if (member.itemtype === 'method' || forceBeMethod) {
|
362 | member.methodDisplay = self.getMethodName(member.name, member.params);
|
363 | member.hasParams = (member.params || []).length > 0;
|
364 | if (member.hasParams) {
|
365 | _.each(member.params, function (param) {
|
366 | param.description = self.markdown(param.description);
|
367 | if (param.props && param.props.length > 0) {
|
368 | _.each(param.props, function (prop) {
|
369 | prop.description = self.markdown(prop.description);
|
370 | });
|
371 | }
|
372 | });
|
373 | }
|
374 | if (member['return']) {
|
375 | member.hasReturn = true;
|
376 | member.returnType = member['return'].type;
|
377 | } else {
|
378 | member.returnType = '';
|
379 | }
|
380 | }
|
381 | if (member.itemtype === 'attribute') {
|
382 | member.emit = self.options.attributesEmit;
|
383 | }
|
384 | return member;
|
385 | },
|
386 |
|
387 | |
388 |
|
389 |
|
390 |
|
391 |
|
392 |
|
393 | buildMembers: function () {
|
394 | _.each(
|
395 | this.ast.members,
|
396 | function (member) {
|
397 | var parent;
|
398 | if (member.clazz) {
|
399 | parent = this.ast.classes[member.clazz];
|
400 | } else if (member.module) {
|
401 | parent = this.ast.modules[member.module];
|
402 | }
|
403 | if (!parent) return;
|
404 | if (!parent.members) {
|
405 | parent.members = [];
|
406 | }
|
407 | var item = this.buildMember(member, false, parent);
|
408 | parent.members.push(item);
|
409 | },
|
410 | this
|
411 | );
|
412 | },
|
413 |
|
414 | |
415 |
|
416 |
|
417 |
|
418 |
|
419 |
|
420 |
|
421 | augmentData: function (o) {
|
422 | var self = this;
|
423 | if (self.options.withSrc) {
|
424 | o = self.addFoundAt(o);
|
425 | }
|
426 | _.each(o, function (i, k1) {
|
427 | if (i && i.forEach) {
|
428 | _.each(i, function (a, k) {
|
429 | if (!(a instanceof Object)) {
|
430 | return;
|
431 | }
|
432 | if (!a.type) {
|
433 | a.type = 'Object';
|
434 | }
|
435 | if (a.final === '') {
|
436 | a.final = true;
|
437 | }
|
438 | if (a.static === '') {
|
439 | a.static = true;
|
440 | }
|
441 | if (!a.description) {
|
442 | a.description = ' ';
|
443 | } else if (!o.extendedFrom) {
|
444 | a.description = self.markdown(a.description);
|
445 | }
|
446 | if (a.example && !o.extendedFrom) {
|
447 | a.example = self.markdown(a.example);
|
448 | }
|
449 | if (self.options.withSrc) {
|
450 | a = self.addFoundAt(a);
|
451 | }
|
452 |
|
453 | _.each(a, function (c, d) {
|
454 | if (c.forEach || (c instanceof Object)) {
|
455 | c = self.augmentData(c);
|
456 | a[d] = c;
|
457 | }
|
458 | });
|
459 | o[k1][k] = a;
|
460 | });
|
461 | } else if (i instanceof Object) {
|
462 | i.foundAt = utils.getFoundAt(i, self.options);
|
463 | _.each(i, function (v, k) {
|
464 | if (k === 'final') {
|
465 | o[k1][k] = true;
|
466 | } else if (k === 'static') {
|
467 | o[k1][k] = true;
|
468 | }
|
469 | else if (k === 'description' || k === 'example') {
|
470 | if (v.forEach || (v instanceof Object)) {
|
471 | o[k1][k] = self.augmentData(v);
|
472 | } else {
|
473 | o[k1][k] = o.extendedFrom ? v : self.markdown(v);
|
474 | }
|
475 | }
|
476 | });
|
477 | } else if (k1 === 'description' || k1 === 'example') {
|
478 | o[k1] = o.extendedFrom ? i : self.markdown(i);
|
479 | }
|
480 | });
|
481 | return o;
|
482 | },
|
483 |
|
484 | |
485 |
|
486 |
|
487 |
|
488 |
|
489 |
|
490 | _mergeCounter: null,
|
491 |
|
492 | |
493 |
|
494 |
|
495 |
|
496 |
|
497 |
|
498 |
|
499 | mergeExtends: function (info, members, first, onmember) {
|
500 | var self = this;
|
501 | self._mergeCounter = (first) ? 0 : (self._mergeCounter + 1);
|
502 |
|
503 | if (self._mergeCounter === 100) {
|
504 | throw new Error('YUIDoc detected a loop extending class ' + info.name);
|
505 | }
|
506 | if (info.extends || info.uses) {
|
507 | var hasItems = {};
|
508 | hasItems[info.extends] = 1;
|
509 | if (info.uses) {
|
510 | info.uses.forEach(function (v) {
|
511 | hasItems[v] = 1;
|
512 | });
|
513 | }
|
514 | self.ast.members.forEach(function (v) {
|
515 | if (hasItems[v.clazz]) {
|
516 | if (!v.static) {
|
517 | var q;
|
518 | var override = _.findWhere(members, {'name': v.name});
|
519 | if (!override) {
|
520 |
|
521 | q = _.extend({}, v);
|
522 | q.extendedFrom = v.clazz;
|
523 | members.push(q);
|
524 | } else {
|
525 |
|
526 | q = _.extend({}, v);
|
527 | q = self.augmentData(q);
|
528 | override.overwrittenFrom = q;
|
529 | }
|
530 | if (typeof onmember === 'function') {
|
531 | onmember(q);
|
532 | }
|
533 | }
|
534 | }
|
535 | });
|
536 | if (self.ast.classes[info.extends]) {
|
537 | if (self.ast.classes[info.extends].extends || self.ast.classes[info.extends].uses) {
|
538 | members = self.mergeExtends(self.ast.classes[info.extends], members);
|
539 | }
|
540 | }
|
541 | }
|
542 | return members;
|
543 | },
|
544 |
|
545 | |
546 |
|
547 |
|
548 |
|
549 |
|
550 |
|
551 |
|
552 | getExpandIterator: function (parent) {
|
553 | var self = this;
|
554 | var pluralsMap = {
|
555 | 'property': 'properties'
|
556 | };
|
557 | return function (item) {
|
558 | if (!item.itemtype) return;
|
559 | var plural = pluralsMap[item.itemtype];
|
560 | if (!plural) {
|
561 | plural = item.itemtype + 's';
|
562 | }
|
563 | if (!parent[plural]) {
|
564 | parent[plural] = [];
|
565 | }
|
566 | parent[plural].push(item);
|
567 | }
|
568 | },
|
569 |
|
570 | |
571 |
|
572 |
|
573 |
|
574 |
|
575 |
|
576 | extendMembers: function (meta) {
|
577 | _.each(
|
578 | meta.classes,
|
579 | function (clazz) {
|
580 | var inherited = [];
|
581 | clazz.members = this.mergeExtends(clazz, clazz.members, true, function (member) {
|
582 | if (member.extendedFrom) inherited.push(member);
|
583 | });
|
584 | clazz.members.inherited = inherited;
|
585 | },
|
586 | this
|
587 | );
|
588 | },
|
589 |
|
590 | |
591 |
|
592 |
|
593 |
|
594 |
|
595 |
|
596 | expandMembersFromModules: function (meta) {
|
597 | _.each(
|
598 | meta.modules,
|
599 | function (mod) {
|
600 | mod.properties = [];
|
601 | mod.attributes = [];
|
602 | mod.methods = [];
|
603 | mod.events = [];
|
604 | mod.members.forEach(
|
605 | this.getExpandIterator(mod.members)
|
606 | );
|
607 | },
|
608 | this
|
609 | );
|
610 | },
|
611 |
|
612 | |
613 |
|
614 |
|
615 |
|
616 |
|
617 |
|
618 | expandMembersFromClasses: function (meta) {
|
619 | _.each(
|
620 | meta.classes,
|
621 | function (clazz) {
|
622 | clazz.members.forEach(
|
623 | this.getExpandIterator(clazz.members)
|
624 | );
|
625 | clazz.members.inherited = clazz.members.inherited || [];
|
626 | clazz.members.inherited.forEach(
|
627 | this.getExpandIterator(clazz.members.inherited)
|
628 | );
|
629 | },
|
630 | this
|
631 | );
|
632 | },
|
633 |
|
634 | |
635 |
|
636 |
|
637 |
|
638 |
|
639 |
|
640 | create: function (context) {
|
641 | this.context = context;
|
642 | this.options = context.options;
|
643 | this.ast = context.ast;
|
644 |
|
645 | debug('Initializing markdown-it rulers');
|
646 | this.initMarkdownRulers();
|
647 |
|
648 | debug('Creating the instance by utils.prepare');
|
649 | var instance = utils.prepare([this.options.theme], this.options);
|
650 |
|
651 | debug('Done preparation, ready for setting classes and modules');
|
652 | instance.meta.classes = this.classes;
|
653 | instance.meta.modules = this.modules;
|
654 |
|
655 |
|
656 | debug('Building members');
|
657 | this.buildMembers();
|
658 |
|
659 |
|
660 | instance.meta.files = this.files;
|
661 | instance.meta.i18n = this.i18n;
|
662 |
|
663 |
|
664 | this.extendMembers(instance.meta);
|
665 | this.expandMembersFromModules(instance.meta);
|
666 | this.expandMembersFromClasses(instance.meta);
|
667 |
|
668 |
|
669 | var locals;
|
670 | var meta = instance.meta;
|
671 | try {
|
672 | if (path.isAbsolute(this.options.theme)) {
|
673 | locals = require(path.join(this.options.theme, '/locals.js'));
|
674 | } else {
|
675 | locals = require(path.join(process.cwd(), this.options.theme, '/locals.js'));
|
676 | }
|
677 | } catch (e) {
|
678 | console.warn('Failed on loading theme script: ' + e);
|
679 | locals = function () {};
|
680 | }
|
681 | locals(meta.modules, meta.classes, instance.meta);
|
682 |
|
683 |
|
684 | instance.meta.globals = instance.meta;
|
685 | return instance;
|
686 | }
|
687 |
|
688 | };
|
689 |
|
690 | exports.Locals = Locals;
|