1 | var _ = require('lodash');
|
2 | var fs = require('fs');
|
3 | var path = require('path');
|
4 | var util = require('util');
|
5 |
|
6 | var findFiles = require('./utils/find_files');
|
7 |
|
8 | var ParameterError = require('./errors/parameter_error');
|
9 | var ParserError = require('./errors/parser_error');
|
10 |
|
11 | var app = {};
|
12 |
|
13 | function Parser(_app) {
|
14 | var self = this;
|
15 |
|
16 |
|
17 | app = _app;
|
18 |
|
19 |
|
20 | self.languages = {};
|
21 | self.parsers = {};
|
22 | self.parsedFileElements = [];
|
23 | self.parsedFiles = [];
|
24 | self.countDeprecated = {};
|
25 |
|
26 |
|
27 | var languages = Object.keys(app.languages);
|
28 | languages.forEach(function(language) {
|
29 | var filename = app.languages[language];
|
30 | app.log.debug('load parser language: ' + language + ', ' + filename);
|
31 | self.addLanguage(language, require(filename));
|
32 | });
|
33 |
|
34 |
|
35 | var parsers = Object.keys(app.parsers);
|
36 | parsers.forEach(function(parser) {
|
37 | var filename = app.parsers[parser];
|
38 | app.log.debug('load parser: ' + parser + ', ' + filename);
|
39 | self.addParser(parser, require(filename));
|
40 | });
|
41 | }
|
42 |
|
43 |
|
44 |
|
45 |
|
46 | util.inherits(Parser, Object);
|
47 |
|
48 |
|
49 |
|
50 |
|
51 | module.exports = Parser;
|
52 |
|
53 |
|
54 |
|
55 |
|
56 | Parser.prototype.addLanguage = function(name, language) {
|
57 | this.languages[name] = language;
|
58 | };
|
59 |
|
60 |
|
61 |
|
62 |
|
63 | Parser.prototype.addParser = function(name, parser) {
|
64 | this.parsers[name] = parser;
|
65 | };
|
66 |
|
67 |
|
68 |
|
69 |
|
70 |
|
71 |
|
72 |
|
73 |
|
74 | Parser.prototype.parseFiles = function(options, parsedFiles, parsedFilenames) {
|
75 | var self = this;
|
76 |
|
77 | findFiles.setPath(options.src);
|
78 | findFiles.setExcludeFilters(options.excludeFilters);
|
79 | findFiles.setIncludeFilters(options.includeFilters);
|
80 | var files = findFiles.search();
|
81 |
|
82 |
|
83 | for (var i = 0; i < files.length; i += 1) {
|
84 | var filename = options.src + files[i];
|
85 | var parsedFile = self.parseFile(filename);
|
86 | if (parsedFile) {
|
87 | app.log.verbose('parse file: ' + filename);
|
88 | parsedFiles.push(parsedFile);
|
89 | parsedFilenames.push(filename);
|
90 | }
|
91 | }
|
92 | };
|
93 |
|
94 |
|
95 |
|
96 |
|
97 | Parser.prototype.parseFile = function(filename) {
|
98 | var self = this;
|
99 |
|
100 | app.log.debug('inspect file: ' + filename);
|
101 |
|
102 | self.filename = filename;
|
103 | self.extension = path.extname(filename).toLowerCase();
|
104 | self.src = fs.readFileSync(filename, 'utf8').toString();
|
105 | app.log.debug('size: ' + self.src.length);
|
106 |
|
107 |
|
108 | self.src = self.src.replace(/\r\n/g, '\n');
|
109 |
|
110 | self.blocks = [];
|
111 | self.indexApiBlocks = [];
|
112 |
|
113 |
|
114 | self.blocks = self._findBlocks();
|
115 | if (self.blocks.length === 0)
|
116 | return;
|
117 |
|
118 | app.log.debug('count blocks: ' + self.blocks.length);
|
119 |
|
120 |
|
121 | self.elements = self.blocks.map(function(block, i) {
|
122 | var elements = self._findElements(block);
|
123 | app.log.debug('count elements in block ' + i + ': ' + elements.length);
|
124 | return elements;
|
125 | });
|
126 | if (self.elements.length === 0)
|
127 | return;
|
128 |
|
129 |
|
130 | self.indexApiBlocks = self._findBlockWithApiGetIndex(self.elements);
|
131 | if (self.indexApiBlocks.length === 0)
|
132 | return;
|
133 |
|
134 | return self._parseBlockElements(self.indexApiBlocks, self.elements, filename);
|
135 | };
|
136 |
|
137 |
|
138 |
|
139 |
|
140 |
|
141 |
|
142 |
|
143 |
|
144 | Parser.prototype._parseBlockElements = function(indexApiBlocks, detectedElements, filename) {
|
145 | var self = this;
|
146 | var parsedBlocks = [];
|
147 |
|
148 | for (var i = 0; i < indexApiBlocks.length; i += 1) {
|
149 | var blockIndex = indexApiBlocks[i];
|
150 | var elements = detectedElements[blockIndex];
|
151 | var blockData = {
|
152 | global: {},
|
153 | local : {}
|
154 | };
|
155 | var countAllowedMultiple = 0;
|
156 |
|
157 | for (var j = 0; j < elements.length; j += 1) {
|
158 | var element = elements[j];
|
159 | var elementParser = self.parsers[element.name];
|
160 |
|
161 | if ( ! elementParser) {
|
162 | app.log.warn('parser plugin \'' + element.name + '\' not found.');
|
163 | break;
|
164 | }
|
165 |
|
166 | app.log.debug('found @' + element.sourceName + ', in block: ' + i);
|
167 |
|
168 |
|
169 | if (elementParser.deprecated) {
|
170 | self.countDeprecated[element.sourceName] = self.countDeprecated[element.sourceName] ? self.countDeprecated[element.sourceName] + 1 : 1;
|
171 |
|
172 | var message = '@' + element.sourceName + ' is deprecated';
|
173 | if (elementParser.alternative)
|
174 | message = '@' + element.sourceName + ' is deprecated, please use ' + elementParser.alternative;
|
175 |
|
176 | if (self.countDeprecated[element.sourceName] === 1)
|
177 |
|
178 | app.log.warn(message);
|
179 | else
|
180 |
|
181 | app.log.verbose(message);
|
182 |
|
183 | app.log.verbose('in file: ' + filename + ', block: ' + blockIndex);
|
184 | }
|
185 |
|
186 | var values;
|
187 | var preventGlobal;
|
188 | var allowMultiple;
|
189 | var pathTo;
|
190 | var attachMethod;
|
191 | try {
|
192 |
|
193 | values = elementParser.parse(element.content, element.source);
|
194 |
|
195 |
|
196 |
|
197 |
|
198 |
|
199 | preventGlobal = elementParser.preventGlobal === true;
|
200 |
|
201 |
|
202 | allowMultiple = elementParser.allowMultiple === true;
|
203 |
|
204 |
|
205 |
|
206 | pathTo = '';
|
207 | if (elementParser.path) {
|
208 | if (typeof elementParser.path === 'string')
|
209 | pathTo = elementParser.path;
|
210 | else
|
211 | pathTo = elementParser.path();
|
212 | }
|
213 |
|
214 | if ( ! pathTo)
|
215 | throw new ParserError('pathTo is not defined in the parser file.', '', '', element.sourceName);
|
216 |
|
217 |
|
218 | attachMethod = elementParser.method || 'push';
|
219 |
|
220 | if (attachMethod !== 'insert' && attachMethod !== 'push')
|
221 | throw new ParserError('Only push or insert are allowed parser method values.', '', '', element.sourceName);
|
222 |
|
223 |
|
224 |
|
225 | if ( values &&
|
226 | app.markdown &&
|
227 | elementParser.markdownFields &&
|
228 | elementParser.markdownFields.length > 0
|
229 | ) {
|
230 | for (var markdownIndex = 0; markdownIndex < elementParser.markdownFields.length; markdownIndex += 1) {
|
231 | var markdownField = elementParser.markdownFields[markdownIndex];
|
232 | if (values[markdownField] && app.markdown) {
|
233 | values[markdownField] = app.markdown(values[markdownField]);
|
234 |
|
235 | values[markdownField] = values[markdownField].replace(/(\r\n|\n|\r)/g, ' ');
|
236 | }
|
237 | }
|
238 | }
|
239 | } catch(e) {
|
240 | if (e instanceof ParameterError) {
|
241 | var extra = [];
|
242 | if (e.definition)
|
243 | extra.push({ 'Definition': e.definition });
|
244 | if (e.example)
|
245 | extra.push({ 'Example': e.example });
|
246 | throw new ParserError(e.message,
|
247 | self.filename, (blockIndex + 1), element.sourceName, element.source, extra);
|
248 | }
|
249 | throw new ParserError('Undefined error.',
|
250 | self.filename, (blockIndex + 1), element.sourceName, element.source);
|
251 | }
|
252 |
|
253 | if ( ! values)
|
254 | throw new ParserError('Empty parser result.',
|
255 | self.filename, (blockIndex + 1), element.sourceName, element.source);
|
256 |
|
257 | if (preventGlobal) {
|
258 |
|
259 |
|
260 | if (Object.keys(blockData.global).length > countAllowedMultiple)
|
261 | throw new ParserError('Only one definition or usage is allowed in the same block.',
|
262 | self.filename, (blockIndex + 1), element.sourceName, element.source);
|
263 | }
|
264 |
|
265 |
|
266 | if (pathTo === 'global' || pathTo.substr(0, 7) === 'global.') {
|
267 | if (allowMultiple) {
|
268 | countAllowedMultiple += 1;
|
269 | } else {
|
270 | if (Object.keys(blockData.global).length > 0)
|
271 | throw new ParserError('Only one definition is allowed in the same block.',
|
272 | self.filename, (blockIndex + 1), element.sourceName, element.source);
|
273 |
|
274 | if (preventGlobal === true)
|
275 | throw new ParserError('Only one definition or usage is allowed in the same block.',
|
276 | self.filename, (blockIndex + 1), element.sourceName, element.source);
|
277 | }
|
278 | }
|
279 |
|
280 | if ( ! blockData[pathTo])
|
281 | self._createObjectPath(blockData, pathTo, attachMethod);
|
282 |
|
283 | var blockDataPath = self._pathToObject(pathTo, blockData);
|
284 |
|
285 |
|
286 | if (attachMethod === 'push')
|
287 | blockDataPath.push(values);
|
288 | else
|
289 | _.extend(blockDataPath, values);
|
290 |
|
291 |
|
292 | if (elementParser.extendRoot === true)
|
293 | _.extend(blockData, values);
|
294 |
|
295 | blockData.index = blockIndex + 1;
|
296 | }
|
297 | parsedBlocks.push(blockData);
|
298 | }
|
299 |
|
300 | return parsedBlocks;
|
301 | };
|
302 |
|
303 |
|
304 |
|
305 |
|
306 |
|
307 |
|
308 |
|
309 |
|
310 |
|
311 | Parser.prototype._createObjectPath = function(src, path, attachMethod) {
|
312 | if ( ! path)
|
313 | return src;
|
314 | var pathParts = path.split('.');
|
315 | var current = src;
|
316 | for (var i = 0; i < pathParts.length; i += 1) {
|
317 | var part = pathParts[i];
|
318 | if ( ! current[part]) {
|
319 | if (i === (pathParts.length - 1) && attachMethod === 'push' )
|
320 | current[part] = [];
|
321 | else
|
322 | current[part] = {};
|
323 | }
|
324 | current = current[part];
|
325 | }
|
326 | return current;
|
327 | };
|
328 |
|
329 |
|
330 |
|
331 |
|
332 |
|
333 | Parser.prototype._pathToObject = function(path, src) {
|
334 | if ( ! path)
|
335 | return src;
|
336 | var pathParts = path.split('.');
|
337 | var current = src;
|
338 | for (var i = 0; i < pathParts.length; i += 1) {
|
339 | var part = pathParts[i];
|
340 | current = current[part];
|
341 | }
|
342 | return current;
|
343 | };
|
344 |
|
345 |
|
346 |
|
347 |
|
348 | Parser.prototype._findBlocks = function() {
|
349 | var self = this;
|
350 | var blocks = [];
|
351 | var src = self.src;
|
352 |
|
353 |
|
354 | src = src.replace(/\n/g, '\uffff');
|
355 |
|
356 | var regexForFile = this.languages[self.extension] || this.languages['default'];
|
357 | var matches = regexForFile.docBlocksRegExp.exec(src);
|
358 | while (matches) {
|
359 | var block = matches[2] || matches[1];
|
360 |
|
361 |
|
362 | block = block.replace(/\uffff/g, '\n');
|
363 |
|
364 | block = block.replace(regexForFile.inlineRegExp, '');
|
365 | blocks.push(block);
|
366 |
|
367 |
|
368 | matches = regexForFile.docBlocksRegExp.exec(src);
|
369 | }
|
370 | return blocks;
|
371 | };
|
372 |
|
373 |
|
374 |
|
375 |
|
376 |
|
377 |
|
378 |
|
379 | Parser.prototype._findBlockWithApiGetIndex = function(blocks) {
|
380 | var foundIndexes = [];
|
381 | for (var i = 0; i < blocks.length; i += 1) {
|
382 | var found = false;
|
383 | for (var j = 0; j < blocks[i].length; j += 1) {
|
384 | if (blocks[i][j].name.substr(0, 9) === 'apiignore') {
|
385 | app.log.debug('apiIgnore found in block: ' + i);
|
386 | found = false;
|
387 | break;
|
388 | }
|
389 |
|
390 | if (blocks[i][j].name.substr(0, 3) === 'api')
|
391 | found = true;
|
392 | }
|
393 | if (found) {
|
394 | foundIndexes.push(i);
|
395 | app.log.debug('api found in block: ' + i);
|
396 | }
|
397 | }
|
398 | return foundIndexes;
|
399 | };
|
400 |
|
401 |
|
402 |
|
403 |
|
404 | Parser.prototype._findElements = function(block) {
|
405 | var elements = [];
|
406 |
|
407 |
|
408 | block = block.replace(/\n/g, '\uffff');
|
409 |
|
410 |
|
411 | var elementsRegExp = /(@(\w*)\s?(.+?)(?=\uffff[\s\*]*@|$))/gm;
|
412 | var matches = elementsRegExp.exec(block);
|
413 | while (matches) {
|
414 | var element = {
|
415 | source : matches[1],
|
416 | name : matches[2].toLowerCase(),
|
417 | sourceName: matches[2],
|
418 | content : matches[3]
|
419 | };
|
420 |
|
421 |
|
422 | element.content = element.content.replace(/\uffff/g, '\n');
|
423 | element.source = element.source.replace(/\uffff/g, '\n');
|
424 |
|
425 | elements.push(element);
|
426 |
|
427 |
|
428 | matches = elementsRegExp.exec(block);
|
429 | }
|
430 | return elements;
|
431 | };
|