summaryrefslogtreecommitdiff
path: root/u/process/argv.ts
blob: 396fa9605eb0eab0248e620d330292450e9e8173 (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
72
73
74
75
76
77
78
79
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))),
        )
        .filter((next) => !isArgKey(next))
        .map((next) => whenValue.present(next))
        .orSome(() => whenValue.unspecified)
        .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;
};