Skip to content

Commit fb973ce

Browse files
committed
Parsing module pages properly now
1 parent c65c31b commit fb973ce

File tree

2 files changed

+256
-9
lines changed

2 files changed

+256
-9
lines changed

tools/doc/generate.js

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,6 @@ if (input) {
4242
}
4343

4444
function next(input) {
45-
var lexed = marked.lexer(input);
46-
console.error(lexed.filter(function(tok) {
47-
return true // tok.type === 'heading';
48-
}));
49-
5045
switch (format) {
5146
case 'json':
5247
require('./json.js')(input, function(er, obj) {

tools/doc/json.js

Lines changed: 256 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ var root = {};
88
var stack = [root];
99
var depth = 0;
1010
var current = root;
11+
var state = null;
1112

1213
function doJSON(input, cb) {
1314
var lexed = marked.lexer(input);
@@ -16,15 +17,16 @@ function doJSON(input, cb) {
1617
var text = tok.text;
1718

1819
// <!-- type = module -->
19-
// This is for cases where the markdown itself is lacking.
20+
// This is for cases where the markdown semantic structure is lacking.
2021
var meta;
2122
if (type === 'paragraph' &&
2223
(meta = text.match(/^<!--([^=]+)=([^\-])+-->\n*$/))) {
2324
current[meta[1].trim()] = meta[2].trim();
2425
return
2526
}
2627

27-
if (type === 'heading') {
28+
if (type === 'heading' &&
29+
!text.trim().toLowerCase().match(/^example/)) {
2830
if (Math.abs(tok.depth - depth) > 1) {
2931
return cb(new Error('Inappropriate heading level\n'+
3032
JSON.stringify(tok)));
@@ -47,10 +49,55 @@ function doJSON(input, cb) {
4749
current = newSection(tok);
4850
depth = tok.depth;
4951
stack.push(current);
52+
state = 'AFTERHEADING';
53+
return;
54+
} // heading
55+
56+
// Immediately after a heading, we can expect the following
57+
//
58+
// { type: 'code', text: 'Stability: ...' },
59+
//
60+
// a list: starting with list_start, ending with list_end,
61+
// maybe containing other nested lists in each item.
62+
//
63+
// If one of these isnt' found, then anything that comes between
64+
// here and the next heading should be parsed as the desc.
65+
var stability
66+
if (state === 'AFTERHEADING') {
67+
if (type === 'code' &&
68+
(stability = text.match(/^Stability: ([0-5])(?:\s*-\s*)?(.*)$/))) {
69+
current.stability = parseInt(stability[1], 10);
70+
current.stabilityText = stability[2].trim();
71+
return;
72+
} else if (type === 'list_start' && !tok.ordered) {
73+
state = 'AFTERHEADING_LIST';
74+
current.list = current.list || [];
75+
current.list.push(tok);
76+
current.list.level = 1;
77+
} else {
78+
current.desc = current.desc || [];
79+
current.desc.push(tok);
80+
state = 'DESC';
81+
}
5082
return;
5183
}
5284

53-
// { type: 'code', text: 'Stability: 1 - Experimental' },
85+
if (state === 'AFTERHEADING_LIST') {
86+
current.list.push(tok);
87+
if (type === 'list_start') {
88+
current.list.level++;
89+
} else if (type === 'list_end') {
90+
current.list.level--;
91+
}
92+
if (current.list.level === 0) {
93+
state = 'AFTERHEADING';
94+
processList(current);
95+
}
96+
return;
97+
}
98+
99+
current.desc = current.desc || [];
100+
current.desc.push(tok);
54101

55102
});
56103

@@ -63,6 +110,192 @@ function doJSON(input, cb) {
63110
}
64111

65112

113+
// go from something like this:
114+
// [ { type: 'list_item_start' },
115+
// { type: 'text',
116+
// text: '`settings` Object, Optional' },
117+
// { type: 'list_start', ordered: false },
118+
// { type: 'list_item_start' },
119+
// { type: 'text',
120+
// text: 'exec: String, file path to worker file. Default: `__filename`' },
121+
// { type: 'list_item_end' },
122+
// { type: 'list_item_start' },
123+
// { type: 'text',
124+
// text: 'args: Array, string arguments passed to worker.' },
125+
// { type: 'text',
126+
// text: 'Default: `process.argv.slice(2)`' },
127+
// { type: 'list_item_end' },
128+
// { type: 'list_item_start' },
129+
// { type: 'text',
130+
// text: 'silent: Boolean, whether or not to send output to parent\'s stdio.' },
131+
// { type: 'text', text: 'Default: `false`' },
132+
// { type: 'space' },
133+
// { type: 'list_item_end' },
134+
// { type: 'list_end' },
135+
// { type: 'list_item_end' },
136+
// { type: 'list_end' } ]
137+
// to something like:
138+
// [ { name: 'settings',
139+
// type: 'object',
140+
// optional: true,
141+
// settings:
142+
// [ { name: 'exec',
143+
// type: 'string',
144+
// desc: 'file path to worker file',
145+
// default: '__filename' },
146+
// { name: 'args',
147+
// type: 'array',
148+
// default: 'process.argv.slice(2)',
149+
// desc: 'string arguments passed to worker.' },
150+
// { name: 'silent',
151+
// type: 'boolean',
152+
// desc: 'whether or not to send output to parent\'s stdio.',
153+
// default: 'false' } ] } ]
154+
155+
function processList(section) {
156+
var list = section.list;
157+
var values = [];
158+
var current;
159+
var stack = [];
160+
161+
// for now, *just* build the heirarchical list
162+
list.forEach(function(tok) {
163+
var type = tok.type;
164+
if (type === 'space') return;
165+
if (type === 'list_item_start') {
166+
if (!current) {
167+
var n = {};
168+
values.push(n);
169+
current = n;
170+
} else {
171+
current.options = current.options || [];
172+
stack.push(current);
173+
var n = {};
174+
current.options.push(n);
175+
current = n;
176+
}
177+
return;
178+
} else if (type === 'list_item_end') {
179+
if (!current) {
180+
throw new Error('invalid list - end without current item\n' +
181+
JSON.stringify(tok) + '\n' +
182+
JSON.stringify(list));
183+
}
184+
current = stack.pop();
185+
} else if (type === 'text') {
186+
if (!current) {
187+
throw new Error('invalid list - text without current item\n' +
188+
JSON.stringify(tok) + '\n' +
189+
JSON.stringify(list));
190+
}
191+
current.textRaw = current.textRaw || '';
192+
current.textRaw += tok.text + ' ';
193+
}
194+
});
195+
196+
// shove the name in there for properties, since they are always
197+
// just going to be the value etc.
198+
if (section.type === 'property') {
199+
values[0].textRaw = '`' + section.name + '` ' + values[0].textRaw;
200+
}
201+
202+
// now pull the actual values out of the text bits.
203+
values.forEach(parseListItem);
204+
205+
// Now figure out what this list actually means.
206+
// depending on the section type, the list could be different things.
207+
208+
switch (section.type) {
209+
case 'method':
210+
// each item is an argument, unless the name is 'return',
211+
// in which case it's the return value.
212+
section.params = values.filter(function(v) {
213+
if (v.name === 'return') {
214+
section.return = v;
215+
return false;
216+
}
217+
return true;
218+
});
219+
break;
220+
221+
case 'property':
222+
// there should be only one item, which is the value.
223+
// copy the data up to the section.
224+
var value = values[0];
225+
delete value.name;
226+
section.typeof = value.type;
227+
delete value.type;
228+
Object.keys(value).forEach(function(k) {
229+
section[k] = value[k];
230+
});
231+
break;
232+
233+
case 'event':
234+
// event: each item is an argument.
235+
section.params = values;
236+
break;
237+
}
238+
239+
// section.listParsed = values;
240+
delete section.list;
241+
242+
}
243+
244+
245+
function parseListItem(item) {
246+
if (item.options) item.options.forEach(parseListItem);
247+
if (!item.textRaw) return;
248+
249+
// the goal here is to find the name, type, default, and optional.
250+
// anything left over is 'desc'
251+
var text = item.textRaw.trim();
252+
text = text.replace(/^(Argument|Param)s?\s*:?\s*/i, '');
253+
254+
text = text.replace(/^, /, '').trim();
255+
var retExpr = /^Returns?\s*:?\s*/i;
256+
var ret = text.match(retExpr);
257+
if (ret) {
258+
item.name = 'return';
259+
text = text.replace(retExpr, '');
260+
} else {
261+
var nameExpr = /^['`"]?([^'`": ]+)['`"]?\s*:?\s*/;
262+
var name = text.match(nameExpr);
263+
if (name) {
264+
item.name = name[1];
265+
text = text.replace(nameExpr, '');
266+
}
267+
}
268+
269+
text = text.replace(/^, /, '').trim();
270+
var defaultExpr = /default\s*[:=]?\s*['"`]?([^, '"`]*)['"`]?/i;
271+
var def = text.match(defaultExpr);
272+
if (def) {
273+
item.default = def[1];
274+
text = text.replace(defaultExpr, '');
275+
}
276+
277+
text = text.replace(/^, /, '').trim();
278+
var typeExpr =
279+
/^((?:[a-zA-Z]* )?object|string|bool(?:ean)?|regexp?|null|function)/i;
280+
var type = text.match(typeExpr);
281+
if (type) {
282+
item.type = type[1];
283+
text = text.replace(typeExpr, '');
284+
}
285+
286+
text = text.replace(/^, /, '').trim();
287+
var optExpr = /^Optional\.|(?:, )?Optional$/;
288+
var optional = text.match(optExpr);
289+
if (optional) {
290+
item.optional = true;
291+
text = text.replace(optExpr, '');
292+
}
293+
294+
text = text.trim();
295+
if (text) item.desc = text;
296+
}
297+
298+
66299
function finishSection(section, parent) {
67300
if (!section || !parent) {
68301
throw new Error('Invalid finishSection call\n'+
@@ -72,9 +305,28 @@ function finishSection(section, parent) {
72305

73306
if (!section.type) {
74307
section.type = 'module';
308+
section.displayName = section.name;
75309
section.name = section.name.toLowerCase();
76310
}
77311

312+
if (section.desc) {
313+
section.desc = marked.parser(section.desc);
314+
}
315+
316+
if (section.list) {
317+
processList(section);
318+
}
319+
320+
// properties are a bit special.
321+
// their "type" is the type of object, not "property"
322+
if (section.properties) {
323+
section.properties.forEach(function (p) {
324+
if (p.typeof) p.type = p.typeof;
325+
else delete p.type;
326+
delete p.typeof;
327+
});
328+
}
329+
78330
var plur;
79331
if (section.type.slice(-1) === 's') {
80332
plur = section.type + 'es';
@@ -99,7 +351,7 @@ var methExpr = /^(?:method:?\s*)?[^\.]+\.([^ \.\(\)]+)\([^\)]*\)\s*?$/i;
99351
function newSection(tok) {
100352
var section = {};
101353
// infer the type from the text.
102-
var text = tok.text;
354+
var text = section.textRaw = tok.text;
103355
if (text.match(eventExpr)) {
104356
section.type = 'event';
105357
section.name = text.replace(eventExpr, '$1');

0 commit comments

Comments
 (0)