diff --git a/deno.json b/deno.json index 7c0fd6e..e9dcaca 100644 --- a/deno.json +++ b/deno.json @@ -3,6 +3,7 @@ "tasks": { "check": "deno fmt --check && deno lint && deno check **/*.ts && deno check **/*.tsx", "cli": "echo \"import '\\$fresh/src/dev/cli.ts'\" | deno run --unstable -A -", + "pmpr": "deno run -A toolbox/cli.ts", "manifest": "deno task cli manifest $(pwd)", "start": "deno run -A --unstable-ffi --watch=static/,routes/ dev.ts", "build": "deno run -A --unstable-ffi dev.ts build", diff --git a/fresh.gen.ts b/fresh.gen.ts index 62756f9..fbb4408 100644 --- a/fresh.gen.ts +++ b/fresh.gen.ts @@ -5,6 +5,7 @@ import * as $_apps_layout from "./routes/(apps)/_layout.tsx"; import * as $_apps_mobility_index from "./routes/(apps)/mobility/index.tsx"; import * as $_apps_mobility_partials_index from "./routes/(apps)/mobility/partials/index.tsx"; +import * as $_apps_notes_api_example from "./routes/(apps)/notes/api/example.ts"; import * as $_apps_notes_index from "./routes/(apps)/notes/index.tsx"; import * as $_apps_notes_partials_admin_courses from "./routes/(apps)/notes/partials/(admin)/courses.tsx"; import * as $_apps_notes_partials_admin_students from "./routes/(apps)/notes/partials/(admin)/students.tsx"; @@ -28,6 +29,7 @@ const manifest = { "./routes/(apps)/mobility/index.tsx": $_apps_mobility_index, "./routes/(apps)/mobility/partials/index.tsx": $_apps_mobility_partials_index, + "./routes/(apps)/notes/api/example.ts": $_apps_notes_api_example, "./routes/(apps)/notes/index.tsx": $_apps_notes_index, "./routes/(apps)/notes/partials/(admin)/courses.tsx": $_apps_notes_partials_admin_courses, diff --git a/routes/(apps)/notes/api/example.ts b/routes/(apps)/notes/api/example.ts new file mode 100644 index 0000000..68399cd --- /dev/null +++ b/routes/(apps)/notes/api/example.ts @@ -0,0 +1,10 @@ +import { Handlers } from "$fresh/server.ts"; + +export const handler: Handlers = { + async GET(request, context) { + return new Response({ + test: await request.json(), + context, + }); + }, +}; diff --git a/toolbox/cli.ts b/toolbox/cli.ts index 4b23d84..91f91f1 100644 --- a/toolbox/cli.ts +++ b/toolbox/cli.ts @@ -1,8 +1,11 @@ import { parseArgs, type ParseOptions } from "@std/cli/parse-args"; -import { createModule } from "$root/toolbox/module.ts"; +import { createModule } from "$root/toolbox/module/create.ts"; +import { listModules } from "$root/toolbox/module/list.ts"; +type CLICommand = (...args: Array) => void; +type CLIAsyncCommand = (...args: Array) => Promise; interface CLI { - [command: string]: CLI | (() => void) | (() => Promise); + [command: string]: CLI | CLICommand | CLIAsyncCommand; } const argSpec: ParseOptions = {}; @@ -10,51 +13,92 @@ const argSpec: ParseOptions = {}; const cli: CLI = { help: () => displayHelp(cli), module: { - create: createModule, + list: () => listModules(), + create: (name: string) => createModule(name), }, }; -function displayHelp(cli: CLI, errorMessage?: string): never { +function displayHelp( + cli: CLI | CLICommand | CLIAsyncCommand, + errorMessage?: string, +): never { const loggingFunction = errorMessage ? console.error : console.log; if (errorMessage) { console.error(errorMessage); } - loggingFunction("Commands:"); + if (typeof cli == "function") { + loggingFunction("Usage:"); + displayFunctionHelp(cli, loggingFunction); + } else { + loggingFunction("Commands:"); + displayObjectHelp(cli, loggingFunction); + } + Deno.exit(errorMessage ? 1 : 0); } -function runCommand(commands: Array, cli: CLI): never | void | Promise { +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) { - console.error( - `No command provided. Available commands are ${ - JSON.stringify(Object.keys(cli)) - }.`, - ); - Deno.exit(1); + displayHelp(cli, `No command provided.`); } const command = commands.shift()!.toString(); if (cli[command] == undefined) { - console.error( - `Command "${command}" doesn't exist. Available commands are ${ - JSON.stringify(Object.keys(cli)) - }.`, - ); - Deno.exit(1); + displayHelp(cli, `Command "${command}" doesn't exist.`); } if (typeof cli[command] == "object") { return runCommand(commands, cli[command]); } - return 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 args = parseArgs(Deno.args, argSpec); - runCommand(args._, cli); + const argv = parseArgs(Deno.args, argSpec); + const { command, args } = runCommand(argv._, cli); + + command(...args.map((element) => element.toString())); } main(); diff --git a/toolbox/module.ts b/toolbox/module.ts deleted file mode 100644 index 6d8f455..0000000 --- a/toolbox/module.ts +++ /dev/null @@ -1,3 +0,0 @@ -export async function createModule() { - -} \ No newline at end of file diff --git a/toolbox/module/create.ts b/toolbox/module/create.ts new file mode 100644 index 0000000..0d9f2b0 --- /dev/null +++ b/toolbox/module/create.ts @@ -0,0 +1,125 @@ +export async function createModule(name: string): Promise { + console.log(`Checking for module ${name}...`); + + try { + await Deno.mkdir(`routes/(apps)/${name}`); + } catch (error) { + if (!(error instanceof Deno.errors.AlreadyExists)) { + throw error; + } + + console.error(`Some module is already named ${name}, aborting.`); + Deno.exit(1); + } + + const capitalizedName = `${name[0].toUpperCase()}${ + name.substring(1).toLowerCase() + }`; + + Promise.allSettled([ + createDir(`routes/(apps)/${name}/(_props)`), + createDir(`routes/(apps)/${name}/partials`), + createDir(`routes/(apps)/${name}/(_islands)`), + createDir(`routes/(apps)/${name}/(_components)`), + createDir(`routes/(apps)/${name}/api`), + ]); + + Promise.allSettled([ + createFile( + `routes/(apps)/${name}/index.tsx`, + getIndexContent(capitalizedName), + ), + createFile( + `routes/(apps)/${name}/(_props)/props.ts`, + getPropsContent(capitalizedName), + ), + createFile( + `routes/(apps)/${name}/partials/index.tsx`, + getPartialIndexContent(capitalizedName), + ), + createFile( + `routes/(apps)/${name}/api/example.ts`, + getApiExampleContent(capitalizedName), + ), + ]); + + const formatter = new Deno.Command(Deno.execPath(), { + args: [ + "fmt", + `routes/(apps)/${name}`, + ], + }); + formatter.output(); +} + +function createFile(path: string, content: string): Promise { + console.log(`Creating file ${path}...`); + return Deno.writeTextFile(path, content); +} + +function createDir(path: string): Promise { + console.log(`Creating directory ${path}...`); + return Deno.mkdir(path); +} + +function getIndexContent(_name: string) { + return ` + import makeIndex from "$root/defaults/makeIndex.ts"; + export default makeIndex(import.meta.dirname!); + `; +} + +function getPartialIndexContent(name: string) { + return ` + import { EmptyObject } from "$root/defaults/interfaces.ts"; + import { + getPartialsConfig, + makePartials, + } from "$root/defaults/makePartials.tsx"; + + type ${name}IndexProps = EmptyObject; + + export function Index(_props: ${name}IndexProps) { + return

Welcome to ${name}.

; + } + + export const config = getPartialsConfig(); + export default makePartials(Index); + `; +} + +function getPropsContent(name: string) { + return ` + import { AppProperties } from "$root/defaults/interfaces.ts"; + + const properties: AppProperties = { + name: "${name}", + icon: "school", + pages: { + index: "Homepage", + }, + adminOnly: [], + hint: "PolyMPR module", + }; + + export default properties; + `; +} + +function getApiExampleContent(name: string) { + return ` + import { AppProperties } from "$root/defaults/interfaces.ts"; + + const properties: AppProperties = { + name: "${name}", + icon: "school", + pages: { + index: "Homepage", + }, + adminOnly: [], + hint: "PolyMPR module", + }; + + export default properties; + `; +} diff --git a/toolbox/module/list.ts b/toolbox/module/list.ts new file mode 100644 index 0000000..ce38149 --- /dev/null +++ b/toolbox/module/list.ts @@ -0,0 +1,7 @@ +export async function listModules(): Promise { + for await (const path of Deno.readDir("routes/(apps)")) { + if (path.isDirectory) { + console.log(path.name); + } + } +} \ No newline at end of file