mirror of
https://gitlab.com/soapbox-pub/ditto.git
synced 2025-12-06 11:29:46 +00:00
throw out commander in favour of homegrown argument parser
This commit is contained in:
parent
7b5cbdb525
commit
10cb65e60f
3 changed files with 184 additions and 9 deletions
|
|
@ -7,8 +7,7 @@
|
|||
"db:export": "deno run -A scripts/db-export.ts",
|
||||
"db:import": "deno run -A scripts/db-import.ts",
|
||||
"db:migrate": "deno run -A scripts/db-migrate.ts",
|
||||
"headless:setup": "deno run -A tribes/setup.ts",
|
||||
"headless:uploader-config": "deno run -A tribes/uploader-config.ts",
|
||||
"tribes-cli": "deno run -A tribes-cli/cli.ts",
|
||||
"nostr:pull": "deno run -A scripts/nostr-pull.ts",
|
||||
"debug": "deno run -A --inspect src/server.ts",
|
||||
"test": "deno test -A --junit-path=./deno-test.xml",
|
||||
|
|
@ -52,7 +51,6 @@
|
|||
"@std/streams": "jsr:@std/streams@^0.223.0",
|
||||
"comlink": "npm:comlink@^4.4.1",
|
||||
"comlink-async-generator": "npm:comlink-async-generator@^0.0.1",
|
||||
"commander": "npm:commander@12.1.0",
|
||||
"deno-safe-fetch/load": "https://gitlab.com/soapbox-pub/deno-safe-fetch/-/raw/v1.0.0/load.ts",
|
||||
"deno.json": "./deno.json",
|
||||
"entities": "npm:entities@^4.5.0",
|
||||
|
|
|
|||
16
deno.lock
generated
16
deno.lock
generated
|
|
@ -29,6 +29,7 @@
|
|||
"jsr:@soapbox/kysely-pglite@^0.0.1": "jsr:@soapbox/kysely-pglite@0.0.1",
|
||||
"jsr:@soapbox/stickynotes@^0.4.0": "jsr:@soapbox/stickynotes@0.4.0",
|
||||
"jsr:@std/assert@^0.213.1": "jsr:@std/assert@0.213.1",
|
||||
"jsr:@std/assert@^0.217.0": "jsr:@std/assert@0.217.0",
|
||||
"jsr:@std/assert@^0.223.0": "jsr:@std/assert@0.223.0",
|
||||
"jsr:@std/assert@^0.224.0": "jsr:@std/assert@0.224.0",
|
||||
"jsr:@std/assert@^0.225.1": "jsr:@std/assert@0.225.3",
|
||||
|
|
@ -68,7 +69,6 @@
|
|||
"npm:comlink-async-generator": "npm:comlink-async-generator@0.0.1",
|
||||
"npm:comlink-async-generator@^0.0.1": "npm:comlink-async-generator@0.0.1",
|
||||
"npm:comlink@^4.4.1": "npm:comlink@4.4.1",
|
||||
"npm:commander@12.1.0": "npm:commander@12.1.0",
|
||||
"npm:entities@^4.5.0": "npm:entities@4.5.0",
|
||||
"npm:fast-stable-stringify@^1.0.0": "npm:fast-stable-stringify@1.0.0",
|
||||
"npm:formdata-helper@^0.3.0": "npm:formdata-helper@0.3.0",
|
||||
|
|
@ -312,6 +312,9 @@
|
|||
"@std/assert@0.213.1": {
|
||||
"integrity": "24c28178b30c8e0782c18e8e94ea72b16282207569cdd10ffb9d1d26f2edebfe"
|
||||
},
|
||||
"@std/assert@0.217.0": {
|
||||
"integrity": "c98e279362ca6982d5285c3b89517b757c1e3477ee9f14eb2fdf80a45aaa9642"
|
||||
},
|
||||
"@std/assert@0.223.0": {
|
||||
"integrity": "eb8d6d879d76e1cc431205bd346ed4d88dc051c6366365b1af47034b0670be24"
|
||||
},
|
||||
|
|
@ -444,6 +447,12 @@
|
|||
"jsr:@std/assert@^0.213.1"
|
||||
]
|
||||
},
|
||||
"@std/path@0.217.0": {
|
||||
"integrity": "1217cc25534bca9a2f672d7fe7c6f356e4027df400c0e85c0ef3e4343bc67d11",
|
||||
"dependencies": [
|
||||
"jsr:@std/assert@^0.217.0"
|
||||
]
|
||||
},
|
||||
"@std/path@1.0.0-rc.1": {
|
||||
"integrity": "b8c00ae2f19106a6bb7cbf1ab9be52aa70de1605daeb2dbdc4f87a7cbaf10ff6"
|
||||
},
|
||||
|
|
@ -638,10 +647,6 @@
|
|||
"integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==",
|
||||
"dependencies": {}
|
||||
},
|
||||
"commander@12.1.0": {
|
||||
"integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==",
|
||||
"dependencies": {}
|
||||
},
|
||||
"cross-spawn@7.0.3": {
|
||||
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
|
||||
"dependencies": {
|
||||
|
|
@ -2030,7 +2035,6 @@
|
|||
"npm:@soapbox.pub/pglite@^0.2.10",
|
||||
"npm:comlink-async-generator@^0.0.1",
|
||||
"npm:comlink@^4.4.1",
|
||||
"npm:commander@12.1.0",
|
||||
"npm:entities@^4.5.0",
|
||||
"npm:fast-stable-stringify@^1.0.0",
|
||||
"npm:formdata-helper@^0.3.0",
|
||||
|
|
|
|||
173
tribes-cli/cli.ts
Normal file
173
tribes-cli/cli.ts
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
interface BaseArg {
|
||||
kind: 'string' | 'number' | 'bool';
|
||||
name: string;
|
||||
invert?: true;
|
||||
}
|
||||
|
||||
type Argument = BaseArg & { optional?: true };
|
||||
type Option = BaseArg & { short?: string };
|
||||
|
||||
interface BaseCommand {
|
||||
options: Option[];
|
||||
name: string;
|
||||
}
|
||||
|
||||
type Command =
|
||||
| (
|
||||
& BaseCommand
|
||||
& ({
|
||||
arguments: Argument[];
|
||||
} | {
|
||||
subcommands: Command[];
|
||||
})
|
||||
)
|
||||
| BaseCommand;
|
||||
|
||||
interface ParseResult {
|
||||
[key: string]: boolean | string | number | ParseResult;
|
||||
}
|
||||
|
||||
const parseArgs = (args: string[], config: Command) => {
|
||||
const result: ParseResult = {};
|
||||
|
||||
let i = 0;
|
||||
const opt = (kind: BaseArg['kind'], idx: number) => kind === 'number' ? Number(args[idx]) : args[idx];
|
||||
const doOption = (cfg: Option, skip = -1, blockLength = -1) => {
|
||||
if (cfg.kind === 'number' || cfg.kind === 'string') {
|
||||
if (skip === -1) {
|
||||
result[cfg.name] = opt(cfg.kind, i + 1);
|
||||
i += 1;
|
||||
} else {
|
||||
result[cfg.name] = opt(cfg.kind, i + skip);
|
||||
if (skip === blockLength) {
|
||||
i += skip;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
result[cfg.name] = !cfg.invert;
|
||||
}
|
||||
};
|
||||
|
||||
if ('arguments' in config && config.arguments.length) {
|
||||
const argumentCount = args.filter((arg) => !arg.startsWith('-')).length;
|
||||
const last = config.arguments.at(-1);
|
||||
const requiredCount = config.arguments.length - (last?.optional ? 1 : 0);
|
||||
if (argumentCount !== requiredCount) throw new Error('Invalid number of arguments passed!');
|
||||
}
|
||||
|
||||
let argumentsParsed = 0;
|
||||
for (i; i < args.length; i++) {
|
||||
const current = args[i];
|
||||
if (current[0] === '-') {
|
||||
if (current.startsWith('--')) {
|
||||
const opt = current.slice(2);
|
||||
const cfg = config.options.find((o) => o.name === opt);
|
||||
if (!cfg) continue;
|
||||
doOption(cfg);
|
||||
} else {
|
||||
const block = current.slice(1).split('');
|
||||
for (let idx = 0; idx < block.length; idx++) {
|
||||
const cfg = config.options.find((o) => o.short === block[idx]);
|
||||
if (!cfg) continue;
|
||||
doOption(cfg, idx + 1, block.length);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ('subcommands' in config) {
|
||||
const subcommand = config.subcommands.find((cmd) => cmd.name === current);
|
||||
if (subcommand) {
|
||||
result[subcommand.name] = parseArgs(args.slice(i + 1), subcommand);
|
||||
}
|
||||
} else if ('arguments' in config) {
|
||||
const arg = config.arguments[argumentsParsed++];
|
||||
if (arg.kind === 'bool') {
|
||||
if (args[i] !== 'true' && args[i] !== 'false') {
|
||||
throw new Error(`Non-boolean value passed for boolean argument ${arg.name}!`);
|
||||
}
|
||||
result[arg.name] = args[i] === 'true' ? true : false;
|
||||
} else if (arg.kind === 'string') {
|
||||
result[arg.name] = args[i];
|
||||
} else if (arg.kind === 'number') {
|
||||
result[arg.name] = Number(args[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* TODOs
|
||||
* - get instance list from dokku for pushing updates
|
||||
* - fix item duplication issues with colocated postgres
|
||||
* - make dokku work with existing postgres
|
||||
* - make new-tribe script work with custom domains
|
||||
* - get ssh key as -i argument
|
||||
* -
|
||||
*/
|
||||
|
||||
const res = parseArgs(Deno.args, {
|
||||
name: 'tribes-cli',
|
||||
options: [{
|
||||
kind: 'string',
|
||||
name: 'identity-file',
|
||||
short: 'i',
|
||||
}],
|
||||
subcommands: [{
|
||||
name: 'tribe',
|
||||
options: [{
|
||||
name: 'alice',
|
||||
short: 'a',
|
||||
kind: 'number',
|
||||
}, {
|
||||
name: 'bob',
|
||||
short: 'b',
|
||||
kind: 'string',
|
||||
}, {
|
||||
name: 'charlie',
|
||||
short: 'c',
|
||||
kind: 'number',
|
||||
}],
|
||||
subcommands: [{
|
||||
name: 'new',
|
||||
arguments: [{
|
||||
name: 'name',
|
||||
kind: 'string',
|
||||
}],
|
||||
options: [],
|
||||
}],
|
||||
}],
|
||||
});
|
||||
|
||||
console.log(res);
|
||||
|
||||
Deno.exit(1);
|
||||
/*
|
||||
if (import.meta.main) {
|
||||
const tribes =
|
||||
new Command('tribes-cli')
|
||||
.description('CLI for managing Ditto Tribes instances.')
|
||||
.version("0.0.1")
|
||||
.option('-i --identity-file <string>', 'Path to the ssh identity file.', defaultIdentityFile())
|
||||
.action(async args => {
|
||||
if (!args.identityFile) {
|
||||
throw new InvalidArgumentError(`Invalid or missing identity file ${args.identityFile || '<empty string>'}`);
|
||||
}
|
||||
try {
|
||||
console.log(args.identityFile);
|
||||
const file = await Deno.readTextFile(args.identityFile);
|
||||
sessionStorage.setItem('identity', file);
|
||||
console.log(`Read identity file from ${args.identityFile} successfully.`);
|
||||
}
|
||||
catch (e) {
|
||||
throw new InvalidArgumentError(`Error reading identity file: ${e.message}`);
|
||||
}
|
||||
})
|
||||
.showHelpAfterError();
|
||||
|
||||
[tribe, remote].forEach(cmd => tribes.addCommand(cmd));
|
||||
const foo = await tribes.parseAsync(Deno.args, { from: 'user' });
|
||||
console.log(foo);
|
||||
}
|
||||
|
||||
*/
|
||||
Loading…
Add table
Reference in a new issue