From 75a9591f6af923d8a125331ff6ded585042f26b8 Mon Sep 17 00:00:00 2001 From: Kevin FEDYNA Date: Wed, 22 Jan 2025 14:24:55 +0100 Subject: [PATCH] Added documentation on cli --- databases/ensure.ts | 2 +- defaults/makePartials.tsx | 4 +- toolbox/cli.ts | 101 ++++---------------------------------- toolbox/cli/command.ts | 40 +++++++++++++++ toolbox/cli/help.ts | 70 ++++++++++++++++++++++++++ toolbox/cli/main.ts | 14 ++++++ toolbox/module/create.ts | 35 +++++++++++++ toolbox/module/list.ts | 3 ++ 8 files changed, 175 insertions(+), 94 deletions(-) create mode 100644 toolbox/cli/command.ts create mode 100644 toolbox/cli/help.ts create mode 100644 toolbox/cli/main.ts diff --git a/databases/ensure.ts b/databases/ensure.ts index c1cde16..41431f5 100644 --- a/databases/ensure.ts +++ b/databases/ensure.ts @@ -5,7 +5,7 @@ import { Database } from "@db/sqlite"; * * Read all SQL files in init directory and create * associated SQLite database file. - * + * * **Must not be used out of statup use-case.** */ export default async function ensureDatabases(): Promise { diff --git a/defaults/makePartials.tsx b/defaults/makePartials.tsx index 5df6916..90dc7cf 100644 --- a/defaults/makePartials.tsx +++ b/defaults/makePartials.tsx @@ -16,14 +16,14 @@ export function getPartialsConfig(): RouteConfig { /** * Partialize the given page for optimized rendering. - * @param page The partial `Route` object to partialize. + * @param page The partial `Route` object to partialize. * @returns The partialized version of `page`. * @example * // Page defintion... * async function Page(_request: Request, context: FreshContext) { * return

My super page!

; * } - * + * * // Partial code that should be at each file's end. * export const config = getPartialsConfig(); * export default makePartials(Page); diff --git a/toolbox/cli.ts b/toolbox/cli.ts index 91f91f1..015616b 100644 --- a/toolbox/cli.ts +++ b/toolbox/cli.ts @@ -1,15 +1,17 @@ -import { parseArgs, type ParseOptions } from "@std/cli/parse-args"; +import { type ParseOptions } from "@std/cli/parse-args"; import { createModule } from "$root/toolbox/module/create.ts"; import { listModules } from "$root/toolbox/module/list.ts"; +import { CLI, displayHelp } from "$root/toolbox/cli/help.ts"; +import { main } from "$root/toolbox/cli/main.ts"; -type CLICommand = (...args: Array) => void; -type CLIAsyncCommand = (...args: Array) => Promise; -interface CLI { - [command: string]: CLI | CLICommand | CLIAsyncCommand; -} - +/** + * CLI will use `args._`, but you can define options for global CLI. + */ const argSpec: ParseOptions = {}; +/** + * Configure CLI commands here. + */ const cli: CLI = { help: () => displayHelp(cli), module: { @@ -18,87 +20,4 @@ const cli: CLI = { }, }; -function displayHelp( - cli: CLI | CLICommand | CLIAsyncCommand, - errorMessage?: string, -): never { - const loggingFunction = errorMessage ? console.error : console.log; - if (errorMessage) { - console.error(errorMessage); - } - - if (typeof cli == "function") { - loggingFunction("Usage:"); - displayFunctionHelp(cli, loggingFunction); - } else { - loggingFunction("Commands:"); - displayObjectHelp(cli, loggingFunction); - } - - Deno.exit(errorMessage ? 1 : 0); -} - -function displayObjectHelp( - cli: CLI, - loggingFunction: typeof console.log, - level: number = 1, -) { - for (const [key, value] of Object.entries(cli)) { - if (typeof value == "function") { - displayFunctionHelp(value, loggingFunction, level); - } else { - loggingFunction(`${" ".repeat(level * 2)}${key}`); - displayObjectHelp(value, loggingFunction, level + 1); - } - } -} - -function displayFunctionHelp( - cli: CLICommand | CLIAsyncCommand, - loggingFunction: typeof console.log, - level: number = 1, -) { - const command = cli.name; - const matched = cli.toString().match(/(?<=^\()[^\)]+(?=\))/); - const args = matched?.[0].split(",").map((arg) => `<${arg.trim()}>`); - loggingFunction( - `${" ".repeat(level * 2)}${command} ${args?.join(" ") ?? ""}`, - ); -} - -function runCommand( - commands: Array, - cli: CLI, -): never | { - command: CLICommand | CLIAsyncCommand; - args: Array; -} { - if (commands.length == 0) { - displayHelp(cli, `No command provided.`); - } - - const command = commands.shift()!.toString(); - - if (cli[command] == undefined) { - displayHelp(cli, `Command "${command}" doesn't exist.`); - } - - if (typeof cli[command] == "object") { - return runCommand(commands, cli[command]); - } - - if (cli[command].length != commands.length) { - displayHelp(cli[command], `Wrong usage of command "${command}".`); - } - - return { command: cli[command], args: commands }; -} - -function main() { - const argv = parseArgs(Deno.args, argSpec); - const { command, args } = runCommand(argv._, cli); - - command(...args.map((element) => element.toString())); -} - -main(); +main(cli, argSpec); diff --git a/toolbox/cli/command.ts b/toolbox/cli/command.ts new file mode 100644 index 0000000..d9dc830 --- /dev/null +++ b/toolbox/cli/command.ts @@ -0,0 +1,40 @@ +import { + CLI, + CLIAsyncCommand, + CLICommand, + displayHelp, +} from "$root/toolbox/cli/help.ts"; + +/** + * Run the command given by arguments. + * @param commands The given arguments. + * @param cli The CLI (sub-)configuration object. + * @returns The command or the help if commands are not valid. + */ +export function runCommand( + commands: Array, + cli: CLI, +): never | { + command: CLICommand | CLIAsyncCommand; + args: Array; +} { + if (commands.length == 0) { + displayHelp(cli, `No command provided.`); + } + + const command = commands.shift()!.toString(); + + if (cli[command] == undefined) { + displayHelp(cli, `Command "${command}" doesn't exist.`); + } + + if (typeof cli[command] == "object") { + return runCommand(commands, cli[command]); + } + + if (cli[command].length != commands.length) { + displayHelp(cli[command], `Wrong usage of command "${command}".`); + } + + return { command: cli[command], args: commands }; +} diff --git a/toolbox/cli/help.ts b/toolbox/cli/help.ts new file mode 100644 index 0000000..b906dd5 --- /dev/null +++ b/toolbox/cli/help.ts @@ -0,0 +1,70 @@ +export type CLICommand = (...args: Array) => void; +export type CLIAsyncCommand = (...args: Array) => Promise; +export interface CLI { + [command: string]: CLI | CLICommand | CLIAsyncCommand; +} + +/** + * Display the help message for the CLI. + * @param cli The CLI (sub-)configuration object. + * @param errorMessage The error message to display. + */ +export function displayHelp( + cli: CLI | CLICommand | CLIAsyncCommand, + errorMessage?: string, +): never { + const loggingFunction = errorMessage ? console.error : console.log; + if (errorMessage) { + console.error(errorMessage); + } + + if (typeof cli == "function") { + loggingFunction("Usage:"); + displayFunctionHelp(cli, loggingFunction); + } else { + loggingFunction("Commands:"); + displayObjectHelp(cli, loggingFunction); + } + + Deno.exit(errorMessage ? 1 : 0); +} + +/** + * Display object help recursivelly. + * @param cli The CLI (sub-)configuration object. + * @param loggingFunction The logging function to use. + * @param level The tab level. + */ +function displayObjectHelp( + cli: CLI, + loggingFunction: typeof console.log, + level: number = 1, +) { + for (const [key, value] of Object.entries(cli)) { + if (typeof value == "function") { + displayFunctionHelp(value, loggingFunction, level); + } else { + loggingFunction(`${" ".repeat(level * 2)}${key}`); + displayObjectHelp(value, loggingFunction, level + 1); + } + } +} + +/** + * Display function help. + * @param cli The CLI function. + * @param loggingFunction The logging function to use. + * @param level The tab level. + */ +function displayFunctionHelp( + cli: CLICommand | CLIAsyncCommand, + loggingFunction: typeof console.log, + level: number = 1, +) { + const command = cli.name; + const matched = cli.toString().match(/(?<=^\()[^\)]+(?=\))/); + const args = matched?.[0].split(",").map((arg) => `<${arg.trim()}>`); + loggingFunction( + `${" ".repeat(level * 2)}${command} ${args?.join(" ") ?? ""}`, + ); +} diff --git a/toolbox/cli/main.ts b/toolbox/cli/main.ts new file mode 100644 index 0000000..7e57c63 --- /dev/null +++ b/toolbox/cli/main.ts @@ -0,0 +1,14 @@ +import { parseArgs, ParseOptions } from "@std/cli/parse-args"; +import { runCommand } from "$root/toolbox/cli/command.ts"; +import { CLI } from "$root/toolbox/cli/help.ts"; + +/** + * Runs the CLI. + * @param cli The CLI configuration object. + * @param argSpec The Parse options for args. + */ +export function main(cli: CLI, argSpec: ParseOptions) { + const argv = parseArgs(Deno.args, argSpec); + const { command, args } = runCommand(argv._, cli); + command(...args.map((element) => element.toString())); +} diff --git a/toolbox/module/create.ts b/toolbox/module/create.ts index aa93463..1647665 100644 --- a/toolbox/module/create.ts +++ b/toolbox/module/create.ts @@ -1,3 +1,7 @@ +/** + * Creates a new module. + * @param name The module name. + */ export async function createModule(name: string): Promise { if (!name.match(/^[a-zA-Z0-9](?:(?:\-(?!\-))?[a-zA-Z0-9]*)*[a-zA-Z0-9]$/)) { console.error("Module names must be in kebab case."); @@ -58,16 +62,32 @@ export async function createModule(name: string): Promise { formatter.output(); } +/** + * Creates a new file at given path. + * @param path The file path. + * @param content The file content. + * @returns The creation promise. + */ function createFile(path: string, content: string): Promise { console.log(`Creating file ${path}...`); return Deno.writeTextFile(path, content); } +/** + * Creates a new directory at given path. + * @param path The directory path. + * @returns The creation promise. + */ function createDir(path: string): Promise { console.log(`Creating directory ${path}...`); return Deno.mkdir(path); } +/** + * Create the index content. + * @param _name The module capitalized name. + * @returns The index content. + */ function getIndexContent(_name: string) { return ` import makeIndex from "$root/defaults/makeIndex.ts"; @@ -75,6 +95,11 @@ function getIndexContent(_name: string) { `; } +/** + * Create the partials index content. + * @param name The module capitalized name. + * @returns The partials index content. + */ function getPartialIndexContent(name: string) { return ` import { @@ -93,6 +118,11 @@ function getPartialIndexContent(name: string) { `; } +/** + * Create the props content. + * @param name The module capitalized name. + * @returns The props content. + */ function getPropsContent(name: string) { return ` import { AppProperties } from "$root/defaults/interfaces.ts"; @@ -111,6 +141,11 @@ function getPropsContent(name: string) { `; } +/** + * Create the API example content. + * @param _name The module capitalized name. + * @returns The API example content. + */ function getApiExampleContent(_name: string) { return ` import { Handlers } from "$fresh/server.ts"; diff --git a/toolbox/module/list.ts b/toolbox/module/list.ts index a348047..258e1a5 100644 --- a/toolbox/module/list.ts +++ b/toolbox/module/list.ts @@ -1,3 +1,6 @@ +/** + * List all modules of PolyMPR. + */ export async function listModules(): Promise { for await (const path of Deno.readDir("routes/(apps)")) { if (path.isDirectory) {