Files
cell/scripts/modules/doc.js
2025-02-09 00:07:01 -06:00

286 lines
8.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

function docOf(obj, prop) {
// Grab the top-level doc block on the object.
var block = obj[prosperon.DOC];
if (!block) return '';
// 1) If `block` is directly a string, thats the entire doc for the object.
// If a sub-property was requested, we have nowhere to look that up → return ''.
if (typeof block === 'string') {
return prop ? '' : block;
}
// 2) Otherwise, `block` is (hopefully) an object. We handle two scenarios:
// (a) No `prop` asked for → return block.doc or block[prosperon.DOC].
// (b) If a `prop` is asked for → check if block[prop] is a string or object with its own doc.
if (typeof block === 'object') {
// 2a) If no property was requested, return block.doc or block[prosperon.DOC], if either is a string.
if (!prop) {
if (typeof block.doc === 'string') {
return block.doc;
}
if (typeof block[prosperon.DOC] === 'string') {
return block[prosperon.DOC];
}
return '';
}
// 2b) If a property is requested, see if there's a doc for that property inside the block.
var subBlock = block[prop];
if (!subBlock) return '';
// If subBlock is a string → return it as the doc
if (typeof subBlock === 'string') {
return subBlock;
}
// If subBlock is an object, see if it has .doc or [prosperon.DOC]
if (typeof subBlock === 'object') {
if (typeof subBlock.doc === 'string') {
return subBlock.doc;
}
if (typeof subBlock[prosperon.DOC] === 'string') {
return subBlock[prosperon.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*(.*)$/;
// We'll collect three categories of lines, then reassemble:
var descLines = []; // For plain description lines
var paramLines = []; // For :param: lines
var returnLines = []; // For :return: lines
for (var i = 0; i < lines.length; i++) {
var line = lines[i].trim();
if (!line) {
// Keep empty lines with the "desc" section to maintain spacing
descLines.push('');
continue;
}
var pm = paramRe.exec(line);
var rm = returnRe.exec(line);
if (pm) {
// param line → store in paramLines
paramLines.push('**' + pm[1] + '**: ' + pm[2]);
paramLines.push('');
} else if (rm) {
// return line → store in returnLines
returnLines.push('**Returns**: ' + rm[1]);
returnLines.push('');
} else {
// Otherwise, it's a general description line
descLines.push(line);
}
}
// Now reassemble in the fixed order:
// 1) description lines, 2) param lines, 3) returns lines
var final = [];
if (descLines.length) {
final.push.apply(final, descLines);
// Insert blank line if needed
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
// io.slurpwrite(outputPath, finalStr)
}
function walkObject(obj, lines, level, name) {
// See what doc string (if any) this object has
var topDocStr = docOf(obj);
// If there's an actual docstring for the object, don't output another heading
if (topDocStr) {
// parseDocStr is your existing function that handles :param etc.
var docOut = parseDocStr(topDocStr);
if (docOut.length > 0) {
lines.push(docOut.join('\n') + '\n');
}
}
// Otherwise, if there's no docstring, insert an auto-generated heading
else {
var heading = '#'.repeat(level + 1) + ' ' + name;
lines.push(heading + '\n');
}
// Now enumerate this object's direct properties
var propNames = Object.keys(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 this is an accessor property (getter/setter)
var hasGetter = typeof desc.get === 'function';
var hasSetter = typeof desc.set === 'function';
if (hasGetter || hasSetter) {
// We handle them as a single "property" entry,
// not separate get/set headings.
writeProperty(lines, obj, prop, desc, level);
continue;
}
// Otherwise, if there's a "value" (could be a function or an object)
if ('value' in desc) {
var val = desc.value;
if (typeof val === 'function') {
writeMethod(lines, obj, prop, val, level);
} else if (val && typeof val === 'object') {
// Possibly a nested object; check if it or its children have doc
if (hasAnyDoc(val)) {
walkObject(val, lines, level + 1, prop);
}
}
}
}
}
/**
* Writes out a single property entry for an accessor (getter/setter).
* - If only a getter, adds "(read only)" just below the property name.
* - If only a setter, adds "(set only)" just below the property name.
* - If both, no extra text is added (but we do a single heading).
*/
function writeProperty(lines, parentObj, prop, desc, level) {
var heading = '#'.repeat(level + 2) + ' ' + prop;
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');
}
// If both, we do nothing extra
// Collect doc from the property-level doc block
// If that's empty, see if either accessor function has doc
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 hasAnyDoc(o) {
if (!o) return false;
// If the object itself has a docOf
if (docOf(o)) return true;
// Or if any property is a function that has doc
var names = Object.getOwnPropertyNames(o);
for (var i = 0; i < names.length; i++) {
var desc = Object.getOwnPropertyDescriptor(o, names[i]);
if (!desc) continue;
// check if accessor doc
if (typeof desc.get === 'function' && docOf(desc.get)) return true;
if (typeof desc.set === 'function' && docOf(desc.set)) return true;
// check function doc
if (desc.value && typeof desc.value === 'function') {
if (docOf(desc.value) || docOf(o, names[i])) return true;
}
// or check nested object
if (desc.value && typeof desc.value === 'object') {
if (hasAnyDoc(desc.value)) return true;
}
}
return false;
}
function writeMethod(lines, parentObj, prop, fn, level) {
var docStr = fn[prosperon.DOC] || docOf(parentObj, prop) || '';
// parse :param lines
var paramNames = [];
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]);
}
}
var heading = '#'.repeat(level + 2) + ' ' + prop;
// if we found param names in the doc, show them in the heading
if (paramNames.length > 0) {
heading += '(' + paramNames.join(', ') + ')';
} else {
// fallback: parse the function signature
var m = fn.toString().match(/\(([^)]*)\)/);
if (m && m[1].trim()) {
heading += '(' + m[1].trim() + ')';
}
}
lines.push(heading + '\n');
// parse doc lines
var docOut = parseDocStr(docStr);
if (docOut.length > 0) {
lines.push(docOut.join('\n') + '\n');
}
}
writeDocFile[prosperon.DOC] = `Return a markdown string for a given obj, with optional title.`;
return {
writeDocFile: writeDocFile
};