diff --git a/tribes-cli/utils/command.ts b/tribes-cli/utils/command.ts new file mode 100644 index 00000000..6ea1ec63 --- /dev/null +++ b/tribes-cli/utils/command.ts @@ -0,0 +1,55 @@ +import { Option, ParsedArgs } from './mod.ts'; + +export class Command { + name: string; + description: string; + action: (args: ParsedArgs) => void | Promise = (_) => {}; + options: Record = {}; + commands: Record = {}; + + constructor(name: string, description: string) { + this.name = name; + this.description = description; + } + + get subcommandCount() { + return Object.keys(this.commands).length; + } + + isValidSubcommand(key: string): boolean { + return Object.keys(this.commands).includes(key.toString()); + } + + getSubcommand(subcommand: string | number) { + if (!subcommand) { + throw new Error('tribes-cli: error: invalid subcommand'); + } + if (typeof subcommand === 'number') { + throw new Error('tribes-cli: error: subcommand cannot be a number'); + } + + if (this.isValidSubcommand(subcommand)) { + return this.commands[subcommand]; + } else { + throw new Error(`tribes-cli: error: ${subcommand} is not a valid subcommand`); + } + } + + setAction(action: Command['action']) { + this.action = action; + return this; + } + + subcommand(name: string, command: Command) { + if (this.isValidSubcommand(name)) { + throw new Error(`tribes-cli: error: ${name} is already a subcommand.`); + } + this.commands[name] = command; + return this; + } + + option(fmt: string, option: Option) { + this.options[fmt] = option; + return this; + } +} diff --git a/tribes-cli/utils/mod.ts b/tribes-cli/utils/mod.ts new file mode 100644 index 00000000..9efdb3fd --- /dev/null +++ b/tribes-cli/utils/mod.ts @@ -0,0 +1,31 @@ +import { join } from '@std/path'; +import { ParsedArgs, setupCli } from './parsing.ts'; +import { Command } from './command.ts'; + +export const defaultIdentityFile = async () => { + const home = Deno.env.get('HOME'); + if (!home) throw new Error('tribes-cli: unable to find default identity file'); + const path = join(home, '.ssh', 'id_rsa'); + try { + await Deno.stat(path); + return path; + } catch {} + + throw new Error('tribes-cli: unable to find default identity file.'); +}; + +export type Option = + & { description: string } + & ({ + default?: string; + } | { + bool: true; + default?: boolean; + }); + +export const cleanArg = (arg: string) => { + return arg.replace(/^--?/g, ''); +}; + +export { Command, setupCli }; +export type { ParsedArgs }; diff --git a/tribes-cli/utils.ts b/tribes-cli/utils/parsing.ts similarity index 60% rename from tribes-cli/utils.ts rename to tribes-cli/utils/parsing.ts index 3975ab7f..87dc6b96 100644 --- a/tribes-cli/utils.ts +++ b/tribes-cli/utils/parsing.ts @@ -1,33 +1,21 @@ -import { join } from '@std/path'; +import { parseArgs as stdParseArgs } from '@std/cli'; +import { cleanArg, Command } from './mod.ts'; -let identity = ''; -export const defaultIdentityFile = async () => { - if (!identity) { - const home = Deno.env.get('HOME'); - if (home) return identity = await Deno.readTextFile(join(home, '.ssh', 'id_rsa')); +export const setupCli = (commands: Command, parserArgs: Partial = {}) => { + for (const [_name, body] of Object.entries(commands)) { + for (const subcommand in body) { + const s = body[subcommand]; + parserArgs = parseSubcommand(s, parserArgs); + } } - return ''; + + const parsed = stdParseArgs(Deno.args, parserArgs); + return { + parsed, + }; }; -export interface Subcommand { - action: Function; - description: string; - options?: Record< - string, - & { description: string } - & ({ - default?: string; - } | { - bool: true; - default?: boolean; - }) - >; - commands?: Record; -} - -export const cleanArg = (arg: string) => { - return arg.replace(/^--?/g, ''); -}; +export type ParsedArgs = ReturnType['parsed']; export interface ParsedSubcommand { string: string[]; @@ -45,7 +33,7 @@ export const defaultParsedSubcommand = (): ParsedSubcommand => { }; }; -export const parseSubcommand = (command: Subcommand, existing?: Partial): ParsedSubcommand => { +export const parseSubcommand = (command: Command, existing?: Partial): ParsedSubcommand => { const res = Object.assign(defaultParsedSubcommand(), existing); if (command.options) {