Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add key and initialValue to option definition #170

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
5 changes: 4 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 Expand Up @@ -89,6 +91,7 @@ function StringPositional<T = string>(opts: StringPositionalFlags<T> = {}) {
return makeCommandOption({
definition(builder, key) {
builder.addPositional({
key,
name: opts.name ?? key,
required: opts.required,
});
Expand Down
31 changes: 22 additions & 9 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 All @@ -726,7 +728,7 @@ export class CommandBuilder<Context> {
Object.assign(this.arity, {leading, trailing, extra, proxy});
}

addPositional({name = `arg`, required = true}: {name?: string, required?: boolean} = {}) {
addPositional({name = `arg`, required = true}: {key: string, name?: string, required?: boolean}) {
if (!required && this.arity.extra === NoLimits)
throw new Error(`Optional parameters cannot be declared when using .rest() or .proxy()`);
if (!required && this.arity.trailing.length > 0)
Expand All @@ -741,24 +743,24 @@ 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)
throw new Error(`Infinite lists cannot be declared after the required trailing positional arguments`);

for (let t = 0; t < required; ++t)
this.addPositional({name});
this.addPositional({key, name});

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> & {key: string, 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
Loading
Loading