312 lines
8.4 KiB
Plaintext
312 lines
8.4 KiB
Plaintext
// doc.js
|
|
function docOf(obj, prop) {
|
|
var block = obj[cell.DOC];
|
|
if (!block) return '';
|
|
|
|
// 1) If `block` is a string, that's the entire doc for `obj`.
|
|
// If a sub-property is requested, we have nowhere to look → return ''.
|
|
if (typeof block == 'string') {
|
|
return prop ? '' : block;
|
|
}
|
|
|
|
// 2) Otherwise, if `block` is an object:
|
|
// (a) With no `prop`, return block.doc or block[cell.DOC].
|
|
// (b) If `prop` is given, look for doc specifically for that property (just one level).
|
|
if (typeof block == 'object') {
|
|
// 2a) No property → top-level doc
|
|
if (!prop) {
|
|
if (typeof block.doc == 'string') {
|
|
return block.doc;
|
|
}
|
|
if (typeof block[cell.DOC] == 'string') {
|
|
return block[cell.DOC];
|
|
}
|
|
return '';
|
|
}
|
|
|
|
// 2b) If a prop is requested → see if there's a doc string or object for that property
|
|
var subBlock = block[prop];
|
|
if (!subBlock) return '';
|
|
if (typeof subBlock == 'string') {
|
|
return subBlock;
|
|
}
|
|
if (typeof subBlock == 'object') {
|
|
if (typeof subBlock.doc == 'string') {
|
|
return subBlock.doc;
|
|
}
|
|
if (typeof subBlock[cell.DOC] == 'string') {
|
|
return subBlock[cell.DOC];
|
|
}
|
|
return '';
|
|
}
|
|
}
|
|
|
|
// If nothing matched, return empty.
|
|
return '';
|
|
}
|
|
|
|
function parseDocStr(docStr) {
|
|
if (!docStr) return [];
|
|
|
|
var lines = docStr.split('\n');
|
|
var paramRe = /^:param\s+([A-Za-z0-9_]+)\s*:\s*(.*)$/;
|
|
var returnRe = /^:return:\s*(.*)$/;
|
|
|
|
var descLines = [];
|
|
var paramLines = [];
|
|
var returnLines = [];
|
|
|
|
for (var i = 0; i < lines.length; i++) {
|
|
var line = lines[i].trim();
|
|
if (!line) {
|
|
// Keep empty lines in the desc section
|
|
descLines.push('');
|
|
continue;
|
|
}
|
|
|
|
var pm = paramRe.exec(line);
|
|
var rm = returnRe.exec(line);
|
|
|
|
if (pm) {
|
|
paramLines.push('**' + pm[1] + '**: ' + pm[2]);
|
|
paramLines.push('');
|
|
} else if (rm) {
|
|
returnLines.push('**Returns**: ' + rm[1]);
|
|
returnLines.push('');
|
|
} else {
|
|
descLines.push(line);
|
|
}
|
|
}
|
|
|
|
var final = [];
|
|
|
|
// Reassemble: description → params → returns
|
|
if (descLines.length) {
|
|
final.push.apply(final, descLines);
|
|
if (paramLines.length || returnLines.length) {
|
|
final.push('');
|
|
}
|
|
}
|
|
|
|
if (paramLines.length) {
|
|
final.push.apply(final, paramLines);
|
|
}
|
|
|
|
if (returnLines.length) {
|
|
// If there were param lines, ensure blank line before the returns
|
|
if (paramLines.length && returnLines[0] != '') {
|
|
final.push('');
|
|
}
|
|
final.push.apply(final, returnLines);
|
|
}
|
|
|
|
return final;
|
|
}
|
|
|
|
function writeDocFile(obj, title) {
|
|
var lines = [];
|
|
var docTitle = title || "Documentation";
|
|
|
|
lines.push('# ' + docTitle + '\n');
|
|
walkObject(obj, lines, 1, docTitle);
|
|
|
|
return lines.join('\n');
|
|
// If you had I/O, you'd write to file here
|
|
}
|
|
|
|
/**
|
|
* Documents the top-level object and each of its immediate properties,
|
|
* but does NOT recurse deeper than that.
|
|
*/
|
|
function walkObject(obj, lines, level, name) {
|
|
// Print top-level doc or fallback heading
|
|
var topDocStr = docOf(obj);
|
|
if (topDocStr) {
|
|
var docOut = parseDocStr(topDocStr);
|
|
if (docOut.length > 0) {
|
|
lines.push(docOut.join('\n') + '\n');
|
|
}
|
|
}
|
|
|
|
var propNames = Object.getOwnPropertyNames(obj);
|
|
for (var i = 0; i < propNames.length; i++) {
|
|
var prop = propNames[i];
|
|
if (prop == 'constructor') continue;
|
|
|
|
var desc = Object.getOwnPropertyDescriptor(obj, prop);
|
|
if (!desc) continue;
|
|
|
|
// Check if accessor property (getter/setter)
|
|
var hasGetter = typeof desc.get == 'function';
|
|
var hasSetter = typeof desc.set == 'function';
|
|
|
|
if (hasGetter || hasSetter) {
|
|
writeProperty(lines, obj, prop, desc, level);
|
|
continue;
|
|
}
|
|
|
|
// If it's a normal data property
|
|
if ('value' in desc) {
|
|
var val = desc.value;
|
|
|
|
// If it's a function, treat it like a method
|
|
if (typeof val == 'function') {
|
|
writeMethod(lines, obj, prop, val, level);
|
|
}
|
|
// If it's an object, just print doc for that object (no deep recursion)
|
|
else if (val && typeof val == 'object') {
|
|
writeSubObject(lines, obj, prop, val, level);
|
|
}
|
|
// Otherwise, it's a primitive or something else
|
|
else {
|
|
writeDataProperty(lines, obj, prop, val, level);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Write out a heading and doc for an object property
|
|
* (but do NOT recurse into that object's subproperties).
|
|
*/
|
|
function writeSubObject(lines, parentObj, prop, val, level) {
|
|
// E.g. "## someObject <sub>object</sub>"
|
|
var heading = '#'.repeat(level + 2) + ' ' + prop + ' <sub>object</sub>';
|
|
lines.push(heading + '\n');
|
|
|
|
// 1) If there's doc assigned specifically to the parent's doc block for `prop`
|
|
var docStr = docOf(parentObj, prop);
|
|
// 2) If no doc there, see if the object itself has top-level doc
|
|
if (!docStr) {
|
|
docStr = docOf(val);
|
|
}
|
|
|
|
var docOut = parseDocStr(docStr);
|
|
if (docOut.length > 0) {
|
|
lines.push(docOut.join('\n') + '\n');
|
|
}
|
|
}
|
|
|
|
function writeProperty(lines, parentObj, prop, desc, level) {
|
|
// E.g. "## myProp <sub>accessor</sub>"
|
|
var heading = '#'.repeat(level + 2) + ' ' + prop + ' <sub>accessor</sub>';
|
|
lines.push(heading + '\n');
|
|
|
|
var hasGetter = typeof desc.get == 'function';
|
|
var hasSetter = typeof desc.set == 'function';
|
|
|
|
if (hasGetter && !hasSetter) {
|
|
lines.push('(read only)\n');
|
|
} else if (!hasGetter && hasSetter) {
|
|
lines.push('(set only)\n');
|
|
}
|
|
|
|
// Look up doc in parent's doc block first, or in the accessor functions
|
|
var docStr = docOf(parentObj, prop);
|
|
if (!docStr && hasGetter) {
|
|
docStr = docOf(desc.get) || docStr;
|
|
}
|
|
if (!docStr && hasSetter) {
|
|
docStr = docOf(desc.set) || docStr;
|
|
}
|
|
|
|
var docOut = parseDocStr(docStr);
|
|
if (docOut.length > 0) {
|
|
lines.push(docOut.join('\n') + '\n');
|
|
}
|
|
}
|
|
|
|
function writeMethod(lines, parentObj, prop, fn, level) {
|
|
// Pull doc from the function or from the parent's doc block
|
|
var docStr = fn[cell.DOC] || docOf(parentObj, prop) || '';
|
|
var paramNames = [];
|
|
|
|
// Try to extract param names from the doc
|
|
if (docStr) {
|
|
var docLines = docStr.split('\n');
|
|
var paramRe = /^:param\s+([A-Za-z0-9_]+)\s*:\s*(.*)$/;
|
|
for (var j = 0; j < docLines.length; j++) {
|
|
var pm = paramRe.exec(docLines[j]);
|
|
if (pm) paramNames.push(pm[1]);
|
|
}
|
|
}
|
|
|
|
// E.g. "## myFunction(param1, param2) <sub>function</sub>"
|
|
var heading = '#'.repeat(level + 2) + ' ' + prop;
|
|
|
|
if (paramNames.length > 0) {
|
|
heading += '(' + paramNames.join(', ') + ')';
|
|
} else {
|
|
// As a fallback, parse function signature
|
|
var m = fn.toString().match(/\(([^)]*)\)/);
|
|
if (m && m[1].trim()) {
|
|
heading += '(' + m[1].trim() + ')';
|
|
} else {
|
|
// If no params at all (or we cannot detect them), still add ()
|
|
heading += '()';
|
|
}
|
|
}
|
|
|
|
heading += ' <sub>function</sub>';
|
|
lines.push(heading + '\n');
|
|
|
|
var docOut = parseDocStr(docStr);
|
|
if (docOut.length > 0) {
|
|
lines.push(docOut.join('\n') + '\n');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Write out a heading and doc for a primitive (string, number, boolean, etc).
|
|
*/
|
|
function writeDataProperty(lines, parentObj, prop, val, level) {
|
|
// E.g. "## someString <sub>string</sub>"
|
|
var typeStr = typeof val;
|
|
var heading = '#'.repeat(level + 2) + ' ' + prop + ' <sub>' + typeStr + '</sub>';
|
|
lines.push(heading + '\n');
|
|
|
|
// Look up doc in parent's doc block
|
|
var docStr = docOf(parentObj, prop);
|
|
var docOut = parseDocStr(docStr);
|
|
if (docOut.length > 0) {
|
|
lines.push(docOut.join('\n') + '\n');
|
|
}
|
|
}
|
|
|
|
// Provide a doc string for writeDocFile
|
|
writeDocFile[cell.DOC] = `Return a markdown string for a given obj, with an optional title.`;
|
|
|
|
var doc = {writeDocFile: writeDocFile}
|
|
doc[cell.DOC] = `
|
|
Provides a consistent way to create documentation for prosperon elements. Objects are documented by adding docstrings directly to object-like things (functions, objects, ...), or to an object's own "doc object".
|
|
|
|
Docstrings are set to the symbol \`cell.DOC\`
|
|
|
|
\`\`\`js
|
|
// Suppose we have a module that returns a function
|
|
function greet(name) { log.console("Hello, " + name) }
|
|
|
|
// We can attach a docstring
|
|
greet.doc = \`
|
|
Greets the user by name.
|
|
:param name: The name of the person to greet.
|
|
\`
|
|
|
|
// A single function is a valid return!
|
|
return greet
|
|
\`\`\`
|
|
|
|
\`\`\`js
|
|
// Another way is to add a docstring object to an object
|
|
var greet = {
|
|
hello() { log.console('hello!') }
|
|
}
|
|
|
|
greet[cell.DOC] = {}
|
|
greet[cell.DOC][cell.DOC] = 'An object full of different greeter functions'
|
|
greet[cell.DOC].hello = 'A greeter that says, "hello!"'
|
|
\`\`\`
|
|
`
|
|
|
|
return doc
|