Skip to content

Commit

Permalink
feat(command): add values method to types to show possible values in…
Browse files Browse the repository at this point in the history
… help text (#202)
  • Loading branch information
c4spar committed May 19, 2021
1 parent 36289a2 commit 143eb1b
Show file tree
Hide file tree
Showing 9 changed files with 72 additions and 35 deletions.
23 changes: 14 additions & 9 deletions command/command.ts
Expand Up @@ -581,12 +581,17 @@ export class Command<

this.cmd.types.set(name, { ...options, name, handler });

if (handler instanceof Type && typeof handler.complete !== "undefined") {
this.complete(
name,
(cmd, parent?: Command) => handler.complete?.(cmd, parent) || [],
options,
);
if (
handler instanceof Type &&
(typeof handler.complete !== "undefined" ||
typeof handler.values !== "undefined")
) {
const completeHandler: ICompleteHandler = (
cmd: Command,
parent?: Command,
) =>
handler.complete?.(cmd, parent) || handler.values?.(cmd, parent) || [];
this.complete(name, completeHandler, options);
}

return this;
Expand Down Expand Up @@ -1703,23 +1708,23 @@ export class Command<
* Get type by name.
* @param name Name of the type.
*/
protected getType(name: string): IType | undefined {
public getType(name: string): IType | undefined {
return this.getBaseType(name) ?? this.getGlobalType(name);
}

/**
* Get base type by name.
* @param name Name of the type.
*/
protected getBaseType(name: string): IType | undefined {
public getBaseType(name: string): IType | undefined {
return this.types.get(name);
}

/**
* Get global type by name.
* @param name Name of the type.
*/
protected getGlobalType(name: string): IType | undefined {
public getGlobalType(name: string): IType | undefined {
if (!this._parent) {
return;
}
Expand Down
25 changes: 18 additions & 7 deletions command/help/_help_generator.ts
Expand Up @@ -14,6 +14,7 @@ import {
} from "../deps.ts";
import type { IArgument } from "../types.ts";
import type { IEnvVar, IExample, IOption } from "../types.ts";
import { Type } from "../type.ts";

export interface HelpOptions {
types?: boolean;
Expand Down Expand Up @@ -238,15 +239,25 @@ export class HelpGenerator {
red(bold(`Conflicts: `)) +
italic(option.conflicts.map(getFlag).join(", ")),
);
(Array.isArray(option.value) || option.value instanceof RegExp) &&

let possibleValues: Array<string | number | RegExp> | undefined;
if (option.value instanceof RegExp) {
possibleValues = [option.value];
} else if (Array.isArray(option.value)) {
possibleValues = option.value;
} else {
const type = this.cmd.getType(option.args[0]?.type)?.handler;
if (type instanceof Type) {
possibleValues = type.values?.(this.cmd, this.cmd.getParent());
}
}

if (possibleValues) {
hints.push(
bold(`Possible values: `) +
italic(
(Array.isArray(option.value)
? option.value
: [option.value.toString()]).join(", "),
),
bold(`Values: `) +
possibleValues.map((value: unknown) => inspect(value)).join(", "),
);
}

if (hints.length) {
return `(${hints.join(", ")})`;
Expand Down
10 changes: 5 additions & 5 deletions command/test/integration/fixtures/help_command.out
Expand Up @@ -8,11 +8,11 @@

Options:

-h, --help - Show this help.
-V, --version - Show the version number for this program.
-g, --global <val> - Foo option.
-m, --main <val> - Bar option.
-c, --color <val> - Color option.
-h, --help - Show this help.
-V, --version - Show the version number for this program.
-g, --global <val> - Foo option.
-m, --main <val> - Bar option.
-c, --color <val> - Color option. (Values: "blue", "yellow", "red")

Commands:

Expand Down
10 changes: 5 additions & 5 deletions command/test/integration/fixtures/help_option_long.out
Expand Up @@ -8,11 +8,11 @@

Options:

-h, --help - Show this help.
-V, --version - Show the version number for this program.
-g, --global <val> - Foo option.
-m, --main <val> - Bar option.
-c, --color <val> - Color option.
-h, --help - Show this help.
-V, --version - Show the version number for this program.
-g, --global <val> - Foo option.
-m, --main <val> - Bar option.
-c, --color <val> - Color option. (Values: "blue", "yellow", "red")

Commands:

Expand Down
10 changes: 5 additions & 5 deletions command/test/integration/fixtures/help_option_short.out
Expand Up @@ -8,11 +8,11 @@

Options:

-h, --help - Show this help.
-V, --version - Show the version number for this program.
-g, --global <val> - Foo option.
-m, --main <val> - Bar option.
-c, --color <val> - Color option.
-h, --help - Show this help.
-V, --version - Show the version number for this program.
-g, --global <val> - Foo option.
-m, --main <val> - Bar option.
-c, --color <val> - Color option. (Values: "blue", "yellow", "red")

Commands:

Expand Down
4 changes: 2 additions & 2 deletions command/test/integration/test.ts
@@ -1,4 +1,4 @@
import { assertEquals, dirname, expandGlob } from "../../../dev_deps.ts";
import { assertEquals, dirname, expandGlob, lt } from "../../../dev_deps.ts";

const baseDir = `${dirname(import.meta.url).replace("file://", "")}`;

Expand All @@ -8,7 +8,7 @@ for await (const file of expandGlob(`${baseDir}/fixtures/*.in`)) {
const outPath = file.path.replace(/\.in$/, ".out");
Deno.test({
name: `command - integration - ${name}`,
ignore: Deno.build.os === "windows",
ignore: Deno.build.os === "windows" || lt(Deno.version.deno, "1.4.1"),
async fn() {
const [cmd, expected] = await Promise.all([
Deno.readTextFile(file.path),
Expand Down
21 changes: 20 additions & 1 deletion command/type.ts
@@ -1,5 +1,9 @@
import type { Command } from "./command.ts";
import type { CompleteHandlerResult, ITypeInfo } from "./types.ts";
import type {
CompleteHandlerResult,
ITypeInfo,
ValuesHandlerResult,
} from "./types.ts";

/**
* Base class for custom types.
Expand All @@ -26,6 +30,21 @@ import type { CompleteHandlerResult, ITypeInfo } from "./types.ts";
export abstract class Type<T> {
public abstract parse(type: ITypeInfo): T;

/**
* Returns values displayed in help text. If no complete method is provided,
* these values are also used for shell completions.
*/
public values?(
// deno-lint-ignore no-explicit-any
cmd: Command<any, any, any, any, any>,
// deno-lint-ignore no-explicit-any
parent?: Command<any, any, any, any, any>,
): ValuesHandlerResult;

/**
* Returns shell completion values. If no complete method is provided,
* values from the values method are used.
*/
public complete?(
// deno-lint-ignore no-explicit-any
cmd: Command<any, any, any, any, any>,
Expand Down
2 changes: 2 additions & 0 deletions command/types.ts
Expand Up @@ -194,6 +194,8 @@ export type CompleteHandlerResult =
| Array<string | number>
| Promise<Array<string | number>>;

export type ValuesHandlerResult = Array<string | number>;

/** Type parser method. */
export type ICompleteHandler<
// deno-lint-ignore no-explicit-any
Expand Down
2 changes: 1 addition & 1 deletion command/types/enum.ts
Expand Up @@ -21,7 +21,7 @@ export class EnumType<T extends string | number> extends Type<T> {
throw new InvalidTypeError(type, this.allowedValues.slice());
}

public complete(): T[] {
public values(): T[] {
return this.allowedValues.slice();
}
}
Expand Down

0 comments on commit 143eb1b

Please sign in to comment.