summaryrefslogtreecommitdiff
path: root/u/process/argv.ts
blob: dcdba854230aed4382f0f3422b2e0f4aa1393d9a (plain)
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
import { Either, type Mapper, type IEither } 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 value =
        argv
            .filter((_argv) => isArgKey(_argv) && _argv.split('=')[0] === arg)
            .map((_argv, i) => {
                const next = _argv.includes('=') ? _argv.split('=')[1] : argv.at(i + 1);
                if (next) {
                    if (isArgKey(next)) return whenValue.unspecified;
                    return whenValue.present(next);
                }
                return whenValue.unspecified;
            })
            .find((x) => x) ?? whenValue.absent;
    if (value === undefined) {
        return Either.left(new Error('no value specified for ' + arg));
    }
    return Either.right(value);
};

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);
    };

    return args
        .map(processArg)
        .reduce(
            (acc: IEither<Error, Partial<Result>>, current: IEither<Error, readonly [Args[number], unknown]>) =>
                acc.flatMap((accValue) =>
                    current.mapRight(([key, value]) => ({
                        ...accValue,
                        [key]: value,
                    })),
                ),
            Either.right(<Partial<Result>>{}),
        )
        .mapRight((result) => <Result>result);
};