// 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 object"
var heading = '#'.repeat(level + 2) + ' ' + prop + ' object';
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 accessor"
var heading = '#'.repeat(level + 2) + ' ' + prop + ' accessor';
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) function"
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 += ' function';
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 string"
var typeStr = typeof val;
var heading = '#'.repeat(level + 2) + ' ' + prop + ' ' + typeStr + '';
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