comment-parser 0.2.3 → 0.3.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/.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.3.2
2
+ - fix RegExp for `description` extraction to allow $ char
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,5 +1,5 @@
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
 
@@ -25,50 +25,120 @@ 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
67
  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
68
 
61
- You can also make raw line available in parsed results by passing `opts.raw_value = true`.
62
-
63
69
  Invalid comment blocks are skipped. Comments starting with `/*` and `/***` are considered not valid.
64
70
 
65
71
  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
72
 
67
- Happy coding :)
73
+ ## Custom parsers
74
+
75
+
76
+ 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}`.
77
+
78
+ 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.
68
79
 
80
+ 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:
69
81
 
70
- Contributors
71
- ============
82
+ ```
83
+ /**
84
+ * Source to be parsed below
85
+ * @tag {type} name Description
86
+ */
87
+ parse(source, {parsers: [
88
+ // takes entire string
89
+ function parse_tag(str, data) {
90
+ return {source: ' @tag', data: {tag: 'tag'}};
91
+ },
92
+ // parser throwing exception
93
+ function check_tag(str, data) {
94
+ if (allowed_tags.indexOf(data.tag) === -1) {
95
+ throw new Error('Unrecognized tag "' + data.tag + '"');
96
+ }
97
+ },
98
+ // takes the rest of the string after ' @tag''
99
+ function parse_name1(str, data) {
100
+ return {source: ' name', data: {name: 'name1'}};
101
+ },
102
+ // alternative name parser
103
+ function parse_name2(str, data) {
104
+ return {source: ' name', data: {name: 'name2'}};
105
+ }
106
+ ]});
107
+ ```
108
+
109
+ This would produce following:
72
110
 
111
+ ```
112
+ [{
113
+ "tags": [{
114
+ "tag": "tag",
115
+ "errors": [
116
+ "check_tag: Unrecognized tag \\"tag\\""
117
+ ],
118
+ "name": "name2",
119
+ "optional": false,
120
+ "type": "",
121
+ "description": "",
122
+ "line": 2,
123
+ "source": "@tag {type} name Description"
124
+ }],
125
+ "line": 0,
126
+ "description": "Source to be parsed below",
127
+ "source": "Source to be parsed below\n@tag {type} name Description"
128
+ }]
129
+ ```
130
+
131
+ ## Packaging
132
+
133
+ `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`
134
+
135
+ ## Contributors
136
+
137
+
138
+ - [Alexej Yaroshevich](https://github.com/zxqfox)
139
+ - [Evgeny Reznichenko](https://github.com/zxcabs)
140
+ - [Jordan Harband](https://github.com/ljharb)
73
141
  - [Sergii Iavorskyi](https://github.com/yavorskiy)
74
- - [Alexej Yaroshevich](https://github.com/zxqfox)
142
+
143
+
144
+ Happy coding :)
package/index.js CHANGED
@@ -1,243 +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
-
11
- /**
12
- * analogue of str.match(/@(\S+)(?:\s+\{([^\}]+)\})?(?:\s+(\S+))?(?:\s+([^$]+))?/);
13
- * @param {string} str raw jsdoc string
14
- * @returns {object} parsed tag node
15
- */
16
- function parse_tag_line(str) {
17
- if (typeof str !== 'string') { return false; }
18
-
19
- if (str[0] !== '@') { return false; }
20
-
21
- var pos = 1;
22
- var l = str.length;
23
- var error = null;
24
- var res = {
25
- tag : _tag(),
26
- type : _type() || '',
27
- name : _name() || '',
28
- description : _rest() || ''
29
- };
30
-
31
- if (error) {
32
- res.error = error;
33
- }
34
-
35
- return res;
36
-
37
- function _skipws() {
38
- while (str[pos] === ' ' && pos < l) { pos ++; }
39
- }
40
- function _tag() { // @(\S+)
41
- var sp = str.indexOf(' ', pos);
42
- sp = sp < 0 ? l : sp;
43
- var res = str.substr(pos, sp - pos);
44
- pos = sp;
45
- return res;
46
- }
47
- function _type() { // (?:\s+\{([^\}]+)\})?
48
- _skipws();
49
- if (str[pos] !== '{') { return ''; }
50
- var ch;
51
- var res = '';
52
- var curlies = 0;
53
- while (pos < l) {
54
- ch = str[pos];
55
- curlies += ch === '{' ? 1 : ch === '}' ? -1 : 0;
56
- res += ch;
57
- pos ++;
58
- if (!curlies) {
59
- break;
60
- }
61
- }
62
- if (curlies !== 0) {
63
- // throw new Error('Unpaired curly in type doc');
64
- error = 'Unpaired curly in type doc';
65
- pos -= res.length;
66
- return '';
67
- }
68
- return res.substr(1, res.length - 2);
69
- }
70
- function _name() { // (?:\s+(\S+))?
71
- if (error) { return ''; }
72
- _skipws();
73
- var ch;
74
- var res = '';
75
- var brackets = 0;
76
- var re = /\s/;
77
- while (pos < l) {
78
- ch = str[pos];
79
- brackets += ch === '[' ? 1 : ch === ']' ? -1 : 0;
80
- res += ch;
81
- pos ++;
82
- if (brackets === 0 && re.test(str[pos])) {
83
- break;
84
- }
85
- }
86
- if (brackets) {
87
- // throw new Error('Unpaired curly in type doc');
88
- error = 'Unpaired brackets in type doc';
89
- pos -= res.length;
90
- return '';
91
- }
92
- return res;
93
- }
94
- function _rest() { // (?:\s+([^$]+))?
95
- _skipws();
96
- return str.substr(pos);
97
- }
98
- }
99
-
100
- function parse_chunk(source, opts) {
101
- source = source
102
- .reduce(function(sections, line) {
103
- if (line.value.match(/^@(\w+)/)) { sections.push([]); }
104
- var section = sections[sections.length - 1];
105
- section.line = section.line || line.line;
106
- section.push(line.value);
107
- return sections;
108
- }, [[]])
109
- .map(function(section) {
110
- return {value: section.join('\n').trim(), line: section.line};
111
- });
112
-
113
- var description = source[0].value.match(/^@(\S+)/) ? {value: '', line: 0} : source.shift();
114
-
115
- var tags = source.reduce(function(tags, tag) {
116
- var tag_node = parse_tag_line(tag.value);
117
- if (!tag_node) { return tags; }
118
-
119
- tag_node.line = Number(tag.line);
120
- if (opts.raw_value) {
121
- tag_node.value = tag.value;
122
- }
123
-
124
- // used for split results below
125
- var parts;
126
-
127
- // parsing optional and default value if exists
128
- // probably if should be hidden with option or moved out to some jsdoc standard
129
- if (tag_node.name[0] === '[' && tag_node.name[tag_node.name.length - 1] === ']') {
130
- tag_node.optional = true;
131
- tag_node.name = tag_node.name.substr(1, tag_node.name.length - 2);
132
-
133
- // default value here
134
- if (tag_node.name.indexOf('=') !== -1) {
135
- parts = tag_node.name.split('=');
136
- tag_node.name = parts[0];
137
- tag_node.default = parts[1].replace(/^(["'])(.+)(\1)$/, '$2');
138
- }
139
- }
140
-
141
- // hidden with `dotted_names` parsing of `obj.value` naming standard
142
- if (opts.dotted_names && tag_node.name.indexOf('.') !== -1) {
143
- var parent_name;
144
- var parent_tag;
145
- var parent_tags = tags;
146
- parts = tag_node.name.split('.');
147
-
148
- while (parts.length > 1) {
149
- parent_name = parts.shift();
150
- parent_tag = _find(parent_tags, {
151
- tag : tag_node.tag,
152
- name : parent_name
153
- });
154
-
155
- if (!parent_tag) {
156
- parent_tag = {
157
- tag : tag_node.tag,
158
- line : Number(tag_node.line),
159
- name : parent_name,
160
- type : '',
161
- description : ''
162
- };
163
- parent_tags.push(parent_tag);
164
- }
165
-
166
- parent_tag.tags = parent_tag.tags || [];
167
- parent_tags = parent_tag.tags;
168
- }
169
-
170
- tag_node.name = parts[0];
171
- parent_tags.push(tag_node);
172
- return tags;
173
- }
174
-
175
- return tags.concat(tag_node);
176
- }, []);
177
-
178
- return {
179
- tags : tags,
180
- line : Number(description.line || 0),
181
- description : description.value
182
- };
183
- }
8
+ var parse = require('./parser.js');
184
9
 
185
- function mkextract(opts) {
10
+ module.exports = parse;
186
11
 
187
- var chunk = null;
188
- var line_number = 0;
189
-
190
- return function extract(line) {
191
- // if oneliner
192
- // then parse it immediately
193
- if (!line_number && line.match(RE_COMMENT_1LINE)) {
194
- // console.log('line (1)', line);
195
- // console.log(' clean:', line.replace(RE_COMMENT_1LINE, '$1'));
196
- return parse_chunk([{value: line.replace(RE_COMMENT_1LINE, '$1'), line: 0}], opts);
197
- }
198
-
199
- line_number += 1;
200
-
201
- // if start of comment
202
- // then init the chunk
203
- if (line.match(RE_COMMENT_START)) {
204
- // console.log('line (1)', line);
205
- // console.log(' clean:', line.replace(RE_COMMENT_START, ''));
206
- chunk = [{value: line.replace(RE_COMMENT_START, ''), line: line_number - 1}];
207
- return null;
208
- }
209
-
210
- // if comment line and chunk started
211
- // then append
212
- if (chunk && line.match(RE_COMMENT_LINE)) {
213
- // console.log('line (2)', line);
214
- // console.log(' clean:', line.replace(RE_COMMENT_LINE, ''));
215
- chunk.push({value: line.replace(RE_COMMENT_LINE, ''), line: line_number - 1});
216
- return null;
217
- }
218
-
219
- // if comment end and chunk started
220
- // then parse the chunk and push
221
- if (chunk && line.match(RE_COMMENT_END)) {
222
- // console.log('line (3)', line);
223
- // console.log(' clean:', line.replace(RE_COMMENT_END, ''));
224
- chunk.push({value: line.replace(RE_COMMENT_END, ''), line: line_number - 1});
225
- return parse_chunk(chunk, opts);
226
- }
227
-
228
- // if non-comment line
229
- // then reset the chunk
230
- chunk = null;
231
- line_number = 0;
232
- };
233
- }
234
-
235
- /* ------- Transform strean ------- */
12
+ /* ------- Transform stream ------- */
236
13
 
237
14
  function Parser(opts) {
238
15
  opts = opts || {};
239
16
  stream.Transform.call(this, {objectMode: true});
240
- this._extract = mkextract(opts);
17
+ this._extract = parse.mkextract(opts);
241
18
  }
242
19
 
243
20
  util.inherits(Parser, stream.Transform);
@@ -245,7 +22,7 @@ util.inherits(Parser, stream.Transform);
245
22
  Parser.prototype._transform = function transform(data, encoding, done) {
246
23
 
247
24
  var block;
248
- var lines = data.split(/\n/);
25
+ var lines = data.toString().split(/\n/);
249
26
 
250
27
  while (lines.length) {
251
28
  block = this._extract(lines.shift());
@@ -257,34 +34,25 @@ Parser.prototype._transform = function transform(data, encoding, done) {
257
34
  done();
258
35
  };
259
36
 
260
- /* ------- Public API ------- */
261
-
262
- module.exports = function parse(source, opts) {
263
- opts = opts || {};
264
-
265
- var block;
266
- var blocks = [];
267
- var extract = mkextract(opts);
268
- var lines = source.split(/\n/);
269
-
270
- while (lines.length) {
271
- block = extract(lines.shift());
272
- if (block) {
273
- blocks.push(block);
274
- }
275
- }
276
-
277
- return blocks;
37
+ module.exports.stream = function stream(opts) {
38
+ return new Parser(opts);
278
39
  };
279
40
 
41
+ /* ------- File parser ------- */
42
+
280
43
  module.exports.file = function file(file_path, done) {
281
44
 
45
+ var opts = {};
282
46
  var collected = [];
283
47
 
48
+ if (arguments.length === 3) {
49
+ opts = done;
50
+ done = arguments[2];
51
+ }
52
+
284
53
  return fs.createReadStream(file_path, {encoding: 'utf8'})
285
54
  .on('error', done)
286
-
287
- .pipe(new Parser())
55
+ .pipe(new Parser(opts))
288
56
  .on('error', done)
289
57
  .on('data', function(data) {
290
58
  collected.push(data);
@@ -294,22 +62,3 @@ module.exports.file = function file(file_path, done) {
294
62
  });
295
63
  };
296
64
 
297
- module.exports.stream = function stream(opts) {
298
- return new Parser(opts);
299
- };
300
-
301
- function _find(list, filter) {
302
- var i, l, k, yes, item;
303
- for (i = 0, l = list.length; i < l; i++) {
304
- item = list[i];
305
- yes = true;
306
- for (k in filter) {
307
- if (filter.hasOwnProperty(k)) {
308
- yes = yes && filter[k] === list[i][k];
309
- }
310
- }
311
- if (yes) {
312
- return item;
313
- }
314
- }
315
- }
package/package.json CHANGED
@@ -1,19 +1,24 @@
1
1
  {
2
2
  "name": "comment-parser",
3
- "version": "0.2.3",
3
+ "version": "0.3.2",
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",