Skip to content
Merged
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
4 changes: 3 additions & 1 deletion src/screens/help.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,9 @@ export default async function helpScreen(): Promise<string> {
throw new Error(`Error: option['${option}'].accepts must resolve to an array`);
}

fullDescription += chalk.gray.italic(` (accepts: ${accepts.join(', ')})`);
fullDescription += chalk.gray.italic(
` (accepts${details.acceptsMultiple ? ' comma separated' : ''}: ${accepts.join(', ')})`
);
}
}

Expand Down
50 changes: 50 additions & 0 deletions src/utils/construct-input-object.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,56 @@ describe('#constructInputObject()', () => {
});
});

it('Handles multiple option strings', async () => {
process.argv = [
'/path/to/node',
path.join(testProjectsPath, 'pizza-ordering', 'cli', 'entry.js'),
'order',
'to-go-without',
'--hold',
'onions,peppers',
];

expect(await constructInputObject()).toStrictEqual({
command: 'order to-go-without',
data: undefined,
flags: {
quiet: false,
},
options: {
'delivery-zip-code': undefined,
hold: ['onions', 'peppers'],
size: undefined,
test: undefined,
},
});
});

it('Handles multiple option integers', async () => {
process.argv = [
'/path/to/node',
path.join(testProjectsPath, 'pizza-ordering', 'cli', 'entry.js'),
'order',
'to-go-without',
'--size',
'6,10,12',
];

expect(await constructInputObject()).toStrictEqual({
command: 'order to-go-without',
data: undefined,
flags: {
quiet: false,
},
options: {
'delivery-zip-code': undefined,
hold: undefined,
size: [6, 10, 12],
test: undefined,
},
});
});

it('Handles passthrough inputs too', async () => {
process.argv = [
'/path/to/node',
Expand Down
1 change: 1 addition & 0 deletions src/utils/get-all-program-commands.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ describe('#getAllProgramCommands()', () => {
'order integer-data',
'order single-topping',
'order to-go',
'order to-go-without',
]);
});
});
80 changes: 54 additions & 26 deletions src/utils/get-organized-arguments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ interface OrganizedArguments {
data?: string | number | string[] | number[];
flags: string[];
options: string[];
values: (string | number)[];
values: (string | number | string[] | number[])[];
passThrough?: string[];
}

Expand All @@ -40,6 +40,7 @@ export default async function getOrganizedArguments(): Promise<OrganizedArgument

let previousOption: string | undefined = undefined;
let nextIsOptionValue = false;
let nextOptionAcceptsMultiple = false;
let nextValueType: string | undefined = undefined;
let nextValueAccepts: string[] | number[] | undefined = undefined;
let reachedData = false;
Expand Down Expand Up @@ -73,42 +74,68 @@ export default async function getOrganizedArguments(): Promise<OrganizedArgument
// Verbose output
await verboseLog(`...Is value for previous option (${String(previousOption)})`);

// Initialize
let value: string | number = argument;

// Validate value, if necessary
if (nextValueType) {
if (nextValueType === 'integer') {
if (/^[0-9]+$/.test(value)) {
value = parseInt(value, 10);
// Helper that returns a valid value or throws
const validValue = (value: string): string | number => {
if (nextValueType) {
if (nextValueType === 'integer') {
if (/^[0-9]+$/.test(value)) {
return parseInt(value, 10);
} else {
throw new PrintableError(`The option ${String(previousOption)} expects an integer\nProvided: ${value}`);
}
} else if (nextValueType === 'float') {
if (/^[0-9]*[.]*[0-9]*$/.test(value) && value !== '.' && value !== '') {
return parseFloat(value);
} else {
throw new PrintableError(`The option ${String(previousOption)} expects a float\nProvided: ${value}`);
}
} else {
throw new PrintableError(`The option ${String(previousOption)} expects an integer\nProvided: ${value}`);
throw new PrintableError(`Unrecognized "type": ${nextValueType}`);
}
} else if (nextValueType === 'float') {
if (/^[0-9]*[.]*[0-9]*$/.test(value) && value !== '.' && value !== '') {
value = parseFloat(value);
} else {
throw new PrintableError(`The option ${String(previousOption)} expects a float\nProvided: ${value}`);
}

if (Array.isArray(nextValueAccepts)) {
const accepts = nextValueAccepts;

// @ts-expect-error: TypeScript is confused here...
if (accepts.includes(value) === false) {
throw new PrintableError(
`Unrecognized value for ${String(previousOption)}: ${value}\nAccepts: ${accepts.join(', ')}`
);
}
} else {
throw new PrintableError(`Unrecognized "type": ${nextValueType}`);
}
}

if (Array.isArray(nextValueAccepts)) {
const accepts = nextValueAccepts;
return value;
};

// Handle comma-separated values for acceptsMultiple options
if (nextOptionAcceptsMultiple) {
const parts = argument.split(',');
const processed = {
strings: [] as string[],
numbers: [] as number[],
};

// @ts-expect-error: TypeScript is confused here...
if (accepts.includes(value) === false) {
throw new PrintableError(
`Unrecognized value for ${String(previousOption)}: ${value}\nAccepts: ${accepts.join(', ')}`
);
for (const part of parts) {
const valid = validValue(part);

if (typeof valid === 'string') {
processed.strings.push(valid);
} else if (typeof valid === 'number') {
processed.numbers.push(valid);
}
}

// Store and continue
nextIsOptionValue = false;
nextOptionAcceptsMultiple = false;
organizedArguments.values.push(processed.strings.length > 0 ? processed.strings : processed.numbers);
continue;
}

// Store and continue
nextIsOptionValue = false;
organizedArguments.values.push(value);
organizedArguments.values.push(validValue(argument));
continue;
}

Expand Down Expand Up @@ -160,6 +187,7 @@ export default async function getOrganizedArguments(): Promise<OrganizedArgument
// Store details
previousOption = argument;
nextIsOptionValue = true;
nextOptionAcceptsMultiple = details.acceptsMultiple === true;
nextValueType = details.type || undefined;
organizedArguments.options.push(option);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = async function() {};
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
module.exports = {
description: 'Order a pizza, to-go',
data: {
description: 'What type of pizza to order',
type: 'fake',
},
options: {
test: {
description: 'Just used for testing',
type: 'fake',
},
hold: {
description: 'What ingredients to hold',
acceptsMultiple: true,
accepts: ['olives', 'onions', 'peppers', 'sauce'],
},
size: {
description: 'What size pizza to order',
acceptsMultiple: true,
type: 'integer',
accepts: [6, 10, 12],
}
},
acceptsPassThroughArgs: true,
};
Loading