comment-parser 0.2.4 → 0.4.0

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/.jshintrc ADDED
@@ -0,0 +1,11 @@
1
+ {
2
+ "node": true,
3
+ "strict": true,
4
+ "maxlen": 100,
5
+ "undef": true,
6
+ "unused": true,
7
+ "onecase": true,
8
+ "lastsemic": true,
9
+ "latedef" : true,
10
+ "indent": 2
11
+ }
package/.travis.yml ADDED
@@ -0,0 +1,8 @@
1
+ language: node_js
2
+ node_js:
3
+ - "4.1"
4
+ - "4.0"
5
+ - "0.12"
6
+ - "0.11"
7
+ - "0.10"
8
+ - "iojs"
package/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ # v0.4.0
2
+ - allow to preserve exact source and line numbers with `opts.trim = false`
3
+
4
+ # v0.3.1
5
+ - use `readable-stream` fro Node 0.8 comatibility
6
+ - allow to pass optional parameters to `parse.file(path [,opts], done)`
7
+ - allow `parse.stream` to work with Buffers in addition to strings
8
+
9
+ # v0.3.0
10
+ - `feature` allow to use custom parsers
11
+ - `feature` always include source, no `raw_value` option needed
12
+ - `bugfix` always provide `optional` tag property
13
+ - `refactor` clean up tests
14
+
1
15
  # v0.2.3
2
16
 
3
17
  - `bugfix` Accept `/** one line */` comments
@@ -28,4 +42,4 @@
28
42
 
29
43
  # v0.1.0
30
44
 
31
- Initial implementation
45
+ Initial implementation
package/README.md CHANGED
@@ -1,11 +1,11 @@
1
- comment-parser
2
- ==============
1
+ # comment-parser
2
+
3
3
 
4
4
  Generic JSDoc-like comment parser. This library is not intended to be documentation generator, but rather composite unit for it.
5
5
 
6
6
  `npm install comment-parser`
7
7
 
8
- Module provides `parse(s:String[, opts:Object]):Object` function which takes `/** ... */` comment string and returns array of objects with parsed data.
8
+ Module provides `parse(s:String[, options:Object]):Object` function which takes `/** ... */` comment string and returns array of objects with parsed data.
9
9
 
10
10
  It is not trying to detect relations between tags or somehow recognize their meaning. Any tag can be used, as long as it satisfies the format.
11
11
 
@@ -25,50 +25,133 @@ this would be parsed into following
25
25
 
26
26
  ```javascript
27
27
  [{
28
- tags: [{
29
- tag: "some-tag",
30
- type: "Type",
31
- name: "name",
32
- line: 15,
33
- description: "Singleline or multiline description text",
34
- tags: [{
35
- tag: "some-tag",
36
- type: "Type",
37
- name: "subname",
38
- line: 16,
39
- description: "Singleline or multiline description text",
40
- tags: [{
41
- tag: "some-tag",
42
- type: "Type",
43
- name: "subsubname",
44
- line: 17,
45
- description: "Singleline or\nmultiline description text"
46
- }]
47
- }]
48
- }, {
49
- tag: "another-tag",
50
- type: "",
51
- name: "",
52
- line: 18,
53
- description: ""
54
- }],
55
- description: "Singleline or multiline description text. Line breaks are preserved."
28
+ "tags": [{
29
+ "tag": "some-tag",
30
+ "type": "Type",
31
+ "name": "name",
32
+ "optional": false,
33
+ "description": "Singleline or multiline description text",
34
+ "line": 3,
35
+ "source": "@some-tag {Type} name Singleline or multiline description text"
36
+ }, {
37
+ "tag": "some-tag",
38
+ "type": "Type",
39
+ "name": "name.subname",
40
+ "optional": false,
41
+ "description": "Singleline or multiline description text",
42
+ "line": 4,
43
+ "source": "@some-tag {Type} name.subname Singleline or multiline description text"
44
+ }, {
45
+ "tag": "some-tag",
46
+ "type": "Type",
47
+ "name": "name.subname.subsubname",
48
+ "optional": false,
49
+ "description": "Singleline or\nmultiline description text",
50
+ "line": 5,
51
+ "source": "@some-tag {Type} name.subname.subsubname Singleline or\nmultiline description text"
52
+ }, {
53
+ "tag": "another-tag",
54
+ "name": "",
55
+ "optional": false,
56
+ "type": "",
57
+ "description": "",
58
+ "line": 7,
59
+ "source": "@another-tag"
60
+ }],
61
+ "line": 0,
62
+ "description": "Singleline or multiline description text. Line breaks are preserved.",
63
+ "source": "Singleline or multiline description text. Line breaks are preserved.\n\n@some-tag {Type} name Singleline or multiline description text\n@some-tag {Type} name.subname Singleline or multiline description text\n@some-tag {Type} name.subname.subsubname Singleline or\nmultiline description text\n@another-tag"
56
64
  }]
57
65
  ```
58
66
 
59
- By default dotted names like `name.subname.subsubname` will be expanded into nested sections, this can be prevented by passing `opts.dotted_names = false`.
60
-
61
- You can also make raw line available in parsed results by passing `opts.raw_value = true`.
62
-
63
67
  Invalid comment blocks are skipped. Comments starting with `/*` and `/***` are considered not valid.
64
68
 
65
69
  Also you can parse entire file with `parse.file('path/to/file', callback)` or acquire an instance of [Transform](http://nodejs.org/api/stream.html#stream_class_stream_transform) stream with `parse.stream()`.
66
70
 
67
- Happy coding :)
68
71
 
72
+ ### Options
73
+
74
+ #### dotted_names `true`
75
+
76
+ Tells parser to expand dotted names like `name.subname.subsubname` into nested sections
77
+
78
+ #### trim `true`
79
+
80
+ Makes parser to trim white spaces, set it to `false` to preserve exact source and line numbers
81
+
82
+ #### parsers, `[PARSERS.{parse_tag, parse_type, parse_name, parse_description}]`
83
+
84
+ Array of alternative parsers (read below).
85
+
86
+
87
+ ## Custom parsers
88
+
89
+
90
+ In case you need to parse tags in different way you can pass `opts.parsers = [parser1, ..., parserN]`, where each parser is `function name(str:String, data:Object):{source:String, data:Object}`.
91
+
92
+ Each parser function takes string left after previous parsers applied and data produced by them. And returns `null` or `{source: '', data:{}}` where `source` is consumed substring and `data` is a payload with tag node fields.
93
+
94
+ Tag node data is build by merging result bits from all parsers. Here is some example that is not doing actual parsing but is demonstrating the flow:
95
+
96
+ ```
97
+ /**
98
+ * Source to be parsed below
99
+ * @tag {type} name Description
100
+ */
101
+ parse(source, {parsers: [
102
+ // takes entire string
103
+ function parse_tag(str, data) {
104
+ return {source: ' @tag', data: {tag: 'tag'}};
105
+ },
106
+ // parser throwing exception
107
+ function check_tag(str, data) {
108
+ if (allowed_tags.indexOf(data.tag) === -1) {
109
+ throw new Error('Unrecognized tag "' + data.tag + '"');
110
+ }
111
+ },
112
+ // takes the rest of the string after ' @tag''
113
+ function parse_name1(str, data) {
114
+ return {source: ' name', data: {name: 'name1'}};
115
+ },
116
+ // alternative name parser
117
+ function parse_name2(str, data) {
118
+ return {source: ' name', data: {name: 'name2'}};
119
+ }
120
+ ]});
121
+ ```
122
+
123
+ This would produce following:
124
+
125
+ ```
126
+ [{
127
+ "tags": [{
128
+ "tag": "tag",
129
+ "errors": [
130
+ "check_tag: Unrecognized tag \\"tag\\""
131
+ ],
132
+ "name": "name2",
133
+ "optional": false,
134
+ "type": "",
135
+ "description": "",
136
+ "line": 2,
137
+ "source": "@tag {type} name Description"
138
+ }],
139
+ "line": 0,
140
+ "description": "Source to be parsed below",
141
+ "source": "Source to be parsed below\n@tag {type} name Description"
142
+ }]
143
+ ```
144
+
145
+ ## Packaging
146
+
147
+ `comment-parser` is CommonJS module and was primarely designed to be used with Node. Module `index.js` includes stream and file functionality. Use prser-only module in browser `comment-parser/parse.js`
148
+
149
+ ## Contributors
69
150
 
70
- Contributors
71
- ============
72
151
 
73
152
  - [Sergii Iavorskyi](https://github.com/yavorskiy)
74
- - [Alexej Yaroshevich](https://github.com/zxqfox)
153
+ - [Alexej Yaroshevich](https://github.com/zxqfox)
154
+ - [Jordan Harband](https://github.com/ljharb)
155
+
156
+
157
+ Happy coding :)
package/index.js CHANGED
@@ -1,250 +1,20 @@
1
1
 
2
+ 'use strict';
3
+
2
4
  var fs = require('fs');
3
- var stream = require('stream');
5
+ var stream = require('readable-stream');
4
6
  var util = require('util');
5
7
 
6
- var RE_COMMENT_START = /^\s*\/\*\*\s*$/m;
7
- var RE_COMMENT_LINE = /^\s*\*(?:\s|$)/m;
8
- var RE_COMMENT_END = /^\s*\*\/\s*$/m;
9
- var RE_COMMENT_1LINE = /^\s*\/\*\*\s*(.*)\s*\*\/\s*$/;
10
- var RE_SPACE = /\s/;
11
-
12
- /**
13
- * analogue of str.match(/@(\S+)(?:\s+\{([^\}]+)\})?(?:\s+(\S+))?(?:\s+([^$]+))?/);
14
- * @param {string} str raw jsdoc string
15
- * @returns {object} parsed tag node
16
- */
17
- function parse_tag_line(str) {
18
- if (typeof str !== 'string') { return false; }
19
-
20
- if (str[0] !== '@') { return false; }
21
-
22
- var pos = 1;
23
- var l = str.length;
24
- var error = null;
25
- var new_line = false;
26
- var res = {
27
- tag : _tag(),
28
- type : !new_line && _type() || '',
29
- name : !new_line && _name() || '',
30
- description : _rest() || ''
31
- };
32
-
33
- if (error) {
34
- res.error = error;
35
- }
36
-
37
- return res;
38
-
39
- function _skipws() {
40
- var prev_pos = pos;
41
- while (pos < l && RE_SPACE.test(str[pos])) {
42
- new_line = new_line || str[pos] === '\n';
43
- pos++;
44
- }
45
- }
46
- function _tag() { // @(\S+)
47
- var sp = str.search(RE_SPACE, pos);
48
- sp = sp < 0 ? l : sp;
49
- var res = str.substr(pos, sp - pos);
50
- pos = sp;
51
- return res;
52
- }
53
- function _type() { // (?:\s+\{([^\}]+)\})?
54
- _skipws();
55
- if (str[pos] !== '{') { return ''; }
56
- var ch;
57
- var res = '';
58
- var curlies = 0;
59
- while (pos < l) {
60
- ch = str[pos];
61
- curlies += ch === '{' ? 1 : ch === '}' ? -1 : 0;
62
- res += ch;
63
- pos ++;
64
- if (!curlies) {
65
- break;
66
- }
67
- }
68
- if (curlies !== 0) {
69
- // throw new Error('Unpaired curly in type doc');
70
- error = 'Unpaired curly in type doc';
71
- pos -= res.length;
72
- return '';
73
- }
74
- return res.substr(1, res.length - 2);
75
- }
76
- function _name() { // (?:\s+(\S+))?
77
- if (error) { return ''; }
78
- _skipws();
79
- var ch;
80
- var res = '';
81
- var brackets = 0;
82
- var re = /\s/;
83
- while (pos < l) {
84
- ch = str[pos];
85
- brackets += ch === '[' ? 1 : ch === ']' ? -1 : 0;
86
- res += ch;
87
- pos ++;
88
- if (brackets === 0 && re.test(str[pos])) {
89
- break;
90
- }
91
- }
92
- if (brackets) {
93
- // throw new Error('Unpaired curly in type doc');
94
- error = 'Unpaired brackets in type doc';
95
- pos -= res.length;
96
- return '';
97
- }
98
- return res;
99
- }
100
- function _rest() { // (?:\s+([^$]+))?
101
- _skipws();
102
- return str.substr(pos);
103
- }
104
- }
8
+ var parse = require('./parser.js');
105
9
 
106
- function parse_chunk(source, base_line_number, opts) {
107
- source = source
108
- .reduce(function(sections, line) {
109
- if (line.value === '' && line.line === base_line_number) return sections;
110
- if (line.value.match(/^@(\w+)/)) { sections.push([]); }
111
- var section = sections[sections.length - 1];
112
- section.line = 'line' in section ? section.line : line.line;
113
- section.push(line.value);
114
- return sections;
115
- }, [[]])
116
- .map(function(section) {
117
- return {value: section.length ? section.join('\n').trim() : null, line: section.line};
118
- });
119
-
120
- var description = source.shift();
121
-
122
- var tags = source.reduce(function(tags, tag) {
123
- var tag_node = parse_tag_line(tag.value);
124
- if (!tag_node) { return tags; }
125
-
126
- tag_node.line = Number(tag.line);
127
- if (opts.raw_value) {
128
- tag_node.value = tag.value;
129
- }
130
-
131
- // used for split results below
132
- var parts;
133
-
134
- // parsing optional and default value if exists
135
- // probably if should be hidden with option or moved out to some jsdoc standard
136
- if (tag_node.name[0] === '[' && tag_node.name[tag_node.name.length - 1] === ']') {
137
- tag_node.optional = true;
138
- tag_node.name = tag_node.name.substr(1, tag_node.name.length - 2);
139
-
140
- // default value here
141
- if (tag_node.name.indexOf('=') !== -1) {
142
- parts = tag_node.name.split('=');
143
- tag_node.name = parts[0];
144
- tag_node.default = parts[1].replace(/^(["'])(.+)(\1)$/, '$2');
145
- }
146
- }
147
-
148
- // hidden with `dotted_names` parsing of `obj.value` naming standard
149
- if (opts.dotted_names && tag_node.name.indexOf('.') !== -1) {
150
- var parent_name;
151
- var parent_tag;
152
- var parent_tags = tags;
153
- parts = tag_node.name.split('.');
154
-
155
- while (parts.length > 1) {
156
- parent_name = parts.shift();
157
- parent_tag = _find(parent_tags, {
158
- tag : tag_node.tag,
159
- name : parent_name
160
- });
161
-
162
- if (!parent_tag) {
163
- parent_tag = {
164
- tag : tag_node.tag,
165
- line : Number(tag_node.line),
166
- name : parent_name,
167
- type : '',
168
- description : ''
169
- };
170
- parent_tags.push(parent_tag);
171
- }
172
-
173
- parent_tag.tags = parent_tag.tags || [];
174
- parent_tags = parent_tag.tags;
175
- }
176
-
177
- tag_node.name = parts[0];
178
- parent_tags.push(tag_node);
179
- return tags;
180
- }
181
-
182
- return tags.concat(tag_node);
183
- }, []);
184
-
185
- return {
186
- tags : tags,
187
- line : Number(description.line || 0),
188
- description : description.value || ''
189
- };
190
- }
10
+ module.exports = parse;
191
11
 
192
- function mkextract(opts) {
193
- var chunk = null;
194
- var line_number = 0;
195
- var base_line_number = 0;
196
-
197
- return function extract(line) {
198
- line_number += 1;
199
-
200
- // if oneliner
201
- // then parse it immediately
202
- if (!chunk && line.match(RE_COMMENT_1LINE)) {
203
- // console.log('line (1)', line, line_number);
204
- // console.log(' clean:', line.replace(RE_COMMENT_1LINE, '$1'));
205
- return parse_chunk([{value: line.replace(RE_COMMENT_1LINE, '$1'), line: line_number - 1}], line_number - 1, opts);
206
- }
207
-
208
- // if start of comment
209
- // then init the chunk
210
- if (line.match(RE_COMMENT_START)) {
211
- // console.log('line (1)', line);
212
- // console.log(' clean:', line.replace(RE_COMMENT_START, ''));
213
- base_line_number = line_number - 1;
214
- chunk = [{value: line.replace(RE_COMMENT_START, ''), line: line_number - 1}];
215
- return null;
216
- }
217
-
218
- // if comment line and chunk started
219
- // then append
220
- if (chunk && line.match(RE_COMMENT_LINE)) {
221
- // console.log('line (2)', line);
222
- // console.log(' clean:', line.replace(RE_COMMENT_LINE, ''));
223
- chunk.push({value: line.replace(RE_COMMENT_LINE, ''), line: line_number - 1});
224
- return null;
225
- }
226
-
227
- // if comment end and chunk started
228
- // then parse the chunk and push
229
- if (chunk && line.match(RE_COMMENT_END)) {
230
- // console.log('line (3)', line);
231
- // console.log(' clean:', line.replace(RE_COMMENT_END, ''));
232
- chunk.push({value: line.replace(RE_COMMENT_END, ''), line: line_number - 1});
233
- return parse_chunk(chunk, base_line_number, opts);
234
- }
235
-
236
- // if non-comment line
237
- // then reset the chunk
238
- chunk = null;
239
- };
240
- }
241
-
242
- /* ------- Transform strean ------- */
12
+ /* ------- Transform stream ------- */
243
13
 
244
14
  function Parser(opts) {
245
15
  opts = opts || {};
246
16
  stream.Transform.call(this, {objectMode: true});
247
- this._extract = mkextract(opts);
17
+ this._extract = parse.mkextract(opts);
248
18
  }
249
19
 
250
20
  util.inherits(Parser, stream.Transform);
@@ -252,7 +22,7 @@ util.inherits(Parser, stream.Transform);
252
22
  Parser.prototype._transform = function transform(data, encoding, done) {
253
23
 
254
24
  var block;
255
- var lines = data.split(/\n/);
25
+ var lines = data.toString().split(/\n/);
256
26
 
257
27
  while (lines.length) {
258
28
  block = this._extract(lines.shift());
@@ -264,34 +34,25 @@ Parser.prototype._transform = function transform(data, encoding, done) {
264
34
  done();
265
35
  };
266
36
 
267
- /* ------- Public API ------- */
268
-
269
- module.exports = function parse(source, opts) {
270
- opts = opts || {};
271
-
272
- var block;
273
- var blocks = [];
274
- var extract = mkextract(opts);
275
- var lines = source.split(/\n/);
276
-
277
- while (lines.length) {
278
- block = extract(lines.shift());
279
- if (block) {
280
- blocks.push(block);
281
- }
282
- }
283
-
284
- return blocks;
37
+ module.exports.stream = function stream(opts) {
38
+ return new Parser(opts);
285
39
  };
286
40
 
41
+ /* ------- File parser ------- */
42
+
287
43
  module.exports.file = function file(file_path, done) {
288
44
 
45
+ var opts = {};
289
46
  var collected = [];
290
47
 
48
+ if (arguments.length === 3) {
49
+ opts = done;
50
+ done = arguments[2];
51
+ }
52
+
291
53
  return fs.createReadStream(file_path, {encoding: 'utf8'})
292
54
  .on('error', done)
293
-
294
- .pipe(new Parser())
55
+ .pipe(new Parser(opts))
295
56
  .on('error', done)
296
57
  .on('data', function(data) {
297
58
  collected.push(data);
@@ -301,22 +62,3 @@ module.exports.file = function file(file_path, done) {
301
62
  });
302
63
  };
303
64
 
304
- module.exports.stream = function stream(opts) {
305
- return new Parser(opts);
306
- };
307
-
308
- function _find(list, filter) {
309
- var i, l, k, yes, item;
310
- for (i = 0, l = list.length; i < l; i++) {
311
- item = list[i];
312
- yes = true;
313
- for (k in filter) {
314
- if (filter.hasOwnProperty(k)) {
315
- yes = yes && filter[k] === list[i][k];
316
- }
317
- }
318
- if (yes) {
319
- return item;
320
- }
321
- }
322
- }
package/package.json CHANGED
@@ -1,19 +1,24 @@
1
1
  {
2
2
  "name": "comment-parser",
3
- "version": "0.2.4",
3
+ "version": "0.4.0",
4
4
  "description": "Generic JSDoc-like comment parser. ",
5
5
  "main": "index.js",
6
6
  "directories": {
7
7
  "test": "tests"
8
8
  },
9
9
  "dependencies": {
10
+ "readable-stream": "^2.0.4"
10
11
  },
11
12
  "devDependencies": {
13
+ "chai": "~1.9.0",
14
+ "jshint": "^2.5.10",
15
+ "jshint-stylish": "^1.0.0",
12
16
  "mocha": "~1.17.1",
13
- "chai": "~1.9.0"
17
+ "nodemon": "^1.2.1"
14
18
  },
15
19
  "scripts": {
16
- "test": "mocha tests/*"
20
+ "test": "jshint --reporter node_modules/jshint-stylish/stylish.js index.js && mocha tests/*",
21
+ "watch": "nodemon -q -w index.js -w parser.js -w tests/ -x npm test"
17
22
  },
18
23
  "repository": {
19
24
  "type": "git",