Added documentation on cli

This commit is contained in:
Kevin FEDYNA
2025-01-22 14:24:55 +01:00
parent 8a5461827e
commit 75a9591f6a
8 changed files with 175 additions and 94 deletions
+1 -1
View File
@@ -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<void> {
+2 -2
View File
@@ -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<State>) {
* return <h2>My super page!</h2>;
* }
*
*
* // Partial code that should be at each file's end.
* export const config = getPartialsConfig();
* export default makePartials(Page);
+10 -91
View File
@@ -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<string>) => void;
type CLIAsyncCommand = (...args: Array<string>) => Promise<void>;
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<string | number>,
cli: CLI,
): never | {
command: CLICommand | CLIAsyncCommand;
args: Array<string | number>;
} {
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);
+40
View File
@@ -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<string | number>,
cli: CLI,
): never | {
command: CLICommand | CLIAsyncCommand;
args: Array<string | number>;
} {
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 };
}
+70
View File
@@ -0,0 +1,70 @@
export type CLICommand = (...args: Array<string>) => void;
export type CLIAsyncCommand = (...args: Array<string>) => Promise<void>;
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(" ") ?? ""}`,
);
}
+14
View File
@@ -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()));
}
+35
View File
@@ -1,3 +1,7 @@
/**
* Creates a new module.
* @param name The module name.
*/
export async function createModule(name: string): Promise<void> {
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<void> {
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<void> {
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<void> {
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";
+3
View File
@@ -1,3 +1,6 @@
/**
* List all modules of PolyMPR.
*/
export async function listModules(): Promise<void> {
for await (const path of Deno.readDir("routes/(apps)")) {
if (path.isDirectory) {