comment-parser 0.3.1 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/parser.js CHANGED
@@ -1,131 +1,43 @@
1
1
 
2
- var RE_COMMENT_START = /^\s*\/\*\*\s*$/m;
3
- var RE_COMMENT_LINE = /^\s*\*(?:\s|$)/m;
4
- var RE_COMMENT_END = /^\s*\*\/\s*$/m;
5
- var RE_COMMENT_1LINE = /^\s*\/\*\*\s*(.*)\s*\*\/\s*$/;
2
+ var PARSERS = require('./parsers')
3
+
4
+ var MARKER_START = '/**'
5
+ var MARKER_START_SKIP = '/***'
6
+ var MARKER_END = '*/'
6
7
 
7
8
  /* ------- util functions ------- */
8
9
 
9
- function merge(/* ...objects */) {
10
- var k, obj, res = {}, objs = Array.prototype.slice.call(arguments);
11
- while (objs.length) {
12
- obj = objs.shift();
13
- for (k in obj) { if (obj.hasOwnProperty(k)) {
14
- res[k] = obj[k];
15
- }}
16
- }
17
- return res;
18
- }
10
+ function merge (/* ...objects */) {
11
+ var k, obj
12
+ var res = {}
13
+ var objs = Array.prototype.slice.call(arguments)
19
14
 
20
- function find(list, filter) {
21
- var k, i = list.length, matchs = true;
22
- while (i--) {
23
- for (k in filter) { if (filter.hasOwnProperty(k)) {
24
- matchs = (filter[k] === list[i][k]) && matchs;
25
- }}
26
- if (matchs) { return list[i]; }
15
+ for (var i = 0, l = objs.length; i < l; i++) {
16
+ obj = objs[i]
17
+ for (k in obj) {
18
+ if (obj.hasOwnProperty(k)) {
19
+ res[k] = obj[k]
20
+ }
21
+ }
27
22
  }
28
- return null;
29
- }
30
-
31
- function skipws(str) {
32
- var i = 0;
33
- do {
34
- if (str[i] !== ' ') { return i; }
35
- } while (++i < str.length);
36
- return i;
23
+ return res
37
24
  }
38
25
 
39
- /* ------- default parsers ------- */
40
-
41
- var PARSERS = {};
42
-
43
- PARSERS.parse_tag = function parse_tag(str) {
44
- var result = str.match(/^\s*@(\S+)/);
45
-
46
- if (!result) { throw new Error('Invalid `@tag`, missing @ symbol'); }
47
-
48
- return {
49
- source : result[0],
50
- data : {tag: result[1]}
51
- };
52
- };
53
-
54
- PARSERS.parse_type = function parse_type(str, data) {
55
- if (data.errors && data.errors.length) { return null; }
56
-
57
- var pos = skipws(str);
58
- var res = '';
59
- var curlies = 0;
26
+ function find (list, filter) {
27
+ var k
28
+ var i = list.length
29
+ var matchs = true
60
30
 
61
- if (str[pos] !== '{') { return null; }
62
-
63
- while (pos < str.length) {
64
- curlies += (str[pos] === '{' ? 1 : (str[pos] === '}' ? -1 : 0));
65
- res += str[pos];
66
- pos ++;
67
- if (curlies === 0) { break; }
68
- }
69
-
70
- if (curlies !== 0) { throw new Error('Invalid `{type}`, unpaired curlies'); }
71
-
72
- return {
73
- source : str.slice(0, pos),
74
- data : {type: res.slice(1, -1)}
75
- };
76
- };
77
-
78
- PARSERS.parse_name = function parse_name(str, data) {
79
- if (data.errors && data.errors.length) { return null; }
80
-
81
- var pos = skipws(str);
82
- var name = '';
83
- var brackets = 0;
84
-
85
- while (pos < str.length) {
86
- brackets += (str[pos] === '[' ? 1 : (str[pos] === ']' ? -1 : 0));
87
- name += str[pos];
88
- pos ++;
89
- if (brackets === 0 && /\s/.test(str[pos])) { break; }
90
- }
91
-
92
- if (brackets !== 0) { throw new Error('Invalid `name`, unpaired brackets'); }
93
-
94
- var res = {name: name, optional: false};
95
-
96
- if (name[0] === '[' && name[name.length - 1] === ']') {
97
- res.optional = true;
98
- name = name.slice(1, -1);
99
-
100
- if (name.indexOf('=') !== -1) {
101
- var parts = name.split('=');
102
- name = parts[0];
103
- res.default = parts[1].replace(/^(["'])(.+)(\1)$/, '$2');
31
+ while (i--) {
32
+ for (k in filter) {
33
+ if (filter.hasOwnProperty(k)) {
34
+ matchs = (filter[k] === list[i][k]) && matchs
35
+ }
104
36
  }
37
+ if (matchs) { return list[i] }
105
38
  }
106
-
107
- res.name = name;
108
-
109
- return {
110
- source : str.slice(0, pos),
111
- data : res
112
- };
113
- };
114
-
115
- PARSERS.parse_description = function parse_description(str, data) {
116
- if (data.errors && data.errors.length) { return null; }
117
-
118
- var result = str.match(/^\s+([^$]+)?/);
119
-
120
- if (result) {
121
- return {
122
- source : result[0],
123
- data : {description: result[1] === undefined ? '' : result[1]}
124
- };
125
- }
126
-
127
- return null;
128
- };
39
+ return null
40
+ }
129
41
 
130
42
  /* ------- parsing ------- */
131
43
 
@@ -135,219 +47,222 @@ PARSERS.parse_description = function parse_description(str, data) {
135
47
  * @param {Array[function]} parsers Array of parsers to be applied to the source
136
48
  * @returns {object} parsed tag node
137
49
  */
138
- function parse_tag(str, parsers) {
139
- if (typeof str !== 'string' || str[0] !== '@') { return null; }
50
+ function parse_tag (str, parsers) {
51
+ if (typeof str !== 'string' || str[0] !== '@') { return null }
140
52
 
141
- var data = parsers.reduce(function(state, parser) {
142
- var result;
53
+ var data = parsers.reduce(function (state, parser) {
54
+ var result
143
55
 
144
56
  try {
145
- result = parser(state.source, merge({}, state.data));
146
- // console.log('----------------');
147
- // console.log(parser.name, ':', result);
57
+ result = parser(state.source, merge({}, state.data))
148
58
  } catch (err) {
149
- // console.warn('Parser "%s" failed: %s', parser.name, err.message);
150
59
  state.data.errors = (state.data.errors || [])
151
- .concat(parser.name + ': ' + err.message);
60
+ .concat(parser.name + ': ' + err.message)
152
61
  }
153
62
 
154
63
  if (result) {
155
- state.source = state.source.slice(result.source.length);
156
- state.data = merge(state.data, result.data);
64
+ state.source = state.source.slice(result.source.length)
65
+ state.data = merge(state.data, result.data)
157
66
  }
158
67
 
159
- return state;
68
+ return state
160
69
  }, {
161
- source : str,
162
- data : {}
163
- }).data;
70
+ source: str,
71
+ data: {}
72
+ }).data
164
73
 
165
- data.optional = !!data.optional;
166
- data.type = data.type === undefined ? '' : data.type;
167
- data.name = data.name === undefined ? '' : data.name;
168
- data.description = data.description === undefined ? '' : data.description;
74
+ data.optional = !!data.optional
75
+ data.type = data.type === undefined ? '' : data.type
76
+ data.name = data.name === undefined ? '' : data.name
77
+ data.description = data.description === undefined ? '' : data.description
169
78
 
170
- return data;
79
+ return data
171
80
  }
172
81
 
173
82
  /**
174
83
  * Parses comment block (array of String lines)
175
84
  */
176
- function parse_block(source, opts) {
85
+ function parse_block (source, opts) {
86
+ var trim = opts.trim
87
+ ? function trim (s) { return s.trim() }
88
+ : function trim (s) { return s }
177
89
 
178
90
  var source_str = source
179
- .map(function(line) { return line.source; })
180
- .join('\n')
181
- .trim();
91
+ .map(function (line) { return trim(line.source) })
92
+ .join('\n')
93
+
94
+ source_str = trim(source_str)
182
95
 
183
- var start = source[0].number;
96
+ var start = source[0].number
184
97
 
185
98
  // merge source lines into tags
186
99
  // we assume tag starts with "@"
187
100
  source = source
188
- .reduce(function(tags, line) {
189
- line.source = line.source.trim();
101
+ .reduce(function (tags, line) {
102
+ line.source = trim(line.source)
190
103
 
191
- if (line.source.match(/^@(\w+)/)) {
192
- tags.push({source: [line.source], line: line.number});
104
+ if (line.source.match(/^\s*@(\w+)/)) {
105
+ tags.push({source: [line.source], line: line.number})
193
106
  } else {
194
- var tag = tags[tags.length - 1];
195
- tag.source.push(line.source);
107
+ var tag = tags[tags.length - 1]
108
+ tag.source.push(line.source)
196
109
  }
197
110
 
198
- return tags;
111
+ return tags
199
112
  }, [{source: []}])
200
- .map(function(tag) {
201
- tag.source = tag.source.join('\n').trim();
202
- return tag;
203
- });
113
+ .map(function (tag) {
114
+ tag.source = trim(tag.source.join('\n'))
115
+ return tag
116
+ })
204
117
 
205
118
  // Block description
206
- var description = source.shift();
119
+ var description = source.shift()
207
120
 
208
121
  // skip if no descriptions and no tags
209
- if (description.source === '' && source.length === 0) {
210
- return null;
122
+ if (description.source === '' && source.length === 0) {
123
+ return null
211
124
  }
212
125
 
213
- var tags = source.reduce(function(tags, tag) {
214
- var tag_node = parse_tag(tag.source, opts.parsers || [
215
- PARSERS.parse_tag,
216
- PARSERS.parse_type,
217
- PARSERS.parse_name,
218
- PARSERS.parse_description
219
- ]);
126
+ var tags = source.reduce(function (tags, tag) {
127
+ var tag_node = parse_tag(tag.source, opts.parsers)
220
128
 
221
- if (!tag_node) { return tags; }
129
+ if (!tag_node) { return tags }
222
130
 
223
- tag_node.line = tag.line;
224
- tag_node.source = tag.source;
131
+ tag_node.line = tag.line
132
+ tag_node.source = tag.source
225
133
 
226
134
  if (opts.dotted_names && tag_node.name.indexOf('.') !== -1) {
227
- var parent_name;
228
- var parent_tag;
229
- var parent_tags = tags;
230
- var parts = tag_node.name.split('.');
135
+ var parent_name
136
+ var parent_tag
137
+ var parent_tags = tags
138
+ var parts = tag_node.name.split('.')
231
139
 
232
140
  while (parts.length > 1) {
233
- parent_name = parts.shift();
234
- parent_tag = find(parent_tags, {
235
- tag : tag_node.tag,
236
- name : parent_name
237
- });
141
+ parent_name = parts.shift()
142
+ parent_tag = find(parent_tags, {
143
+ tag: tag_node.tag,
144
+ name: parent_name
145
+ })
238
146
 
239
147
  if (!parent_tag) {
240
148
  parent_tag = {
241
- tag : tag_node.tag,
242
- line : Number(tag_node.line),
243
- name : parent_name,
244
- type : '',
245
- description : ''
246
- };
247
- parent_tags.push(parent_tag);
149
+ tag: tag_node.tag,
150
+ line: Number(tag_node.line),
151
+ name: parent_name,
152
+ type: '',
153
+ description: ''
154
+ }
155
+ parent_tags.push(parent_tag)
248
156
  }
249
157
 
250
- parent_tag.tags = parent_tag.tags || [];
251
- parent_tags = parent_tag.tags;
158
+ parent_tag.tags = parent_tag.tags || []
159
+ parent_tags = parent_tag.tags
252
160
  }
253
161
 
254
- tag_node.name = parts[0];
255
- parent_tags.push(tag_node);
256
- return tags;
162
+ tag_node.name = parts[0]
163
+ parent_tags.push(tag_node)
164
+ return tags
257
165
  }
258
166
 
259
- return tags.concat(tag_node);
260
- }, []);
261
-
262
- // console.log('-----------');
263
- // console.log(description, tags);
167
+ return tags.concat(tag_node)
168
+ }, [])
264
169
 
265
170
  return {
266
- tags : tags,
267
- line : start,
268
- description : description.source,
269
- source : source_str
270
- };
171
+ tags: tags,
172
+ line: start,
173
+ description: description.source,
174
+ source: source_str
175
+ }
271
176
  }
272
177
 
273
178
  /**
274
179
  * Produces `extract` function with internal state initialized
275
180
  */
276
- function mkextract(opts) {
277
-
278
- var chunk = null;
279
- var number = 0;
181
+ function mkextract (opts) {
182
+ var chunk = null
183
+ var indent = 0
184
+ var number = 0
185
+
186
+ opts = merge({}, {
187
+ trim: true,
188
+ dotted_names: false,
189
+ parsers: [
190
+ PARSERS.parse_tag,
191
+ PARSERS.parse_type,
192
+ PARSERS.parse_name,
193
+ PARSERS.parse_description
194
+ ]
195
+ }, opts || {})
280
196
 
281
197
  /**
282
- * Cumulatively reading lines until they make one comment block
283
- * Returns block object or null.
198
+ * Read lines until they make a block
199
+ * Return parsed block once fullfilled or null otherwise
284
200
  */
285
- return function extract(line) {
286
-
287
- // if oneliner
288
- // then parse it immediately
289
- if (line.match(RE_COMMENT_1LINE)) {
290
- // console.log('line (1)', line);
291
- // console.log(' clean:', line.replace(RE_COMMENT_1LINE, '$1'));
292
- return parse_block([{
293
- source: line.replace(RE_COMMENT_1LINE, '$1'),
294
- number: number}], opts);
295
- }
296
-
297
- number += 1;
298
-
299
- // if start of comment
300
- // then init the chunk
301
- if (line.match(RE_COMMENT_START)) {
302
- // console.log('line (1)', line);
303
- // console.log(' clean:', line.replace(RE_COMMENT_START, ''));
304
- chunk = [{source: line.replace(RE_COMMENT_START, ''), number: number - 1}];
305
- return null;
201
+ return function extract (line) {
202
+ var result = null
203
+ var startPos = line.indexOf(MARKER_START)
204
+ var endPos = line.indexOf(MARKER_END)
205
+
206
+ // if open marker detected and it's not skip one
207
+ if (startPos !== -1 && line.indexOf(MARKER_START_SKIP) !== startPos) {
208
+ chunk = []
209
+ indent = startPos + MARKER_START.length
306
210
  }
307
211
 
308
- // if comment line and chunk started
309
- // then append
310
- if (chunk && line.match(RE_COMMENT_LINE)) {
311
- // console.log('line (2)', line);
312
- // console.log(' clean:', line.replace(RE_COMMENT_LINE, ''));
313
- chunk.push({source: line.replace(RE_COMMENT_LINE, ''), number: number - 1});
314
- return null;
315
- }
212
+ // if we are on middle of comment block
213
+ if (chunk) {
214
+ var lineStart = indent
215
+
216
+ // figure out if we slice from opening marker pos
217
+ // or line start is shifted to the left
218
+ var nonSpaceChar = line.match(/\S/)
219
+
220
+ // skip for the first line starting with /** (fresh chunk)
221
+ // it always has the right indentation
222
+ if (chunk.length > 0 && nonSpaceChar) {
223
+ if (nonSpaceChar[0] === '*') {
224
+ lineStart = nonSpaceChar.index + 2
225
+ } else if (nonSpaceChar.index < indent) {
226
+ lineStart = nonSpaceChar.index
227
+ }
228
+ }
316
229
 
317
- // if comment end and chunk started
318
- // then parse the chunk and push
319
- if (chunk && line.match(RE_COMMENT_END)) {
320
- // console.log('line (3)', line);
321
- // console.log(' clean:', line.replace(RE_COMMENT_END, ''));
322
- chunk.push({source: line.replace(RE_COMMENT_END, ''), number: number - 1});
323
- return parse_block(chunk, opts);
230
+ // slice the line until end or until closing marker start
231
+ chunk.push({
232
+ number: number,
233
+ source: line.slice(lineStart, endPos === -1 ? line.length : endPos)
234
+ })
235
+
236
+ // finalize block if end marker detected
237
+ if (endPos !== -1) {
238
+ result = parse_block(chunk, opts)
239
+ chunk = null
240
+ indent = 0
241
+ }
324
242
  }
325
243
 
326
- // if non-comment line
327
- // then reset the chunk
328
- chunk = null;
329
- };
244
+ number += 1
245
+ return result
246
+ }
330
247
  }
331
248
 
332
249
  /* ------- Public API ------- */
333
250
 
334
- module.exports = function parse(source, opts) {
335
- opts = opts || {};
336
-
337
- var block;
338
- var blocks = [];
339
- var extract = mkextract(opts);
340
- var lines = source.split(/\n/);
251
+ module.exports = function parse (source, opts) {
252
+ var block
253
+ var blocks = []
254
+ var extract = mkextract(opts)
255
+ var lines = source.split(/\n/)
341
256
 
342
- while (lines.length) {
343
- block = extract(lines.shift());
257
+ for (var i = 0, l = lines.length; i < l; i++) {
258
+ block = extract(lines.shift())
344
259
  if (block) {
345
- blocks.push(block);
260
+ blocks.push(block)
346
261
  }
347
262
  }
348
263
 
349
- return blocks;
350
- };
264
+ return blocks
265
+ }
351
266
 
352
- module.exports.PARSERS = PARSERS;
353
- module.exports.mkextract = mkextract;
267
+ module.exports.PARSERS = PARSERS
268
+ module.exports.mkextract = mkextract
package/parsers.js ADDED
@@ -0,0 +1,100 @@
1
+ function skipws (str) {
2
+ var i = 0
3
+ do {
4
+ if (str[i] !== ' ') { return i }
5
+ } while (++i < str.length)
6
+ return i
7
+ }
8
+
9
+ /* ------- default parsers ------- */
10
+
11
+ var PARSERS = {}
12
+
13
+ PARSERS.parse_tag = function parse_tag (str) {
14
+ var result = str.match(/^\s*@(\S+)/)
15
+
16
+ if (!result) { throw new Error('Invalid `@tag`, missing @ symbol') }
17
+
18
+ return {
19
+ source: result[0],
20
+ data: {tag: result[1]}
21
+ }
22
+ }
23
+
24
+ PARSERS.parse_type = function parse_type (str, data) {
25
+ if (data.errors && data.errors.length) { return null }
26
+
27
+ var pos = skipws(str)
28
+ var res = ''
29
+ var curlies = 0
30
+
31
+ if (str[pos] !== '{') { return null }
32
+
33
+ while (pos < str.length) {
34
+ curlies += (str[pos] === '{' ? 1 : (str[pos] === '}' ? -1 : 0))
35
+ res += str[pos]
36
+ pos++
37
+ if (curlies === 0) { break }
38
+ }
39
+
40
+ if (curlies !== 0) { throw new Error('Invalid `{type}`, unpaired curlies') }
41
+
42
+ return {
43
+ source: str.slice(0, pos),
44
+ data: {type: res.slice(1, -1)}
45
+ }
46
+ }
47
+
48
+ PARSERS.parse_name = function parse_name (str, data) {
49
+ if (data.errors && data.errors.length) { return null }
50
+
51
+ var pos = skipws(str)
52
+ var name = ''
53
+ var brackets = 0
54
+
55
+ while (pos < str.length) {
56
+ brackets += (str[pos] === '[' ? 1 : (str[pos] === ']' ? -1 : 0))
57
+ name += str[pos]
58
+ pos++
59
+ if (brackets === 0 && /\s/.test(str[pos])) { break }
60
+ }
61
+
62
+ if (brackets !== 0) { throw new Error('Invalid `name`, unpaired brackets') }
63
+
64
+ var res = {name: name, optional: false}
65
+
66
+ if (name[0] === '[' && name[name.length - 1] === ']') {
67
+ res.optional = true
68
+ name = name.slice(1, -1)
69
+
70
+ if (name.indexOf('=') !== -1) {
71
+ var parts = name.split('=')
72
+ name = parts[0]
73
+ res.default = parts[1].replace(/^(["'])(.+)(\1)$/, '$2')
74
+ }
75
+ }
76
+
77
+ res.name = name
78
+
79
+ return {
80
+ source: str.slice(0, pos),
81
+ data: res
82
+ }
83
+ }
84
+
85
+ PARSERS.parse_description = function parse_description (str, data) {
86
+ if (data.errors && data.errors.length) { return null }
87
+
88
+ var result = str.match(/^\s+((.|\s)+)?/)
89
+
90
+ if (result) {
91
+ return {
92
+ source: result[0],
93
+ data: {description: result[1] === undefined ? '' : result[1]}
94
+ }
95
+ }
96
+
97
+ return null
98
+ }
99
+
100
+ module.exports = PARSERS