// 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