1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
|
import { Either, type Mapper, type IEither, Optional } from '@emprespresso/pengueno';
export const isArgKey = <K extends string>(k: string): k is K => k.startsWith('--');
interface ArgHandler<V> {
absent?: V;
unspecified?: V;
present: Mapper<string, V>;
}
export const getArg = <K extends string, V>(
arg: K,
argv: Array<string>,
whenValue: ArgHandler<V>,
): IEither<Error, V> => {
const argIndex = Optional.from(argv.findIndex((_argv) => isArgKey(_argv) && _argv.split('=')[0] === arg)).filter(
(index) => index >= 0 && index < argv.length,
);
if (!argIndex.present()) {
return Optional.from(whenValue.absent)
.map((v) => Either.right<Error, V>(v))
.orSome(() =>
Either.left(
new Error(`arg ${arg} is not present in arguments list and does not have an 'absent' value`),
),
)
.get();
}
return argIndex
.flatMap((idx) =>
Optional.from(argv.at(idx)).map((_argv) => (_argv.includes('=') ? _argv.split('=')[1] : argv.at(idx + 1))),
)
.map((next) => (isArgKey(next) ? whenValue.unspecified : whenValue.present(next)))
.map((v) => Either.right<Error, V>(<V>v))
.get();
};
type MappedArgs<
Args extends ReadonlyArray<string>,
Handlers extends Partial<Record<Args[number], ArgHandler<unknown>>>,
> = {
[K in Args[number]]: K extends keyof Handlers ? (Handlers[K] extends ArgHandler<infer T> ? T : string) : string;
};
export const argv = <
const Args extends ReadonlyArray<string>,
const Handlers extends Partial<Record<Args[number], ArgHandler<unknown>>>,
>(
args: Args,
handlers?: Handlers,
argv = process.argv.slice(2),
): IEither<Error, MappedArgs<Args, Handlers>> => {
type Result = MappedArgs<Args, Handlers>;
const defaultHandler: ArgHandler<string> = { present: (value: string) => value };
const processArg = (arg: Args[number]): IEither<Error, [Args[number], unknown]> => {
const handler = handlers?.[arg] ?? defaultHandler;
return getArg(arg, argv, handler).mapRight((value) => [arg, value] as const);
};
const res = args
.map(processArg)
.reduce(
(acc: IEither<Error, Partial<Result>>, current: IEither<Error, [Args[number], unknown]>) =>
acc.flatMap((accValue) =>
current.mapRight(([key, value]) => ({
...accValue,
[key]: value,
})),
),
Either.right(<Partial<Result>>{}),
)
.mapRight((result) => <Result>result);
return res;
};
|