resolver.js
5.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
/**
* @module jsdoc/tutorial/resolver
*/
'use strict';
var env = require('jsdoc/env');
var fs = require('jsdoc/fs');
var logger = require('jsdoc/util/logger');
var path = require('path');
var stripBom = require('jsdoc/util/stripbom');
var tutorial = require('jsdoc/tutorial');
var hasOwnProp = Object.prototype.hasOwnProperty;
// TODO: make this an instance member of `RootTutorial`?
var conf = {};
var finder = /^(.*)\.(x(?:ht)?ml|html?|md|markdown|json)$/i;
/** checks if `conf` is the metadata for a single tutorial.
* A tutorial's metadata has a property 'title' and/or a property 'children'.
* @param {object} json - the object we want to test (typically from JSON.parse)
* @returns {boolean} whether `json` could be the metadata for a tutorial.
*/
function isTutorialJSON(json) {
// if conf.title exists or conf.children exists, it is metadata for a tutorial
return (hasOwnProp.call(json, 'title') || hasOwnProp.call(json, 'children'));
}
/**
* Root tutorial.
* @type {module:jsdoc/tutorial.Root}
*/
exports.root = new tutorial.RootTutorial();
/**
* Helper function that adds tutorial configuration to the `conf` variable. This helps when multiple
* tutorial configurations are specified in one object, or when a tutorial's children are specified
* as tutorial configurations as opposed to an array of tutorial names.
*
* Recurses as necessary to ensure all tutorials are added.
*
* @param {string} name - if `meta` is a configuration for a single tutorial, this is that
* tutorial's name.
* @param {object} meta - object that contains tutorial information. Can either be for a single
* tutorial, or for multiple (where each key in `meta` is the tutorial name and each value is the
* information for a single tutorial). Additionally, a tutorial's 'children' property may either be
* an array of strings (names of the child tutorials), OR an object giving the configuration for the
* child tutorials.
*/
function addTutorialConf(name, meta) {
var i;
var l;
var names;
if (isTutorialJSON(meta)) {
// if the children are themselves tutorial defintions as opposed to an
// array of strings, add each child.
if (hasOwnProp.call(meta, 'children') && !Array.isArray(meta.children)) {
names = Object.keys(meta.children);
for (i = 0, l = names.length; i < l; ++i) {
addTutorialConf(names[i], meta.children[names[i]]);
}
// replace with an array of names.
meta.children = names;
}
// check if the tutorial has already been defined...
if (hasOwnProp.call(conf, name)) {
logger.warn('Metadata for the tutorial %s is defined more than once. Only the first definition will be used.', name );
} else {
conf[name] = meta;
}
} else {
// keys are tutorial names, values are `Tutorial` instances
names = Object.keys(meta);
for (i = 0, l = names.length; i < l; ++i) {
addTutorialConf(names[i], meta[names[i]]);
}
}
}
/**
* Add a tutorial.
* @param {module:jsdoc/tutorial.Tutorial} current - Tutorial to add.
*/
exports.addTutorial = function(current) {
if (exports.root.getByName(current.name)) {
logger.warn('The tutorial %s is defined more than once. Only the first definition will be used.', current.name);
} else {
// by default, the root tutorial is the parent
current.setParent(exports.root);
exports.root._addTutorial(current);
}
};
/**
* Load tutorials from the given path.
* @param {string} filepath - Tutorials directory.
*/
exports.load = function(filepath) {
var content;
var current;
var files = fs.ls(filepath, env.opts.recurse ? env.conf.recurseDepth : undefined);
var name;
var match;
var type;
// tutorials handling
files.forEach(function(file) {
match = file.match(finder);
// any filetype that can apply to tutorials
if (match) {
name = path.basename(match[1]);
content = fs.readFileSync(file, env.opts.encoding);
switch (match[2].toLowerCase()) {
// HTML type
case 'xml':
case 'xhtml':
case 'html':
case 'htm':
type = tutorial.TYPES.HTML;
break;
// Markdown typs
case 'md':
case 'markdown':
type = tutorial.TYPES.MARKDOWN;
break;
// configuration file
case 'json':
var meta = JSON.parse(stripBom.strip(content));
addTutorialConf(name, meta);
// don't add this as a tutorial
return;
// how can it be? check `finder' regexp
// not a file we want to work with
default:
return;
}
current = new tutorial.Tutorial(name, content, type);
exports.addTutorial(current);
}
});
};
/**
* Resolves hierarchical structure.
*/
exports.resolve = function() {
var item;
var current;
Object.keys(conf).forEach(function(name) {
current = exports.root.getByName(name);
// TODO: should we complain about this?
if (!current) {
return;
}
item = conf[name];
// set title
if (item.title) {
current.title = item.title;
}
// add children
if (item.children) {
item.children.forEach(function(child) {
var childTutorial = exports.root.getByName(child);
if (!childTutorial) {
logger.error('Missing child tutorial: %s', child);
}
else {
childTutorial.setParent(current);
}
});
}
});
};