Code docs output is serviceable and there's a UI now.

main
Zed A. Shaw 2 years ago
parent 5651b770ce
commit 28cfe55921
  1. 89
      admin/pages/DocsBrowser.svelte
  2. 2
      admin/routes.js
  3. 98
      commands/codedoc.js

@ -0,0 +1,89 @@
<script>
import { push, link } from 'svelte-spa-router';
import Validator from 'Validator';
import { onMount } from 'svelte';
import Layout from '$/client/Layout.svelte';
import Icon from '$/client/components/Icon.svelte';
import Blockstart from "$/client/components/Blockstart.svelte";
import Code from "$/client/components/Code.svelte";
import api from "$/client/api.js";
let index = {};
let docs_data;
let url;
export let params;
const load_docs = async (to_load) => {
const [status, data] = await api.get(`/docs/api/${to_load}.json`);
docs_data = status === 200 ? data : {};
url = to_load;
window.scrollTo(0, 0);
}
const load_index = async () => {
const [status, data] = await api.get("/docs/api/index.json");
index = status === 200 ? data : {};
}
onMount(async () => {
await load_index();
});
$: console.log("params", params);
$: if(params.wild && params.wild !== url) {
load_docs(params.wild);
}
</script>
<style>
sidebar {
max-width: 300px;
}
</style>
<Layout centered={ true } fullwidth={ true }>
<Blockstart>
<block class="horizontal">
<sidebar>
<top><h4><a href="/" use:link><Icon name="chevrons-up" /> Docs</a></h4></top>
<items>
{#each Object.keys(index) as item}
<a class:active={ item === url } href="/docs/{item}" use:link>{item}</a>
{/each}
</items>
</sidebar>
<right style="--pad: 0.5rem;">
{#if docs_data}
{#each docs_data.exports as exp}
<h1>{ url }</h1>
<h2>{exp.name}</h2>
{#if exp.comment}
<pre>
{ exp.comment.value }
</pre>
{/if}
{#if exp.isa === "class"}
{#each exp.methods as method}
<h4>{ method.name }</h4>
{#if exp.comment}
<pre>
{ exp.comment.value }
</pre>
{/if}
<Code content={ method.code } language="javascript" />
{/each}
{:else}
<Code content={ exp.code } language="javascript" />
{/if}
{/each}
{/if}
</right>
</block>
</Blockstart>
</Layout>

@ -8,8 +8,10 @@ import ReadUpdate from "$/admin/pages/ReadUpdate.svelte";
import EmailConfig from "$/admin/pages/EmailConfig.svelte";
import NotFound from "$/client/pages/NotFound.svelte";
import Components from "./bando/Components.svelte";
import DocsBrowser from "$/admin/pages/DocsBrowser.svelte";
export default {
"/docs/*": DocsBrowser,
"/bando/components/:name?": Components,
"/errors/": Errors,
"/routes/": Routes,

@ -2,8 +2,7 @@
import fs from "fs";
import assert from "assert";
import logging from '../lib/logging.js';
import { mkdir, mkdir_to } from "../lib/builderator.js";
import glob from "fast-glob";
import { mkdir, mkdir_to, glob } from "../lib/builderator.js";
import path from "path";
import template from "lodash/template.js";
import * as acorn from "acorn";
@ -18,7 +17,7 @@ export const options = [
]
// example of a positional argument, it's the 1st argument to main
export const argument = ["source", "source directory"];
export const argument = ["<source...>", "source input globs"];
// put required options in the required variable
export const required = [
@ -38,9 +37,10 @@ const dump = (obj) => {
}
class ParseWalker {
constructor(comments) {
constructor(comments, code) {
this.comments = comments;
this.exported = [];
this.code = code;
}
handle_class(root) {
@ -56,7 +56,8 @@ class ParseWalker {
acorn_walk.simple(root, {
ClassDeclaration: (cls_node) => {
assert(cls_node.id.name === new_class.name, "Name of class changed!");
this.exported.push(new_class);
new_class.range = [cls_node.start, cls_node.body.start];
this.add_export(cls_node.id, new_class);
},
MethodDefinition: (meth_node) => {
@ -67,9 +68,13 @@ class ParseWalker {
generator: meth_node.value.generator,
name: meth_node.key.name,
line_start: meth_node.loc.start.line,
range: [meth_node.start, meth_node.value.body.start],
params: this.handle_params(meth_node.value.params),
comment: this.find_comment(meth_node.loc.start.line)
comment: this.find_comment(meth_node.loc.start.line),
}
new_method.code = this.slice_code(new_method.range);
new_class.methods.push(new_method);
}
});
@ -106,51 +111,39 @@ class ParseWalker {
exp.name = id.name;
exp.line_start = id.loc.start.line;
exp.comment = this.find_comment(exp.line_start);
exp.code = this.slice_code(exp.range);
this.exported.push(exp);
}
slice_code(range) {
return this.code.slice(range[0], range[1]);
}
handle_arrow_func(id, arrow) {
this.add_export(id, {
isa: "function",
async: arrow.async,
generator: arrow.generator,
expression: arrow.expression,
range: [id.start, arrow.body.start],
params: this.handle_params(arrow.params),
});
}
handle_variable(root) {
const declare = root.declaration.declarations[0];
// console.log("VARIABLE", declare);
const id = declare.id;
const _node = declare.init;
const init_is = declare.init.type;
if(declare.init.type === "ArrowFunctionExpression") {
if(init_is === "ArrowFunctionExpression") {
this.handle_arrow_func(id, declare.init);
} else {
acorn_walk.simple(root, {
Literal: (_node) => {
this.add_export(id, {
isa: _node.type.toLowerCase(),
value: _node.value,
raw: _node.raw
});
},
CallExpression: (_node) => {
this.add_export(id, {
isa: _node.type.toLowerCase(),
identifier: _node.callee.name,
arguments: this.handle_params(_node.arguments)
});
},
MemberExpression: (_node) => {
this.add_export(id, {
isa: _node.type.toLowerCase(),
object: _node.object.name,
property: _node.property.name,
computed: _node.computed,
options: _node.optional,
});
}
this.add_export(id, {
isa: _node.type.toLowerCase(),
value: _node.value,
range: declare.range,
raw: _node.raw
});
}
}
@ -186,17 +179,16 @@ class ParseWalker {
}
}
export const main = async (arg, opts) => {
const code = fs.readFileSync(arg);
const parse_source = (source) => {
const code = fs.readFileSync(source);
mkdir(opts.output);
let comments = [];
const acorn_opts = {
sourceType: "module",
ecmaVersion: "2023",
locations: true,
sourceFile: arg,
sourceFile: source,
ranges: true,
onComment: comments
}
@ -213,17 +205,45 @@ export const main = async (arg, opts) => {
}
});
const walker = new ParseWalker(comments);
const walker = new ParseWalker(comments, code.toString());
// acorn is stupid and they grab a reference to the functions so that _removes_
// this from the object, instead of just...calling walker.function() like a normal person
acorn_walk.simple(parsed, {
ExportNamedDeclaration: (_node) => walker.handle_export(_node),
});
console.log(dump({
return {
// normalize to / even on windows
source: source.replaceAll("\\", "/"),
exports: walker.exported,
orphan_comments: walker.comments.filter(c => !c.found)
}));
};
}
export const main = async (source_globs, opts) => {
const index = {};
mkdir_to(opts.output);
for(let source of source_globs) {
const source_list = glob(source);
for(let fname of source_list) {
const result = parse_source(fname);
const target = `${path.join(opts.output, fname)}.json`;
mkdir_to(target);
fs.writeFileSync(target, dump(result));
const name = fname.replaceAll("\\", "/").slice(2);
index[name] = result.exports.map(e => {
return {isa: e.isa, name: e.name};
});
}
}
// now write the grand index
const index_name = path.join(opts.output, "index.json");
fs.writeFileSync(index_name, dump(index));
process.exit(0);
}

Loading…
Cancel
Save