Skip to content

Commit

Permalink
Add key and initialValue to option definition
Browse files Browse the repository at this point in the history
The `key` allows mapping an option definition to a class variable.
The `initialValue` is used to show the default value in a command's usage output.
  • Loading branch information
akuntsch committed Oct 16, 2024
1 parent 434b5a6 commit 7b0c3ce
Show file tree
Hide file tree
Showing 9 changed files with 57 additions and 12 deletions.
2 changes: 2 additions & 0 deletions sources/advanced/Command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,13 @@ export type Definition = Usage & {
* The various options registered on the command.
*/
options: Array<{
key?: string;
preferredName: string;
nameSet: Array<string>;
definition: string;
description?: string;
required: boolean;
initialValue: any;
}>;
};

Expand Down
4 changes: 3 additions & 1 deletion sources/advanced/options/Array.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,17 @@ export function Array<T = string, Arity extends number = 1>(descriptor: string,
const nameSet = new Set(optNames);

return makeCommandOption({
definition(builder) {
definition(builder, key) {
builder.addOption({
key,
names: optNames,

arity,

hidden: opts?.hidden,
description: opts?.description,
required: opts.required,
initialValue,
});
},

Expand Down
6 changes: 4 additions & 2 deletions sources/advanced/options/Boolean.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ export function Boolean(descriptor: string, initialValueBase: BooleanFlags | boo
const nameSet = new Set(optNames);

return makeCommandOption({
definition(builder) {
definition(builder, key) {
builder.addOption({
key,
names: optNames,

allowBinding: false,
Expand All @@ -29,10 +30,11 @@ export function Boolean(descriptor: string, initialValueBase: BooleanFlags | boo
hidden: opts.hidden,
description: opts.description,
required: opts.required,
initialValue,
});
},

transformer(builer, key, state) {
transformer(builder, key, state) {
let currentValue = initialValue;

for (const {name, value} of state.options) {
Expand Down
4 changes: 3 additions & 1 deletion sources/advanced/options/Counter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ export function Counter(descriptor: string, initialValueBase: CounterFlags | num
const nameSet = new Set(optNames);

return makeCommandOption({
definition(builder) {
definition(builder, key) {
builder.addOption({
key,
names: optNames,

allowBinding: false,
Expand All @@ -30,6 +31,7 @@ export function Counter(descriptor: string, initialValueBase: CounterFlags | num
hidden: opts.hidden,
description: opts.description,
required: opts.required,
initialValue,
});
},

Expand Down
1 change: 1 addition & 0 deletions sources/advanced/options/Proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export function Proxy(opts: ProxyFlags = {}) {
return makeCommandOption({
definition(builder, key) {
builder.addProxy({
key,
name: opts.name ?? key,
required: opts.required,
});
Expand Down
1 change: 1 addition & 0 deletions sources/advanced/options/Rest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export function Rest(opts: RestFlags = {}) {
return makeCommandOption({
definition(builder, key) {
builder.addRest({
key,
name: opts.name ?? key,
required: opts.required,
});
Expand Down
4 changes: 3 additions & 1 deletion sources/advanced/options/String.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,17 @@ function StringOption<T = string, Arity extends number = 1>(descriptor: string,
const nameSet = new Set(optNames);

return makeCommandOption({
definition(builder) {
definition(builder, key) {
builder.addOption({
key,
names: optNames,

arity: opts.tolerateBoolean ? 0 : arity,

hidden: opts.hidden,
description: opts.description,
required: opts.required,
initialValue,
});
},

Expand Down
27 changes: 20 additions & 7 deletions sources/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -693,13 +693,15 @@ export type ArityDefinition = {
};

export type OptDefinition = {
key?: string;
preferredName: string;
nameSet: Array<string>;
description?: string;
arity: number;
hidden: boolean;
required: boolean;
allowBinding: boolean;
initialValue: any;
};

export class CommandBuilder<Context> {
Expand Down Expand Up @@ -741,7 +743,7 @@ export class CommandBuilder<Context> {
}
}

addRest({name = `arg`, required = 0}: {name?: string, required?: number} = {}) {
addRest({key, name = `arg`, required = 0}: {key?: string, name?: string, required?: number} = {}) {
if (this.arity.extra === NoLimits)
throw new Error(`Infinite lists cannot be declared multiple times in the same command`);
if (this.arity.trailing.length > 0)
Expand All @@ -753,12 +755,12 @@ export class CommandBuilder<Context> {
this.arity.extra = NoLimits;
}

addProxy({required = 0}: {name?: string, required?: number} = {}) {
this.addRest({required});
addProxy({key, required = 0}: {key?: string, name?: string, required?: number} = {}) {
this.addRest({key, required});
this.arity.proxy = true;
}

addOption({names: nameSet, description, arity = 0, hidden = false, required = false, allowBinding = true}: Partial<OptDefinition> & {names: Array<string>}) {
addOption({key, names: nameSet, description, arity = 0, hidden = false, required = false, allowBinding = true, initialValue = undefined}: Partial<OptDefinition> & {names: Array<string>}) {
if (!allowBinding && arity > 1)
throw new Error(`The arity cannot be higher than 1 when the option only supports the --arg=value syntax`);
if (!Number.isInteger(arity))
Expand All @@ -773,7 +775,16 @@ export class CommandBuilder<Context> {
for (const name of nameSet)
this.allOptionNames.set(name, preferredName);

this.options.push({preferredName, nameSet, description, arity, hidden, required, allowBinding});
let descriptionWithDefault: string | undefined = undefined;

if (description && initialValue !== undefined)
descriptionWithDefault = `${description} [default: ${initialValue}]`;
else if (description)
descriptionWithDefault = description;
else if (initialValue !== undefined)
descriptionWithDefault = `[default: ${initialValue}]`;

this.options.push({key, preferredName, nameSet, description: descriptionWithDefault, arity, hidden, required, allowBinding, initialValue});
}

setContext(context: Context) {
Expand All @@ -784,18 +795,20 @@ export class CommandBuilder<Context> {
const segments = [this.cliOpts.binaryName];

const detailedOptionList: Array<{
key?: string;
preferredName: string;
nameSet: Array<string>;
definition: string;
description: string;
required: boolean;
initialValue: any;
}> = [];

if (this.paths.length > 0)
segments.push(...this.paths[0]);

if (detailed) {
for (const {preferredName, nameSet, arity, hidden, description, required} of this.options) {
for (const {key, preferredName, nameSet, arity, hidden, description, required, initialValue} of this.options) {
if (hidden)
continue;

Expand All @@ -806,7 +819,7 @@ export class CommandBuilder<Context> {
const definition = `${nameSet.join(`,`)}${args.join(``)}`;

if (!inlineOptions && description) {
detailedOptionList.push({preferredName, nameSet, definition, description, required});
detailedOptionList.push({key, preferredName, nameSet, definition, description, required, initialValue});
} else {
segments.push(required ? `<${definition}>` : `[${definition}]`);
}
Expand Down
20 changes: 20 additions & 0 deletions tests/specs/advanced.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -788,6 +788,26 @@ describe(`Advanced`, () => {
expect(cli.usage(CommandA, {detailed: true})).toMatch(/\u001b\[1m\$ \u001b\[22m\.\.\. greet \[--message #0\]\n\n\u001b\[1m━━━ Options .*\n\n +\S*--verbose *\S* +Log output\n +\S*--output #0 *\S* +The output directory\n/);
});


it(`should print flags with a description and/or initialValue separately`, async () => {
class CommandA extends Command {
verbose = Option.Boolean(`--verbose`, {description: `Log output`});
output = Option.String(`--output`, {description: `The output directory`});
message = Option.String(`--message`, `Hello world`);
recipient = Option.String(`--recipient`, `Bob`, {description: `The greeting's recipient`});

static paths = [[`greet`]];
async execute() {
throw new Error(`not implemented, just testing usage()`);
}
}

const cli = Cli.from([CommandA]);

// eslint-disable-next-line no-control-regex
expect(cli.usage(CommandA, {detailed: true})).toMatch(/\u001b\[1m\$ \u001b\[22m\.\.\. greet\n\n\u001b\[1m━━━ Options .*\n\n +\S*--verbose *\S* +Log output\n +\S*--output #0 *\S* +The output directory\n +\S*--message #0 *\S* +\[default: Hello world\]\n +\S*--recipient #0 *\S* +The greeting's recipient \[default: Bob\]\n/);
});

it(`should support tuples`, async () => {
class PointCommand extends Command {
point = Option.String(`--point`, {arity: 3});
Expand Down

0 comments on commit 7b0c3ce

Please sign in to comment.