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
JavaScript
;
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;