@jil/args
Version:
A convention based argument parsing and formatting library, with strict validation checks
259 lines (221 loc) • 7.82 kB
text/typescript
/* eslint-disable @typescript-eslint/no-explicit-any */
export type Argv = string[];
export type ArgList = string[];
export type ListType = number[] | string[];
export type ScalarType = number | string;
export type PrimitiveType = ScalarType | boolean;
export type ValueType = ListType | PrimitiveType;
export type OptionMap = Record<string, ValueType>;
export type UnknownOptionMap = Record<string, string>;
export type AliasMap = Record<string, string>;
/** Determine option based on type. Only primitives are allowed. */
export type InferParamConfig<T> = T extends PrimitiveType ? Param<T> : never;
// This is janky but we don't have mapped array/tuples.
// This assumes no more than 5 typed params, which is usually enough.
export type MapParamConfig<T extends PrimitiveType[]> = T extends [infer A, infer B, infer C, infer D, infer E]
? [InferParamConfig<A>, InferParamConfig<B>, InferParamConfig<C>, InferParamConfig<D>, InferParamConfig<E>]
: T extends [infer A, infer B, infer C, infer D]
? [InferParamConfig<A>, InferParamConfig<B>, InferParamConfig<C>, InferParamConfig<D>]
: T extends [infer A, infer B, infer C]
? [InferParamConfig<A>, InferParamConfig<B>, InferParamConfig<C>]
: T extends [infer A, infer B]
? [InferParamConfig<A>, InferParamConfig<B>]
: T extends [infer A]
? [InferParamConfig<A>]
: T extends ArgList
? Param<string>[]
: never;
// Like the above but for the types themselves.
// If nothing, we just fallback to an array of strings.
export type MapParamType<T extends PrimitiveType[]> = T extends [infer A, infer B, infer C, infer D, infer E]
? [A, B, C, D, E, ...ArgList]
: T extends [infer A, infer B, infer C, infer D]
? [A, B, C, D, ...ArgList]
: T extends [infer A, infer B, infer C]
? [A, B, C, ...ArgList]
: T extends [infer A, infer B]
? [A, B, ...ArgList]
: T extends [infer A]
? [A, ...ArgList]
: T extends ArgList
? ArgList
: never;
/** Determine option based on type. */
export type InferOptionConfig<T> = T extends boolean
? Flag
: T extends number[] | string[]
? MultipleOption<T>
: T extends number | string
? SingleOption<T>
: never;
// Map over option types to infer the configs.
export type MapOptionConfig<T extends object> = {[K in keyof T]: InferOptionConfig<T[K]>};
export type CommandChecker = (arg: string) => boolean;
export type ContextFactory = (arg: string, argv: Argv) => ParserOptions<{}> | undefined;
export interface Arguments<O extends object, P extends PrimitiveType[] = ArgList> {
/** Current running command and sub-commads. Is an array split on ":". */
command: string[];
/** List of errors detected during argument parsing. */
errors: Error[];
/** Mapping of options to their values passed on the command line (or their default). */
options: O;
/** List of parameter values passed on the command line. */
params: MapParamType<P>;
/** List of arguments that appear after "--" on the command line. */
rest: ArgList;
/** Mapping of unconfigured options to string values. */
unknown: UnknownOptionMap;
}
export interface ParserSettings {
/** Enable loose mode parsing. */
loose?: boolean;
/** Allow unknown options to be passed. Will be placed in a special `unknown` object. */
unknown?: boolean;
/** Allow variadic params to be passed. Will be accumlated after configured params. */
variadic?: boolean;
}
export interface ParserOptions<O extends object, P extends PrimitiveType[] = ArgList> extends ParserSettings {
/** List of valid commands. Sub-commands should be denoted with ":". */
commands?: CommandChecker | string[];
/** Mapping of options to their type and configurations. */
options: MapOptionConfig<O>;
/** List of param configurations (in order). */
params?: MapParamConfig<P>;
}
// BASE TYPES
export interface Config {
/** Whether the object is deprecated or not. Will display a tag in the help menu. Defaults to `false`. */
deprecated?: boolean;
/** A description of what the object is and does. Supports basic markdown for bold, italics, and underline. */
description: string;
/** Whether the object should be hidden from the help menu or not. Will still match on the command line. Defaults to `false`. */
hidden?: boolean;
}
export interface Command extends Config {
/** The category this object belongs to. Will be used to group in the parent command or program. Defaults to no category. */
category?: string;
/** Define one or many usage examples to display in the help menu. */
usage?: string[] | string;
}
// ARGUMENT TYPES
export type InferArgType<T> = T extends boolean ? 'boolean' : T extends number[] | number ? 'number' : 'string';
export interface Arg<T> extends Config {
/**
* The default value if option not provided on the command line. The value's type
* is dependent on the `type` and `multiple` settings. Furthermore, this value defaults
* to the following if not defined.
*
* - A zero (`0`) when type is `number`.
* - An empty string (`''`) when type is `string`.
* - And `false` when type is `boolean`.
*/
default?: T;
/** An optional function to format the value after parsing. Must return the same type. */
format?: (value: T) => T;
/** Expected type of the provided value. When a value is captured from the command line, it will be type casted. */
type: InferArgType<T>;
/** An optional function to validate the provided value. */
validate?: (value: T) => void;
}
export interface Option<T extends ValueType> extends Arg<T> {
/** A unique key to group options within categories. Couples with the `Category` type. */
category?: string;
/** Single character used as a the short option alias. */
short?: ShortOptionName;
}
export interface SingleOption<T extends ScalarType> extends Option<T> {
/** Whitelist of acceptable values. */
choices?: T[];
/** When found in an option group, increment the value for each occurrence. _(Numbers only)_ */
count?: T extends number ? true : never;
default?: T;
}
export interface MultipleOption<T extends ListType> extends Option<T> {
/** Throw an error unless the list of values satisfy this required length. */
arity?: number;
default?: T;
/** Allow multiple values to be passed. */
multiple: true;
}
export interface Flag extends Omit<Option<boolean>, 'format' | 'validate'> {
default?: boolean;
}
export interface Param<T extends PrimitiveType> extends Arg<T> {
/** Informational label to display in terminal output. */
label?: string;
/** Whether the param is required or not. If required and not passed, the
parser will throw an error. Defaults to `false`. */
required?: boolean;
}
export interface Category {
name: string;
weight?: number;
}
/** Abstract type for easier typing. */
export type OptionConfig = Option<any> & {
arity?: number;
choices?: PrimitiveType[];
count?: boolean;
default?: ValueType;
multiple?: boolean;
};
export type OptionConfigMap = Record<string, OptionConfig>;
/** Abstract type for easier typing. */
export type ParamConfig = Param<any>;
export type ParamConfigList = ParamConfig[];
/** Option name without leading "--". */
export type LongOptionName = string;
/** Short option name without leading "-". */
export type ShortOptionName =
| 'A'
| 'a'
| 'B'
| 'b'
| 'C'
| 'c'
| 'D'
| 'd'
| 'E'
| 'e'
| 'F'
| 'f'
| 'G'
| 'g'
| 'H'
| 'h'
| 'I'
| 'i'
| 'J'
| 'j'
| 'K'
| 'k'
| 'L'
| 'l'
| 'M'
| 'm'
| 'N'
| 'n'
| 'O'
| 'o'
| 'P'
| 'p'
| 'Q'
| 'q'
| 'R'
| 'r'
| 'S'
| 's'
| 'T'
| 't'
| 'U'
| 'u'
| 'V'
| 'v'
| 'W'
| 'w'
| 'X'
| 'x'
| 'Y'
| 'y'
| 'Z'
| 'z';