UNPKG

decline-ts

Version:

Composable command-line parser for TypeScript - a (partial) porting of Scala decline using fp-ts

81 lines (80 loc) 6.43 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Parser = exports.shortOpt = exports.longOptWithEquals = exports.longOpt = void 0; const fp_ts_1 = require("fp-ts"); const function_1 = require("fp-ts/function"); const index_1 = require("./Accumulator/index"); const Help_1 = require("./Help"); const Opts_1 = require("./Opts"); const Result_1 = require("./Result"); const StringUtils_1 = require("./utils/StringUtils"); const nonEmptyString = (str) => StringUtils_1.StringUtils.isNonEmpty(str) ? fp_ts_1.option.some([str[0], str.substring(1)]) : fp_ts_1.option.none; const regex = { longOpt: /^--(.+)$/, longOptWithEquals: /^--(.+?)=(.+)$/, shortOpt: /^-(.+)$/, }; exports.longOpt = StringUtils_1.StringUtils.matcher1(regex.longOpt); exports.longOptWithEquals = StringUtils_1.StringUtils.matcher2(regex.longOptWithEquals); exports.shortOpt = function_1.flow(StringUtils_1.StringUtils.matcher1(regex.shortOpt), fp_ts_1.option.chain(nonEmptyString)); const Parser = (command) => { const help = Help_1.Help.fromCommand(command); return args => consumeAll(args, index_1.Accumulator.fromOpts(command.opts)); function failure(...reasons) { return fp_ts_1.either.left(function_1.pipe(help, Help_1.Help.withErrors(reasons))); } function evalResult(out) { return function_1.pipe(out.get, fp_ts_1.either.fold(failed => failure(...function_1.pipe(failed, Result_1.Result.Failure.messages, fp_ts_1.readonlyArray.uniq(fp_ts_1.eq.eqString))), // NB: if any of the user-provided functions have side-effects, they will happen here! fn => function_1.pipe(fn(), fp_ts_1.either.fold(messages => failure(...function_1.pipe(messages, fp_ts_1.readonlyArray.uniq(fp_ts_1.eq.eqString))), result => fp_ts_1.either.right(result))))); } function toOption(args) { return function_1.pipe(args, fp_ts_1.readonlyArray.filterMap(fp_ts_1.option.fromEither), fp_ts_1.readonlyNonEmptyArray.fromReadonlyArray, fp_ts_1.option.map(([head, ...tail]) => function_1.pipe(tail, fp_ts_1.readonlyArray.reduce(head, index_1.Accumulator.orElse)))); } function consumeAll(args, accumulator) { const [arg, ...tail] = args; if (arg === undefined) return evalResult(index_1.Accumulator.result(accumulator)); return function_1.pipe(function_1.pipe(exports.longOptWithEquals(arg), fp_ts_1.option.map(consumeLongOptWithEquals(tail, accumulator))), fp_ts_1.option.alt(() => function_1.pipe(exports.longOpt(arg), fp_ts_1.option.map(consumeLongOpt(tail, accumulator)))), fp_ts_1.option.alt(() => (arg === '--' ? fp_ts_1.option.some(consumeArgs(tail, accumulator)) : fp_ts_1.option.none)), fp_ts_1.option.alt(() => function_1.pipe(exports.shortOpt(arg), fp_ts_1.option.map(consumeShortOpt(tail, accumulator)))), fp_ts_1.option.getOrElse(() => consumeDefault(arg, tail, accumulator))); } function consumeLongOptWithEquals(tail, accumulator) { return ([o, value]) => function_1.pipe(accumulator, index_1.Accumulator.parseOption(Opts_1.Opts.Name.longName(o)), fp_ts_1.option.fold(() => fp_ts_1.either.left(function_1.pipe(help, Help_1.Help.withErrors(fp_ts_1.readonlyArray.of(`Unexpected option: --${o}`)))), index_1.AccumulatorMatch.fold({ onFlag: () => failure(`Got unexpected value for flag: --${o}`), onOption: next => consumeAll(tail, next(value)), onAmbiguous: () => failure(`Ambiguous option/flag: --${o}`), }))); } function consumeLongOpt(rest, accumulator) { return o => function_1.pipe(accumulator, index_1.Accumulator.parseOption(Opts_1.Opts.Name.longName(o)), fp_ts_1.option.fold(() => fp_ts_1.either.left(function_1.pipe(help, Help_1.Help.withErrors(fp_ts_1.readonlyArray.of(`Unexpected option: --${o}`)))), index_1.AccumulatorMatch.fold({ onFlag: next => consumeAll(rest, next), onOption: next => fp_ts_1.readonlyArray.isNonEmpty(rest) ? function_1.pipe(rest, ([h, ...t]) => consumeAll(t, next(h))) : failure(`Missing value for option: --${o}`), onAmbiguous: () => failure(`Ambiguous option/flag: --${o}`), }))); } function consumeArgs(args, accumulator) { const [arg, ...tail] = args; if (arg === undefined) return evalResult(index_1.Accumulator.result(accumulator)); return function_1.pipe(accumulator, index_1.Accumulator.parseArg(arg), toOption, fp_ts_1.option.fold(() => failure(`Unexpected argument: ${arg}`), next => consumeArgs(tail, next))); } function consumeShortOpt(rest, accumulator) { return ([flag, tail]) => { return function_1.pipe(consumeShort(flag, tail, accumulator), fp_ts_1.either.chain(([newRest, newAccumulator]) => consumeAll(newRest, newAccumulator))); function consumeShort(char, tail2, accumulator2) { return function_1.pipe(accumulator2, index_1.Accumulator.parseOption(Opts_1.Opts.Name.shortName(char)), fp_ts_1.option.fold(() => fp_ts_1.either.left(function_1.pipe(help, Help_1.Help.withErrors(fp_ts_1.readonlyArray.of(`Unexpected option: -${char}`)))), index_1.AccumulatorMatch.fold({ onFlag: next => function_1.pipe(nonEmptyString(tail2), fp_ts_1.option.fold(() => fp_ts_1.either.right([rest, next]), ([nextFlag, nextTail]) => consumeShort(nextFlag, nextTail, next))), onOption: next => StringUtils_1.StringUtils.isEmpty(tail2) ? function_1.pipe(fp_ts_1.readonlyNonEmptyArray.fromReadonlyArray(rest), fp_ts_1.option.fold(() => failure(`Missing value for option: -${char}`), ([v, ...r]) => fp_ts_1.either.right([r, next(v)]))) : fp_ts_1.either.right([rest, next(tail2)]), onAmbiguous: () => failure(`Ambiguous option/flag: -${char}`), }))); } }; } function consumeDefault(arg, tail, accumulator) { return function_1.pipe(accumulator, index_1.Accumulator.parseSub(arg), fp_ts_1.option.fold(() => function_1.pipe(accumulator, index_1.Accumulator.parseArg(arg), toOption, fp_ts_1.option.fold(() => failure(`Unexpected argument: ${arg}`), next => consumeAll(tail, next))), result => function_1.pipe(result(tail), fp_ts_1.either.mapLeft(Help_1.Help.withPrefix(fp_ts_1.readonlyArray.of(command.name))), fp_ts_1.either.chain(evalResult)))); } }; exports.Parser = Parser;