From 2ac767b858c8038801c41c2a15c48ccca1bd633c Mon Sep 17 00:00:00 2001 From: "Zed A. Shaw" Date: Sat, 17 Dec 2022 01:10:25 -0500 Subject: [PATCH] DocsBrowser is looking nice and I'm actually doing the docs for one file to work on the design more. --- admin/pages/DocsBrowser.svelte | 17 +++-- commands/codedoc.js | 23 ++++--- lib/docgen.js | 2 +- lib/ormish.js | 117 +++++++++++++++++++++++++-------- package.json | 3 +- 5 files changed, 118 insertions(+), 44 deletions(-) diff --git a/admin/pages/DocsBrowser.svelte b/admin/pages/DocsBrowser.svelte index 3ea813a..5f98139 100644 --- a/admin/pages/DocsBrowser.svelte +++ b/admin/pages/DocsBrowser.svelte @@ -77,6 +77,7 @@ } export > heading h4 { + font-family: var(--font-family); margin: 0px !important; } @@ -117,13 +118,17 @@ {#if docs_data} {#each docs_data.exports as exp} {#if exp.isa === "class"} -

Class: { exp.name }

+ + +

Class: { exp.name }

+ +
{#if exp.comment} {@html exp.comment} {/if} - +
{#each exp.methods as member} @@ -131,19 +136,18 @@

{ member.name }

{docs_data.source}:{ member.line_start } - { member.isa } + { member.isa } of { exp.name } {#if member.static}static{/if} {#if member.async}async{/if} {#if member.generator}generator{/if} - {#if exp.comment} + {#if member.comment} - { @html exp.comment } + { @html member.comment } {/if} -

Signature

@@ -166,7 +170,6 @@ {@html exp.comment} {/if} -

Signature

diff --git a/commands/codedoc.js b/commands/codedoc.js index 060b1d0..ba00f61 100644 --- a/commands/codedoc.js +++ b/commands/codedoc.js @@ -27,14 +27,21 @@ export const required = [ const RENDERER = create_renderer(); +/* + Strips 1 leading space from the comments, or the \s\* combinations + in traditional documentation comments. + + If we strip more it'll mess up formatting in markdown for indentation + formats. Weirdly Remarkable seems to be able to handle leading spaces pretty + well so only need to remove one space or \s\* combinations like with + traditional comment docs. +*/ const render_comment = (comment) => { - // strip 1 leading space from the comments, if we strip more it'll - // mess up formatting in markdown for indentation formats - const lines = comment.split(/\n/).map(l => l.replace(/^\s/, '')); + const lines = comment.split(/\n/).map(l => l.replace(/^(\s*\*\s?|\s)/, '')); return RENDERER.render(lines.join("\n")); } -// handy function for checking things are good and aborting +/* Handy function for checking things are good and aborting. */ const check = (test, fail_message) => { if(!test) { log.error(fail_message); @@ -61,8 +68,6 @@ class ParseWalker { methods: [], } - new_class.comment = this.find_comment(new_class.line_start); - acorn_walk.simple(root, { ClassDeclaration: (cls_node) => { assert(cls_node.id.name === new_class.name, "Name of class changed!"); @@ -159,13 +164,13 @@ class ParseWalker { } /** - * Find the nearest comment to this line, giving - * about 2 lines of slack. + Find the nearest comment to this line, giving + about 2 lines of slack. */ find_comment(line) { for(let c of this.comments) { const distance = c.end - line; - if(!c.found && distance < 0 && distance > -3) { + if(!c.found && distance == -1) { c.found = true; return render_comment(c.value); } diff --git a/lib/docgen.js b/lib/docgen.js index 1660194..f781a25 100644 --- a/lib/docgen.js +++ b/lib/docgen.js @@ -97,7 +97,7 @@ export const create_renderer = (toc) => { let level = tokens[idx].hLevel; let content = tokens[idx + 1].content; let slug = slugify(content, {lower: true, strict: true}); - toc.push({level, content, slug}); + if(toc) toc.push({level, content, slug}); return ``; } diff --git a/lib/ormish.js b/lib/ormish.js index d4ce3dd..3541c58 100644 --- a/lib/ormish.js +++ b/lib/ormish.js @@ -3,10 +3,18 @@ import knexConfig from 'knex'; import assert from 'assert'; import { attachPaginate } from 'knex-paginate'; +/* + A preconfigured knex.js driver using the config.development configuration + by default. + + _TODO_: Need to make this configurable, even though I just use one config right now since I run sqlite3 all the time. + */ export const knex = knexConfig(config.development); -// run the PERF_TRICKS to configure sqlite3 when thing start, really need to make this -// a configuration and only do it with sqlite3, but for now just get this done +/* + run the PERF_TRICKS to configure sqlite3 when thing start, really need to make this + a configuration and only do it with sqlite3, but for now just get this done + */ if(config.development.client === "sqlite3") { const PERF_TRICKS = [ "pragma journal_mode = WAL", // use a WAL journal to not block writers/readers @@ -25,6 +33,10 @@ if(config.development.client === "sqlite3") { attachPaginate(); +/* + Filled in by `load_schema` to give access to the database scheme in the admin + tool and generally through the API. + */ export const SCHEMA = {}; const load_schema = async () => { @@ -43,11 +55,11 @@ await load_schema(); is called by Model.validation and you can call it directly to get rules for a database table. - + `name string` - the table name. - + `rules Object` - default rules with empty "" for the rules you want filled in - + `all boolean` - set this to true if you want everything - + `no_id boolean` - defaults to true, set false if you also want the id - + `return Object` - the resulting rules to use with Validator + 1. `name string` - the table name. + 2. `rules Object` - default rules with empty "" for the rules you want filled in + 3. `all boolean` - set this to true if you want everything + 4. `no_id boolean` - defaults to true, set false if you also want the id + 5. `return Object` - the resulting rules to use with Validator */ export const validation = (name, rules, all=false, no_id=true) => { assert(rules, "rules parameter is required and will be modified"); @@ -99,20 +111,73 @@ export const validation = (name, rules, all=false, no_id=true) => { return rules; } +/* + The base class for all models found in `lib/models.js`. You use this by extending it with: + + ```javascript + class User extends Model.from_table('user') { + } + ``` + + This will create a `User` class that is automatically configured using the SCHEMA create from the `user` table in your database. You won't need to define the attributes on this class as it will be correctly populated from the database. + + The database is therefore the "source of truth" for all of the models. You can then add functions to extend what this class does. + */ export class Model { + + /* + Allows you to build a new object of this Model with the given `attr` + already set, but really you should use the `Model.from(attr)` method instead. + This does _no_ object sanitization with `Model.clean(attr)` method, and if + it doesn't match the underlying database it will throw an exception. + + - `attr Object` - the attributes for this model + */ constructor(attr) { assert(attr, "Must give attributes."); Object.assign(this, attr); } + /* + How to actually create a new instance of this model. This + will do two things: + + 1. Correctly use the schema for the subclass model. + 2. Sanitize the input to remove anything that shouldn't be in the database. + + The `also_remove` parameter is a list of additional keys to also scrub from the object. + + - `attr Object` -- The attributes this should start with. + - `also_remove Array` -- list of additional attributes to remove. + */ static from(attr, also_remove=undefined) { return new this(this.clean(attr, also_remove)); } + /* + Returns an object representing the schema for this Model. Remember that this + will reflect what's in the database schema, which is formatted however + `knex.js` formats your database Schema. Might not be portable between + databases and only tested with SQlite3. + + _This is an attribute accessor, so just do `obj.schema` rather than call it like a function._ + + - `return Object` - The schema for this model. + */ get schema() { return this.constructor.schema; } + /* + Uses the `this.schema` scrub out any attributes that are not valid for the + schema. This is effectively a whitelist for the allowed attributes based on + the database schema. You can use the `also_remove` parameter to list + additional attributes to remove, which you should do to sanitize incoming + objects for things like password fields. + + - `attr Object` - The attributes to clean. + - `also_remove Array` - Additional attributes to remove. + */ static clean(attr, also_remove=undefined) { assert(attr, "Must give attributes to clean."); @@ -128,18 +193,18 @@ export class Model { return this.constructor.table_name; } - /** - * Returns an object of basic rules meant for lib/api.js:validate - * based on what's in the database. It's meant to be an easy to - * pass in starter which you can augment. It expects a set of rules - * with keys you want configured. Any key that's set to an empty string "" - * will be filled in with a minimum rule to match the database schema. - * - * It's designed to be called once at the top of an api/ handler to get - * a basic set of rules. You could also run it to print out the rules then - * simply write the rules directly where you need them. - * - * @param rules {Object} - rules specifier + /* + Returns an object of basic rules meant for lib/api.js:validate + based on what's in the database. It's meant to be an easy to + pass in starter which you can augment. It expects a set of rules + with keys you want configured. Any key that's set to an empty string "" + will be filled in with a minimum rule to match the database schema. + + It's designed to be called once at the top of an api/ handler to get + a basic set of rules. You could also run it to print out the rules then + simply write the rules directly where you need them. + + - `param rules {Object}` - rules specifier */ static validation(rules) { return validation(this.table_name, rules); @@ -204,13 +269,13 @@ export class Model { return new this(attr); } - /** - * Implements an upsert (insert but update on conflict) for Postgres, MySQL, and SQLite3 only. - * - * @param { Object } attr - The attributes to insert or update. - * @param { string } conflict_key - The key that can cause a conflict then update. - * @param { boolean } merge - Defaults to true and will change the record. false will ignore and not update on conflict. - * @return { number } - id or undefined + /* + Implements an upsert (insert but update on conflict) for Postgres, MySQL, and SQLite3 only. + + + attr { Object } - The attributes to insert or update. + + conflict_key { string } - The key that can cause a conflict then update. + + merge { boolean } - Defaults to true and will change the record. false will ignore and not update on conflict. + + return { number } - id or undefined */ static async upsert(attr, conflict_key, merge=true) { assert(conflict_key !== undefined, `You forgot to set the conflict_key on upsert to table ${this.table_name}`); diff --git a/package.json b/package.json index 799ce88..d924879 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ }, "scripts": { "dev": "run-p -l -n api queue watch tracker rendered-watch", - "DANGER_ADMIN": "cross-env DANGER_ADMIN=1 run-p -l -n api queue tracker watch rendered-watch", + "DANGER_ADMIN": "cross-env DANGER_ADMIN=1 run-p -l -n api queue tracker watch rendered-watch docs", "prod": "run-p api queue", "tracker": "node ./bando.js tracker", "build": "node ./bando.js build --config build.prod.json", @@ -17,6 +17,7 @@ "api": "nodemon ./bando.js api", "queue": "nodemon ./bando.js queue", "test": "npx ava tests/**/*.js", + "docs": "nodemon bando.js codedoc --output public/docs/api lib/*.js client/*.js", "modules": "./bando.js load", "modules-watch": "nodemon --watch ../ljsthw-private/db/modules/ --ext .md,.js,.sh ./bando.js load", "rendered-watch": "nodemon --ignore \"rendered/build/**/*\" --watch ./rendered --watch static -e md,svelte,js,css ./bando.js rendered",