Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/bombshell-dev/configliere. A statically typed entry-point router for command-line applications
Something went wrong. Try again.
5.2 kB · 174 lines
TypeScript
at bugfix
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175import type { AnyStep, Field, Input, Parser, Step } from "./types.ts";import { matchAll, parseArgument, parseLongOption, parseLongOptionEql, parseNegativeSwitch, parseSwitch, primitive,} from "./parse-args.ts";import { field } from "./field.ts";import { optionalBoolean } from "./schema.ts";
export type Attrs<T extends object> = & { [K in keyof T]: Parser<[Step<T[K], AnyStep["data"]>]>; } & {};
export type ObjectValue<T extends Record<string, Partial<Parser<[AnyStep]>>>> = & { [K in keyof T]: T[K] extends Parser<[Step<infer V, AnyStep["data"]>]> ? V : boolean | undefined; } & {};
export type ObjectData<A extends Attrs<object>> = & { [K in keyof A]: A[K] extends Parser<[Step<unknown, infer TData>]> ? TData : never; } & {};
export interface ObjectParser<T extends object> extends Parser<[Step<T, ObjectData<Attrs<T>>>]> { attrs: Attrs<T>;}
export function object<T extends Record<string, Partial<Parser<[AnyStep]>>>>( attrs: T,): ObjectParser<ObjectValue<T>> { type V = ObjectValue<T>; let resolved = Object.fromEntries( Object.entries(attrs).map(([key, entry]) => { let parser = typeof entry.parse === "function" ? entry as Parser<[AnyStep]> : { ...field(optionalBoolean), ...entry }; return [key, parser]; }), ) as Attrs<V>; let entries = Object.entries(resolved) as [ keyof V, Parser<[Step<V[keyof V], AnyStep["data"]>]>, ][]; let parsers = entries.map(([key, parser]) => { return [key, { ...parser, path: [String(key), ...parser.path], }] as [keyof V, Parser<[Step<V[keyof V], AnyStep["data"]>]>]; }); return { attrs: resolved, path: [], parse(input) { let value = {} as Record<keyof V, unknown>; let data = {} as ObjectData<Attrs<V>>; let errors: { path: string[]; error: Error }[] = [];
// extract CLI args into per-key values let cliValues = new Map<keyof V, { name: string; value: unknown }[]>(); let args = input.args ?? [];
if (args.length > 0) { let matched = new Set<keyof V>(); let prev: string[] | undefined; while ( args.length > 0 && (prev === undefined || args.length < prev.length) ) { prev = args; for (let [key, parser] of parsers) { let f = asField(parser); if (!f) continue; if (matched.has(key) && !f.mods.array) continue;
let matcher = matchAll( parseSwitch(f.schema, f.aliases ?? []), parseNegativeSwitch(f.schema), parseLongOption(f.aliases ?? []), parseLongOptionEql(), parseArgument(f.mods.argument), );
let match = matcher(args, parser.path); if (match.matched) { let val = primitive(match.value); if (f.mods.array) { let existing = cliValues.get(key); if (existing && existing.length > 0) { let last = existing[existing.length - 1]; (last.value as unknown[]).push(val); } else { cliValues.set(key, [{ name: "cli", value: [val] }]); } } else { let existing = cliValues.get(key) ?? []; existing.push({ name: "cli", value: val }); cliValues.set(key, existing); } matched.add(key); args = match.remainder; break; } } } }
for (let [key, parser] of parsers) { // scope values to this key let scoped: Input = { values: [ ...(input.values ?? []).flatMap((v) => { if (v.value == null) return []; let obj = v.value as Record<string, unknown>; if (!(String(key) in obj)) return []; return [{ name: v.name, value: obj[String(key)] }]; }), ...(cliValues.get(key) ?? []), ], envs: input.envs, };
let parsed = parser.parse(scoped);
if (parsed.ok) { value[key] = parsed.value; data[key] = parsed.data as ObjectData<Attrs<V>>[keyof V]; } else { errors.push({ path: parser.path, error: parsed.error }); } }
let remainder: Input = { ...input, args };
if (errors.length > 0) { return { ok: false as const, error: new ObjectValidationError(errors), remainder, }; }
return { ok: true as const, value: value as V, data, remainder, }; }, };}
function asField(parser: Parser): Field<unknown> | undefined { return "mods" in parser ? parser as Field<unknown> : undefined;}
export class ObjectValidationError extends Error { constructor(public fields: { path: string[]; error: Error }[]) { let message = fields.map(({ path, error }) => { return `${path.join(".")}: ${error.message}`; }).join("\n"); super(message); this.name = "ObjectValidationError"; }}