diff --git a/src/lib/options.test.ts b/src/lib/options.test.ts index 77c08cd..9e72e01 100644 --- a/src/lib/options.test.ts +++ b/src/lib/options.test.ts @@ -1,6 +1,6 @@ -import { parse } from "../parser"; -import { Script, TextPiece } from "../words"; -import { getOpt, Options } from "./options"; +import { parse } from '../parser'; +import { Script, TextPiece } from '../words'; +import { getOpt, Options } from './options'; describe("getOpt", () => { const expectOpts =
(command: string, opts: P) => {
@@ -16,13 +16,41 @@ describe("getOpt", () => {
expectOpts("cmd apple banana", {}).toEqual([{}, "apple", "banana"]));
it("enforces minimum # of arguments", () =>
- expectOpts("cmd", { $min: 1 }).toEqual([
- { error: "cmd: Not enough arguments" },
+ expectOpts("extrovert", { $min: 1 }).toEqual([
+ { error: "extrovert: Not enough arguments" },
]));
it("enforces maximum # of arguments", () =>
- expectOpts("cmd apple banana", { $max: 1 }).toEqual([
- { error: "cmd: Too many arguments" },
+ expectOpts("introvert apple banana", { $max: 1 }).toEqual([
+ { error: "introvert: Too many arguments" },
]));
it("allows # of arguments in-spec", () =>
expectOpts("cmd apple", { $min: 1, $max: 1 }).toEqual([{}, "apple"]));
+
+ it("parses boolean switches", () =>
+ expectOpts("cmd -red", { red: 0, blue: 0 }).toEqual([
+ { red: true, blue: false },
+ ]));
+ it("parses switches that take arguments", () =>
+ expectOpts("cmd -onPepper {sneeze}", { onPepper: 1 }).toEqual([
+ { onPepper: ["sneeze"] },
+ ]));
+ it("enforces switch arguments exist", () =>
+ expectOpts("cmd -onPepper", { onPepper: 1 }).toEqual([
+ { error: "cmd: Not enough arguments to -onPepper" },
+ ]));
+ it("raises an error on unknown switches", () =>
+ expectOpts("cmd -unsolicited", {}).toEqual([
+ { error: "cmd: -unsolicited is not a switch this command knows about" },
+ ]));
+
+ it("doesn't count switches as positional arguments", () =>
+ expectOpts("cmd -yellow banana", { $min: 1, $max: 1, yellow: 0 }).toEqual([
+ { yellow: true },
+ "banana",
+ ]));
+ it("doesn't count quoted words as switches", () =>
+ expectOpts('cmd "-purple"', { purple: 0 }).toEqual([
+ { purple: false },
+ "-purple",
+ ]));
});
diff --git a/src/lib/options.ts b/src/lib/options.ts
index 30f3aa0..e1fddf7 100644
--- a/src/lib/options.ts
+++ b/src/lib/options.ts
@@ -1,4 +1,4 @@
-import { AsText, ErrorResult, ProcResult, TextPiece } from "../words";
+import { AsText, ErrorResult, ProcResult, TextPiece } from '../words';
export type Options = Record = {
? never
: P[K] extends 0
? boolean
- : string[];
+ : string[] | [];
} & {
error?: string;
};
@@ -68,20 +68,66 @@ export function getOptRaw (
}
}
+const SWITCH_REGEX = /^-([^]*)/;
+
function getOptCore (
argv: TextPiece[],
options: P
): [ParsedOptions , TextPiece[]] {
- const [cmd, ...textPiece] = argv;
+ const [cmd, ...textPieces] = argv;
+ const positionalArgs: TextPiece[] = [];
const flags: ParsedOptions = Object.fromEntries(
Object.entries(options)
.filter(([name]) => name != "$min" && name != "$max")
- .map(([name]) => [])
- );
+ .map(([name]) => [name, options[name] == 0 ? false : []])
+ ) as ParsedOptions ;
- // TODO: parse switches
+ // loop over args & extract switches
+ for (let i = 0; i < textPieces.length; i++) {
+ const word = textPieces[i];
+ if (!("bare" in word)) {
+ positionalArgs.push(word);
+ continue;
+ }
- if (options.$min !== undefined && options.$min > textPiece.length) {
+ const switchMatch = SWITCH_REGEX.exec(word.bare);
+ if (switchMatch == null) {
+ positionalArgs.push(word);
+ continue;
+ }
+
+ const switchName = switchMatch[1] as keyof ParsedOptions & string;
+ if (!(switchName in flags)) {
+ return [
+ {
+ error: `${AsText(
+ cmd
+ )}: -${switchName} is not a switch this command knows about`,
+ } as ParsedOptions ,
+ [],
+ ];
+ }
+
+ const switchArgCount = options[switchName];
+ if (switchArgCount == 0) {
+ (flags[switchName] as boolean) = true;
+ } else if (i + switchArgCount >= textPieces.length) {
+ return [
+ {
+ error: `${AsText(cmd)}: Not enough arguments to -${switchName}`,
+ } as ParsedOptions ,
+ [],
+ ];
+ } else {
+ const takeUntil = i + switchArgCount;
+ for (i++; i <= takeUntil; i++) {
+ (flags[switchName] as string[]).push(AsText(textPieces[i]));
+ }
+ }
+ }
+
+ // check correct number of positional arguments
+ if (options.$min !== undefined && options.$min > positionalArgs.length) {
return [
{
error: `${AsText(cmd)}: Not enough arguments`,
@@ -89,7 +135,7 @@ function getOptCore (
[],
];
}
- if (options.$max !== undefined && options.$max < textPiece.length) {
+ if (options.$max !== undefined && options.$max < positionalArgs.length) {
return [
{
error: `${AsText(cmd)}: Too many arguments`,
@@ -98,5 +144,5 @@ function getOptCore (
];
}
- return [flags, textPiece];
+ return [flags, positionalArgs];
}