diff options
Diffstat (limited to 'lib/process/argv.ts')
-rw-r--r-- | lib/process/argv.ts | 79 |
1 files changed, 79 insertions, 0 deletions
diff --git a/lib/process/argv.ts b/lib/process/argv.ts new file mode 100644 index 0000000..396fa96 --- /dev/null +++ b/lib/process/argv.ts @@ -0,0 +1,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; +}; |