Skip to content

Commit

Permalink
[feat] made flags easier to handle on the client side
Browse files Browse the repository at this point in the history
  • Loading branch information
aricart committed Apr 7, 2021
1 parent 55f1afa commit 46ea485
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 34 deletions.
15 changes: 10 additions & 5 deletions main.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import { cli } from "./mod.ts";

const root = cli("greeting");
// create the root command
const root = cli({ use: "greeting (hello|goodbye) [--name name] [--strong]" });
// add a subcommand
const hello = root.addCommand({
use: "hello --name string [--strong]",
short: "says hello",
// this is the handler for the command, you get
// the command being executed, any args following a `--`
// and an object to let you access relevant flags.
run: (cmd, args, flags): Promise<number> => {
const strong = (flags.get("strong")?.value ?? false) ? "!!!" : "";
const n = flags.get("name")?.value ?? "mystery person";
const strong = (flags.value<boolean>("strong") ?? false) ? "!!!" : "";
const n = flags.value<string>("name") ?? "mystery person";
console.log(`hello ${n}${strong}`);
return Promise.resolve(0);
},
Expand All @@ -29,8 +34,8 @@ const goodbye = root.addCommand({
use: "goodbye --name string [--strong]",
short: "says goodbye",
run: (cmd, args, flags): Promise<number> => {
const strong = (flags.get("strong")?.value ?? false) ? "!!!" : "";
const n = flags.get("name")?.value ?? "mystery person";
const strong = flags.value<boolean>("strong") ? "!!!" : "";
const n = flags.value<string>("name") ?? "mystery person";
console.log(`goodbye ${n}${strong}`);
return Promise.resolve(0);
},
Expand Down
95 changes: 72 additions & 23 deletions mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { sprintf } from "https://deno.land/std/fmt/printf.ts";
export type Run = (
cmd: Command,
args: string[],
flags: Map<string, Flag>,
flags: Flags,
) => Promise<number>;

export interface Cmd {
Expand Down Expand Up @@ -44,17 +44,70 @@ function flag(f: Partial<Flag>): Flag {
return Object.assign(d, f);
}

function flagMap(flags: Flag[]): Map<string, Flag> {
const m = new Map<string, Flag>();
flags.forEach((f) => {
if (f.name) {
m.set(f.name, f);
export interface Flags {
value<T>(n: string): T;
values<T>(n: string): T[];
getFlag(n: string): Flag | null;
}

export class FlagsImpl implements Flags {
m: Map<string, Flag>;

constructor(flags: Flag[]) {
this.m = new Map<string, Flag>();
flags.forEach((f) => {
if (f.name) {
this.m.set(f.name, f);
}
if (f.short) {
this.m.set(f.short, f);
}
});
}

getFlag(n: string): Flag | null {
const f = this.m.get(n);
if (f === undefined) {
return null;
}
if (f.short) {
m.set(f.short, f);
return f;
}

defaultValue<T = unknown>(f: Flag): T {
let t;
if (f.type === "string") {
t = "";
} else if (f.type === "boolean") {
t = false;
} else if (f.type === "number") {
t = 0;
}
});
return m;
return t as unknown as T;
}

value<T = unknown>(n: string): T {
const f = this.m.get(n);
if (!f) {
throw new Error("unknown flag ${n}");
}
let v = f.value ?? f.default ?? this.defaultValue(f);
if (Array.isArray(v)) {
v = v[0];
}
return v as T;
}

values<T = unknown>(n: string): T[] {
const f = this.m.get(n);
if (!f) {
throw new Error("unknown flag ${n}");
}
let v = f.value ?? f.default ?? this.defaultValue(f);
if (!Array.isArray(v)) {
v = [v];
}
return v as T[];
}
}

export class Command implements Cmd {
Expand Down Expand Up @@ -188,7 +241,7 @@ export class Command implements Cmd {
return this.parent.getFlag(name);
}

run(cmd: Command, args: string[], flags: Map<string, Flag>): Promise<number> {
run(cmd: Command, args: string[], flags: Flags): Promise<number> {
return this.cmd.run(cmd, args, flags);
}

Expand Down Expand Up @@ -235,7 +288,7 @@ export class RootCommand extends Command implements Execute {
lastCmd!: {
cmd: Command;
args: string[];
flags: Map<string, Flag>;
flags: Flags;
helped?: boolean;
};
_help: Flag;
Expand Down Expand Up @@ -289,11 +342,11 @@ export class RootCommand extends Command implements Execute {

execute(args: string[] = Deno.args): Promise<number> {
const [cmd, a] = this.matchCmd(args);
const opts = cmd.getFlags();
const flags = cmd.getFlags();

// deno-lint-ignore no-explicit-any
const parseOpts = { "--": true } as any;
opts.forEach((f) => {
flags.forEach((f) => {
const key = f.short.length ? f.short : f.name;

if (f.short && f.name) {
Expand All @@ -316,8 +369,8 @@ export class RootCommand extends Command implements Execute {
const argv = parse(args, parseOpts);
argv._ = a;

const cf: Flag[] = [];
opts.forEach((f) => {
flags.forEach((f) => {
f.changed = false;
const key = f.short.length ? f.short : f.name;
if (argv[key]) {
f.value = argv[key];
Expand All @@ -326,11 +379,11 @@ export class RootCommand extends Command implements Execute {
} else {
f.changed = true;
}
cf.push(f);
}
});

const fm = flagMap(cf);
const fm = new FlagsImpl(flags);

this.lastCmd = { cmd: cmd, args: a, flags: fm };

if (this._help.value) {
Expand All @@ -350,7 +403,7 @@ export function cli(opts: Partial<Cmd>): RootCommand {
if (opts.run) {
const orig = opts.run;
opts.run = (cmd, args, flags): Promise<number> => {
const h = flags.get("help");
const h = flags.getFlag("help");
if (h && h.value) {
cmd.help();
return Promise.resolve(1);
Expand Down Expand Up @@ -389,10 +442,6 @@ export function calcPad(
return { short: s[0].length, long: s[1].length };
}

export function argNames(f: Partial<Flag>): [string?, string?] {
return [f.short, f.name];
}

export function flagHelp(
f: Partial<Flag>,
pad: { short: number; long: number },
Expand Down
12 changes: 6 additions & 6 deletions test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import {
assertEquals,
assertThrows,
} from "https://deno.land/std/testing/asserts.ts";
import { cli, Cmd, Command, Flag } from "./mod.ts";
import { cli, Cmd, Command, Flag, Flags } from "./mod.ts";

export function buildCmd(v: Partial<Cmd>, debug = false): Cmd {
const d = {
run: (
cmd: Command,
args: string[],
flags: Map<string, Flag>,
flags: Flags,
): Promise<number> => {
if (debug) {
console.info(cmd.name, args, flags);
Expand Down Expand Up @@ -76,9 +76,9 @@ Deno.test("matches nested commands", async () => {

const rv = await root.execute(["-S", "short flag"]);
assertEquals(rv, 0);
assert(root.lastCmd.flags.get("S"));
assertEquals(root.lastCmd.flags.get("S")!.value, "short flag");
assertEquals(root.lastCmd.flags.get("long-flag")!.value, "short flag");
assert(root.lastCmd.flags.getFlag("S"));
assertEquals(root.lastCmd.flags.value<string>("S"), "short flag");
assertEquals(root.lastCmd.flags.value<string>("long-flag"), "short flag");
});

Deno.test("nested command will get help", async () => {
Expand All @@ -87,7 +87,7 @@ Deno.test("nested command will get help", async () => {
const rv = await root.execute(["a", "--help"]);
assertEquals(rv, 1);
assertEquals(root.lastCmd.cmd.name, "a");
assertEquals(root.lastCmd.flags.get("help")!.value, true);
assertEquals(root.lastCmd.flags.value<boolean>("help"), true);
});

Deno.test("command needs use", () => {
Expand Down

0 comments on commit 46ea485

Please sign in to comment.