clipanion
Advanced tools
Comparing version 3.2.1 to 4.0.0-rc.1
export * from './definitions'; | ||
export * from './help'; | ||
export * from './tokens'; | ||
export * from './version'; |
@@ -7,2 +7,3 @@ 'use strict'; | ||
var advanced_builtins_help = require('./help.js'); | ||
var advanced_builtins_tokens = require('./tokens.js'); | ||
var advanced_builtins_version = require('./version.js'); | ||
@@ -14,2 +15,3 @@ | ||
exports.HelpCommand = advanced_builtins_help.HelpCommand; | ||
exports.TokensCommand = advanced_builtins_tokens.TokensCommand; | ||
exports.VersionCommand = advanced_builtins_version.VersionCommand; |
@@ -98,2 +98,6 @@ /// <reference types="node" /> | ||
/** | ||
* Get the definition of a particular command. | ||
*/ | ||
definition(command: CommandClass<Context>): Definition | null; | ||
/** | ||
* Formats errors using colors. | ||
@@ -142,2 +146,19 @@ * | ||
}; | ||
export declare type MandatoryContextKeys<Context extends BaseContext> = keyof MandatoryContext<Context>; | ||
export declare type MandatoryContext<Context extends BaseContext> = { | ||
[K in Exclude<keyof Context, keyof BaseContext> as undefined extends Context[K] ? never : K]: Context[K]; | ||
}; | ||
export declare type UserProvidedContext<Context extends BaseContext> = MandatoryContext<Context> & Partial<Omit<Context, MandatoryContextKeys<Context>>>; | ||
export declare type MaybeProvidedContext<Context extends BaseContext> = MandatoryContextKeys<Context> extends never ? { | ||
context?: UserProvidedContext<Context>; | ||
} : { | ||
context: UserProvidedContext<Context>; | ||
}; | ||
export declare type ProcessOptions<Context extends BaseContext> = MaybeProvidedContext<Context> & { | ||
input: Array<string>; | ||
/** | ||
* @deprecated Experimental setting, exact behavior may change | ||
*/ | ||
partial?: boolean; | ||
}; | ||
/** | ||
@@ -222,2 +243,3 @@ * An all-in-one helper that simultaneously instantiate a CLI and immediately | ||
register(commandClass: CommandClass<Context>): void; | ||
process(opts: ProcessOptions<Context>): Command<Context>; | ||
process(input: Array<string>, context: VoidIfEmpty<Omit<Context, keyof BaseContext>>): Command<Context>; | ||
@@ -237,3 +259,5 @@ process(input: Array<string>, context: MakeOptional<Context, keyof BaseContext>): Command<Context>; | ||
runExit(input: Command<Context> | Array<string>, context: MakeOptional<Context, keyof BaseContext>): Promise<void>; | ||
suggest(input: Array<string>, partial: boolean): string[][]; | ||
definition(commandClass: CommandClass<Context>, { colored }?: { | ||
colored?: boolean; | ||
}): Definition | null; | ||
definitions({ colored }?: { | ||
@@ -258,2 +282,4 @@ colored?: boolean; | ||
options: { | ||
preferredName: string; | ||
nameSet: string[]; | ||
definition: string; | ||
@@ -270,2 +296,4 @@ description: string; | ||
options: { | ||
preferredName: string; | ||
nameSet: string[]; | ||
definition: string; | ||
@@ -272,0 +300,0 @@ description: string; |
@@ -144,5 +144,8 @@ 'use strict'; | ||
} | ||
process(input, userContext) { | ||
process(opts, contextArg) { | ||
const { input, context: userContext, partial } = typeof opts === `object` && Array.isArray(opts) | ||
? { input: opts, context: contextArg } | ||
: opts; | ||
const { contexts, process } = this.builder.compile(); | ||
const state = process(input); | ||
const state = process(input, { partial }); | ||
const context = { | ||
@@ -157,2 +160,3 @@ ...Cli.defaultContext, | ||
command.context = context; | ||
command.tokens = state.tokens; | ||
return command; | ||
@@ -168,2 +172,3 @@ } | ||
command.context = context; | ||
command.tokens = state.tokens; | ||
command.path = state.path; | ||
@@ -215,2 +220,3 @@ try { | ||
definitions: () => this.definitions(), | ||
definition: command => this.definition(command), | ||
error: (error, opts) => this.error(error, opts), | ||
@@ -238,26 +244,28 @@ format: colored => this.format(colored), | ||
} | ||
suggest(input, partial) { | ||
const { suggest } = this.builder.compile(); | ||
return suggest(input, partial); | ||
definition(commandClass, { colored = false } = {}) { | ||
if (!commandClass.usage) | ||
return null; | ||
const { usage: path } = this.getUsageByRegistration(commandClass, { detailed: false }); | ||
const { usage, options } = this.getUsageByRegistration(commandClass, { detailed: true, inlineOptions: false }); | ||
const category = typeof commandClass.usage.category !== `undefined` | ||
? format.formatMarkdownish(commandClass.usage.category, { format: this.format(colored), paragraphs: false }) | ||
: undefined; | ||
const description = typeof commandClass.usage.description !== `undefined` | ||
? format.formatMarkdownish(commandClass.usage.description, { format: this.format(colored), paragraphs: false }) | ||
: undefined; | ||
const details = typeof commandClass.usage.details !== `undefined` | ||
? format.formatMarkdownish(commandClass.usage.details, { format: this.format(colored), paragraphs: true }) | ||
: undefined; | ||
const examples = typeof commandClass.usage.examples !== `undefined` | ||
? commandClass.usage.examples.map(([label, cli]) => [format.formatMarkdownish(label, { format: this.format(colored), paragraphs: false }), cli.replace(/\$0/g, this.binaryName)]) | ||
: undefined; | ||
return { path, usage, category, description, details, examples, options }; | ||
} | ||
definitions({ colored = false } = {}) { | ||
const data = []; | ||
for (const [commandClass, { index }] of this.registrations) { | ||
if (typeof commandClass.usage === `undefined`) | ||
for (const commandClass of this.registrations.keys()) { | ||
const usage = this.definition(commandClass, { colored }); | ||
if (!usage) | ||
continue; | ||
const { usage: path } = this.getUsageByIndex(index, { detailed: false }); | ||
const { usage, options } = this.getUsageByIndex(index, { detailed: true, inlineOptions: false }); | ||
const category = typeof commandClass.usage.category !== `undefined` | ||
? format.formatMarkdownish(commandClass.usage.category, { format: this.format(colored), paragraphs: false }) | ||
: undefined; | ||
const description = typeof commandClass.usage.description !== `undefined` | ||
? format.formatMarkdownish(commandClass.usage.description, { format: this.format(colored), paragraphs: false }) | ||
: undefined; | ||
const details = typeof commandClass.usage.details !== `undefined` | ||
? format.formatMarkdownish(commandClass.usage.details, { format: this.format(colored), paragraphs: true }) | ||
: undefined; | ||
const examples = typeof commandClass.usage.examples !== `undefined` | ||
? commandClass.usage.examples.map(([label, cli]) => [format.formatMarkdownish(label, { format: this.format(colored), paragraphs: false }), cli.replace(/\$0/g, this.binaryName)]) | ||
: undefined; | ||
data.push({ path, usage, category, description, details, examples, options }); | ||
data.push(usage); | ||
} | ||
@@ -264,0 +272,0 @@ return data; |
import { LooseTest } from 'typanion'; | ||
import { Token } from '../core'; | ||
import { BaseContext, MiniCli } from './Cli'; | ||
@@ -52,2 +53,4 @@ import { isOptionSymbol } from './options/utils'; | ||
options: Array<{ | ||
preferredName: string; | ||
nameSet: Array<string>; | ||
definition: string; | ||
@@ -73,2 +76,3 @@ description?: string; | ||
export declare abstract class Command<Context extends BaseContext = BaseContext> { | ||
[`constructor`]: CommandClass<Context>; | ||
/** | ||
@@ -137,2 +141,7 @@ * @deprecated Do not use this; prefer the static `paths` property instead. | ||
path: Array<string>; | ||
/** | ||
* Predefined variable that will be populated with the tokens found when | ||
* interpreting the command line. | ||
*/ | ||
tokens: Array<Token>; | ||
validateAndExecute(): Promise<number>; | ||
@@ -139,0 +148,0 @@ /** |
export { Command } from './Command'; | ||
export { BaseContext, Cli, RunContext, CliOptions } from './Cli'; | ||
export { CommandClass, Usage, Definition } from './Command'; | ||
export { Token } from '../core'; | ||
export { UsageError, ErrorMeta, ErrorWithMeta } from '../errors'; | ||
@@ -5,0 +6,0 @@ export { formatMarkdownish, ColorFormat } from '../format'; |
@@ -6,6 +6,6 @@ 'use strict'; | ||
var advanced_options_utils = require('./utils.js'); | ||
var advanced_options_Proxy = require('./Proxy.js'); | ||
var advanced_options_Array = require('./Array.js'); | ||
var advanced_options_Boolean = require('./Boolean.js'); | ||
var advanced_options_Counter = require('./Counter.js'); | ||
var advanced_options_Proxy = require('./Proxy.js'); | ||
var advanced_options_Rest = require('./Rest.js'); | ||
@@ -22,7 +22,7 @@ var advanced_options_String = require('./String.js'); | ||
exports.rerouteArguments = advanced_options_utils.rerouteArguments; | ||
exports.Proxy = advanced_options_Proxy.Proxy; | ||
exports.Array = advanced_options_Array.Array; | ||
exports.Boolean = advanced_options_Boolean.Boolean; | ||
exports.Counter = advanced_options_Counter.Counter; | ||
exports.Proxy = advanced_options_Proxy.Proxy; | ||
exports.Rest = advanced_options_Rest.Rest; | ||
exports.String = advanced_options_String.String; |
@@ -1,6 +0,12 @@ | ||
export declare const NODE_INITIAL = 0; | ||
export declare const NODE_SUCCESS = 1; | ||
export declare const NODE_ERRORED = 2; | ||
export declare const START_OF_INPUT = "\u0001"; | ||
export declare const END_OF_INPUT = "\0"; | ||
export declare enum SpecialToken { | ||
StartOfInput = "\0", | ||
EndOfInput = "\u0001", | ||
EndOfPartialInput = "\u0002" | ||
} | ||
export declare enum NodeType { | ||
InitialNode = 0, | ||
SuccessNode = 1, | ||
ErrorNode = 2, | ||
CustomNode = 3 | ||
} | ||
export declare const HELP_COMMAND_INDEX = -1; | ||
@@ -11,2 +17,2 @@ export declare const HELP_REGEX: RegExp; | ||
export declare const BINDING_REGEX: RegExp; | ||
export declare const DEBUG: boolean; | ||
export declare const IS_DEBUG: boolean; |
@@ -5,7 +5,13 @@ 'use strict'; | ||
const NODE_INITIAL = 0; | ||
const NODE_SUCCESS = 1; | ||
const NODE_ERRORED = 2; | ||
const START_OF_INPUT = `\u0001`; | ||
const END_OF_INPUT = `\u0000`; | ||
(function (SpecialToken) { | ||
SpecialToken["StartOfInput"] = "\0"; | ||
SpecialToken["EndOfInput"] = "\u0001"; | ||
SpecialToken["EndOfPartialInput"] = "\u0002"; | ||
})(exports.SpecialToken || (exports.SpecialToken = {})); | ||
(function (NodeType) { | ||
NodeType[NodeType["InitialNode"] = 0] = "InitialNode"; | ||
NodeType[NodeType["SuccessNode"] = 1] = "SuccessNode"; | ||
NodeType[NodeType["ErrorNode"] = 2] = "ErrorNode"; | ||
NodeType[NodeType["CustomNode"] = 3] = "CustomNode"; | ||
})(exports.NodeType || (exports.NodeType = {})); | ||
const HELP_COMMAND_INDEX = -1; | ||
@@ -16,14 +22,9 @@ const HELP_REGEX = /^(-h|--help)(?:=([0-9]+))?$/; | ||
const BINDING_REGEX = /^([^=]+)=([\s\S]*)$/; | ||
const DEBUG = process.env.DEBUG_CLI === `1`; | ||
const IS_DEBUG = process.env.DEBUG_CLI === `1`; | ||
exports.BATCH_REGEX = BATCH_REGEX; | ||
exports.BINDING_REGEX = BINDING_REGEX; | ||
exports.DEBUG = DEBUG; | ||
exports.END_OF_INPUT = END_OF_INPUT; | ||
exports.HELP_COMMAND_INDEX = HELP_COMMAND_INDEX; | ||
exports.HELP_REGEX = HELP_REGEX; | ||
exports.NODE_ERRORED = NODE_ERRORED; | ||
exports.NODE_INITIAL = NODE_INITIAL; | ||
exports.NODE_SUCCESS = NODE_SUCCESS; | ||
exports.IS_DEBUG = IS_DEBUG; | ||
exports.OPTION_REGEX = OPTION_REGEX; | ||
exports.START_OF_INPUT = START_OF_INPUT; |
@@ -0,1 +1,2 @@ | ||
import { NodeType } from './constants'; | ||
export declare function debug(str: string): void; | ||
@@ -5,2 +6,27 @@ export declare type StateMachine = { | ||
}; | ||
export declare type TokenBase = { | ||
segmentIndex: number; | ||
}; | ||
export declare type PathToken = TokenBase & { | ||
type: `path`; | ||
slice?: undefined; | ||
}; | ||
export declare type PositionalToken = TokenBase & { | ||
type: `positional`; | ||
slice?: undefined; | ||
}; | ||
export declare type OptionToken = TokenBase & { | ||
type: `option`; | ||
slice?: [number, number]; | ||
option: string; | ||
}; | ||
export declare type AssignToken = TokenBase & { | ||
type: `assign`; | ||
slice: [number, number]; | ||
}; | ||
export declare type ValueToken = TokenBase & { | ||
type: `value`; | ||
slice?: [number, number]; | ||
}; | ||
export declare type Token = PathToken | PositionalToken | OptionToken | AssignToken | ValueToken; | ||
export declare type RunState = { | ||
@@ -22,2 +48,3 @@ candidateUsage: string | null; | ||
selectedIndex: number | null; | ||
tokens: Array<Token>; | ||
}; | ||
@@ -59,8 +86,8 @@ export declare function makeStateMachine(): StateMachine; | ||
to: number; | ||
reducer: "setCandidateState" | "setSelectedIndex" | "pushBatch" | "pushBound" | "pushPath" | "pushPositional" | "pushExtra" | "pushExtraNoLimits" | "pushTrue" | "pushFalse" | "pushUndefined" | "pushStringValue" | "setStringValue" | "inhibateOptions" | "useHelp" | "setError" | "setOptionArityError" | ["setCandidateState" | "setSelectedIndex" | "pushBatch" | "pushBound" | "pushPath" | "pushPositional" | "pushExtra" | "pushExtraNoLimits" | "pushTrue" | "pushFalse" | "pushUndefined" | "pushStringValue" | "setStringValue" | "inhibateOptions" | "useHelp" | "setError" | "setOptionArityError"] | ["setCandidateState" | "setSelectedIndex" | "pushBatch" | "pushBound" | "pushPath" | "pushPositional" | "pushExtra" | "pushExtraNoLimits" | "pushTrue" | "pushFalse" | "pushUndefined" | "pushStringValue" | "setStringValue" | "inhibateOptions" | "useHelp" | "setError" | "setOptionArityError", Partial<RunState>] | ["setCandidateState" | "setSelectedIndex" | "pushBatch" | "pushBound" | "pushPath" | "pushPositional" | "pushExtra" | "pushExtraNoLimits" | "pushTrue" | "pushFalse" | "pushUndefined" | "pushStringValue" | "setStringValue" | "inhibateOptions" | "useHelp" | "setError" | "setOptionArityError", number] | ["setCandidateState" | "setSelectedIndex" | "pushBatch" | "pushBound" | "pushPath" | "pushPositional" | "pushExtra" | "pushExtraNoLimits" | "pushTrue" | "pushFalse" | "pushUndefined" | "pushStringValue" | "setStringValue" | "inhibateOptions" | "useHelp" | "setError" | "setOptionArityError", (string | undefined)?] | ["setCandidateState" | "setSelectedIndex" | "pushBatch" | "pushBound" | "pushPath" | "pushPositional" | "pushExtra" | "pushExtraNoLimits" | "pushTrue" | "pushFalse" | "pushUndefined" | "pushStringValue" | "setStringValue" | "inhibateOptions" | "useHelp" | "setError" | "setOptionArityError", string] | undefined; | ||
reducer: "setCandidateState" | "setSelectedIndex" | "pushBatch" | "pushBound" | "pushPath" | "pushPositional" | "pushExtra" | "pushExtraNoLimits" | "pushTrue" | "pushFalse" | "pushUndefined" | "pushStringValue" | "setStringValue" | "inhibateOptions" | "useHelp" | "setError" | "setOptionArityError" | ["setCandidateState" | "setSelectedIndex" | "pushBatch" | "pushBound" | "pushPath" | "pushPositional" | "pushExtra" | "pushExtraNoLimits" | "pushTrue" | "pushFalse" | "pushUndefined" | "pushStringValue" | "setStringValue" | "inhibateOptions" | "useHelp" | "setError" | "setOptionArityError"] | ["setCandidateState" | "setSelectedIndex" | "pushBatch" | "pushBound" | "pushPath" | "pushPositional" | "pushExtra" | "pushExtraNoLimits" | "pushTrue" | "pushFalse" | "pushUndefined" | "pushStringValue" | "setStringValue" | "inhibateOptions" | "useHelp" | "setError" | "setOptionArityError", Partial<RunState>] | ["setCandidateState" | "setSelectedIndex" | "pushBatch" | "pushBound" | "pushPath" | "pushPositional" | "pushExtra" | "pushExtraNoLimits" | "pushTrue" | "pushFalse" | "pushUndefined" | "pushStringValue" | "setStringValue" | "inhibateOptions" | "useHelp" | "setError" | "setOptionArityError", number] | ["setCandidateState" | "setSelectedIndex" | "pushBatch" | "pushBound" | "pushPath" | "pushPositional" | "pushExtra" | "pushExtraNoLimits" | "pushTrue" | "pushFalse" | "pushUndefined" | "pushStringValue" | "setStringValue" | "inhibateOptions" | "useHelp" | "setError" | "setOptionArityError", Map<string, string>] | ["setCandidateState" | "setSelectedIndex" | "pushBatch" | "pushBound" | "pushPath" | "pushPositional" | "pushExtra" | "pushExtraNoLimits" | "pushTrue" | "pushFalse" | "pushUndefined" | "pushStringValue" | "setStringValue" | "inhibateOptions" | "useHelp" | "setError" | "setOptionArityError", string] | undefined; | ||
}; | ||
export declare function cloneNode(input: Node, offset?: number): Node; | ||
export declare function registerDynamic<T extends keyof typeof tests, R extends keyof typeof reducers>(machine: StateMachine, from: number, test: Callback<T, typeof tests>, to: number, reducer?: Callback<R, typeof reducers>): void; | ||
export declare function registerShortcut<R extends keyof typeof reducers>(machine: StateMachine, from: number, to: number, reducer?: Callback<R, typeof reducers>): void; | ||
export declare function registerStatic<R extends keyof typeof reducers>(machine: StateMachine, from: number, test: string, to: number, reducer?: Callback<R, typeof reducers>): void; | ||
export declare function registerDynamic<T extends keyof typeof tests, R extends keyof typeof reducers>(machine: StateMachine, from: NodeType | number, test: Callback<T, typeof tests>, to: NodeType | number, reducer?: Callback<R, typeof reducers>): void; | ||
export declare function registerShortcut<R extends keyof typeof reducers>(machine: StateMachine, from: NodeType | number, to: NodeType | number, reducer?: Callback<R, typeof reducers>): void; | ||
export declare function registerStatic<R extends keyof typeof reducers>(machine: StateMachine, from: NodeType | number, test: string, to: NodeType | number, reducer?: Callback<R, typeof reducers>): void; | ||
declare type UndefinedKeys<T> = { | ||
@@ -71,4 +98,4 @@ [P in keyof T]-?: undefined extends T[P] ? P : never; | ||
declare type TupleKeys<T> = Exclude<keyof T, keyof []>; | ||
export declare type CallbackFn<P extends Array<any>, R> = (state: RunState, segment: string, ...args: P) => R; | ||
export declare type CallbackFnParameters<T extends CallbackFn<any, any>> = T extends ((state: RunState, segment: string, ...args: infer P) => any) ? P : never; | ||
export declare type CallbackFn<P extends Array<any>, R> = (state: RunState, segment: string, segmentIndex: number, ...args: P) => R; | ||
export declare type CallbackFnParameters<T extends CallbackFn<any, any>> = T extends ((state: RunState, segment: string, segmentIndex: number, ...args: infer P) => any) ? P : never; | ||
export declare type CallbackStore<T extends string, R> = Record<T, CallbackFn<any, R>>; | ||
@@ -78,4 +105,3 @@ export declare type Callback<T extends string, S extends CallbackStore<T, any>> = [ | ||
] extends [UndefinedTupleKeys<CallbackFnParameters<S[T]>>] ? (T | [T, ...CallbackFnParameters<S[T]>]) : [T, ...CallbackFnParameters<S[T]>]; | ||
export declare function execute<T extends string, R, S extends CallbackStore<T, R>>(store: S, callback: Callback<T, S>, state: RunState, segment: string): R; | ||
export declare function suggest(callback: Callback<keyof typeof tests, typeof tests>, state: RunState): Array<string> | null; | ||
export declare function execute<T extends string, R, S extends CallbackStore<T, R>>(store: S, callback: Callback<T, S>, state: RunState, segment: string, segmentIndex: number): R; | ||
export declare const tests: { | ||
@@ -85,12 +111,12 @@ always: () => boolean; | ||
isNotOptionLike: (state: RunState, segment: string) => boolean; | ||
isOption: (state: RunState, segment: string, name: string, hidden?: boolean) => boolean; | ||
isBatchOption: (state: RunState, segment: string, names: Array<string>) => boolean; | ||
isBoundOption: (state: RunState, segment: string, names: Array<string>, options: Array<OptDefinition>) => boolean; | ||
isNegatedOption: (state: RunState, segment: string, name: string) => boolean; | ||
isOption: (state: RunState, segment: string, segmentIndex: number, name: string) => boolean; | ||
isBatchOption: (state: RunState, segment: string, segmentIndex: number, names: Map<string, string>) => boolean; | ||
isBoundOption: (state: RunState, segment: string, segmentIndex: number, names: Map<string, string>, options: Array<OptDefinition>) => boolean; | ||
isNegatedOption: (state: RunState, segment: string, segmentIndex: number, name: string) => boolean; | ||
isHelp: (state: RunState, segment: string) => boolean; | ||
isUnsupportedOption: (state: RunState, segment: string, names: Array<string>) => boolean; | ||
isUnsupportedOption: (state: RunState, segment: string, segmentIndex: number, names: Map<string, string>) => boolean; | ||
isInvalidOption: (state: RunState, segment: string) => boolean; | ||
}; | ||
export declare const reducers: { | ||
setCandidateState: (state: RunState, segment: string, candidateState: Partial<RunState>) => { | ||
setCandidateState: (state: RunState, segment: string, segmentIndex: number, candidateState: Partial<RunState>) => { | ||
candidateUsage: string | null; | ||
@@ -111,4 +137,5 @@ requiredOptions: Array<Array<string>>; | ||
selectedIndex: number | null; | ||
tokens: Array<Token>; | ||
}; | ||
setSelectedIndex: (state: RunState, segment: string, index: number) => { | ||
setSelectedIndex: (state: RunState, segment: string, segmentIndex: number, index: number) => { | ||
selectedIndex: number; | ||
@@ -129,4 +156,5 @@ candidateUsage: string | null; | ||
remainder: string | null; | ||
tokens: Array<Token>; | ||
}; | ||
pushBatch: (state: RunState, segment: string) => { | ||
pushBatch: (state: RunState, segment: string, segmentIndex: number, names: Map<string, string>) => { | ||
options: { | ||
@@ -136,2 +164,3 @@ name: string; | ||
}[]; | ||
tokens: Token[]; | ||
candidateUsage: string | null; | ||
@@ -149,3 +178,3 @@ requiredOptions: Array<Array<string>>; | ||
}; | ||
pushBound: (state: RunState, segment: string) => { | ||
pushBound: (state: RunState, segment: string, segmentIndex: number) => { | ||
options: { | ||
@@ -155,2 +184,3 @@ name: string; | ||
}[]; | ||
tokens: Token[]; | ||
candidateUsage: string | null; | ||
@@ -168,4 +198,5 @@ requiredOptions: Array<Array<string>>; | ||
}; | ||
pushPath: (state: RunState, segment: string) => { | ||
pushPath: (state: RunState, segment: string, segmentIndex: number) => { | ||
path: string[]; | ||
tokens: Token[]; | ||
candidateUsage: string | null; | ||
@@ -186,3 +217,3 @@ requiredOptions: Array<Array<string>>; | ||
}; | ||
pushPositional: (state: RunState, segment: string) => { | ||
pushPositional: (state: RunState, segment: string, segmentIndex: number) => { | ||
positionals: { | ||
@@ -192,2 +223,3 @@ value: string; | ||
}[]; | ||
tokens: Token[]; | ||
candidateUsage: string | null; | ||
@@ -205,3 +237,3 @@ requiredOptions: Array<Array<string>>; | ||
}; | ||
pushExtra: (state: RunState, segment: string) => { | ||
pushExtra: (state: RunState, segment: string, segmentIndex: number) => { | ||
positionals: { | ||
@@ -211,2 +243,3 @@ value: string; | ||
}[]; | ||
tokens: Token[]; | ||
candidateUsage: string | null; | ||
@@ -224,3 +257,3 @@ requiredOptions: Array<Array<string>>; | ||
}; | ||
pushExtraNoLimits: (state: RunState, segment: string) => { | ||
pushExtraNoLimits: (state: RunState, segment: string, segmentIndex: number) => { | ||
positionals: { | ||
@@ -230,2 +263,3 @@ value: string; | ||
}[]; | ||
tokens: Token[]; | ||
candidateUsage: string | null; | ||
@@ -243,3 +277,3 @@ requiredOptions: Array<Array<string>>; | ||
}; | ||
pushTrue: (state: RunState, segment: string, name?: string) => { | ||
pushTrue: (state: RunState, segment: string, segmentIndex: number, name: string) => { | ||
options: { | ||
@@ -249,2 +283,3 @@ name: string; | ||
}[]; | ||
tokens: Token[]; | ||
candidateUsage: string | null; | ||
@@ -262,3 +297,3 @@ requiredOptions: Array<Array<string>>; | ||
}; | ||
pushFalse: (state: RunState, segment: string, name?: string) => { | ||
pushFalse: (state: RunState, segment: string, segmentIndex: number, name: string) => { | ||
options: { | ||
@@ -268,2 +303,3 @@ name: string; | ||
}[]; | ||
tokens: Token[]; | ||
candidateUsage: string | null; | ||
@@ -281,3 +317,3 @@ requiredOptions: Array<Array<string>>; | ||
}; | ||
pushUndefined: (state: RunState, segment: string) => { | ||
pushUndefined: (state: RunState, segment: string, segmentIndex: number, name: string) => { | ||
options: { | ||
@@ -287,2 +323,3 @@ name: string; | ||
}[]; | ||
tokens: Token[]; | ||
candidateUsage: string | null; | ||
@@ -300,3 +337,3 @@ requiredOptions: Array<Array<string>>; | ||
}; | ||
pushStringValue: (state: RunState, segment: string) => { | ||
pushStringValue: (state: RunState, segment: string, segmentIndex: number) => { | ||
options: { | ||
@@ -306,2 +343,3 @@ name: string; | ||
}[]; | ||
tokens: Token[]; | ||
candidateUsage: string | null; | ||
@@ -319,3 +357,3 @@ requiredOptions: Array<Array<string>>; | ||
}; | ||
setStringValue: (state: RunState, segment: string) => { | ||
setStringValue: (state: RunState, segment: string, segmentIndex: number) => { | ||
options: { | ||
@@ -325,2 +363,3 @@ name: string; | ||
}[]; | ||
tokens: Token[]; | ||
candidateUsage: string | null; | ||
@@ -354,4 +393,5 @@ requiredOptions: Array<Array<string>>; | ||
selectedIndex: number | null; | ||
tokens: Array<Token>; | ||
}; | ||
useHelp: (state: RunState, segment: string, command: number) => { | ||
useHelp: (state: RunState, segment: string, segmentIndex: number, command: number) => { | ||
options: { | ||
@@ -372,4 +412,5 @@ name: string; | ||
selectedIndex: number | null; | ||
tokens: Array<Token>; | ||
}; | ||
setError: (state: RunState, segment: string, errorMessage: string) => { | ||
setError: (state: RunState, segment: string, segmentIndex: number, errorMessage: string) => { | ||
errorMessage: string; | ||
@@ -390,2 +431,3 @@ candidateUsage: string | null; | ||
selectedIndex: number | null; | ||
tokens: Array<Token>; | ||
}; | ||
@@ -408,2 +450,3 @@ setOptionArityError: (state: RunState, segment: string) => { | ||
selectedIndex: number | null; | ||
tokens: Array<Token>; | ||
}; | ||
@@ -419,3 +462,4 @@ }; | ||
export declare type OptDefinition = { | ||
names: Array<string>; | ||
preferredName: string; | ||
nameSet: Array<string>; | ||
description?: string; | ||
@@ -430,3 +474,3 @@ arity: number; | ||
readonly cliOpts: Readonly<CliOptions>; | ||
readonly allOptionNames: Array<string>; | ||
readonly allOptionNames: Map<string, string>; | ||
readonly arity: ArityDefinition; | ||
@@ -451,3 +495,3 @@ readonly options: Array<OptDefinition>; | ||
}): void; | ||
addOption({ names, description, arity, hidden, required, allowBinding }: Partial<OptDefinition> & { | ||
addOption({ names: nameSet, description, arity, hidden, required, allowBinding }: Partial<OptDefinition> & { | ||
names: Array<string>; | ||
@@ -462,2 +506,4 @@ }): void; | ||
options: { | ||
preferredName: string; | ||
nameSet: Array<string>; | ||
definition: string; | ||
@@ -484,4 +530,5 @@ description: string; | ||
contexts: Context[]; | ||
process: (input: string[]) => RunState; | ||
suggest: (input: string[], partial: boolean) => string[][]; | ||
process: (input: string[], { partial }?: { | ||
partial?: boolean | undefined; | ||
}) => RunState; | ||
}; | ||
@@ -495,6 +542,7 @@ constructor({ binaryName }?: Partial<CliOptions>); | ||
contexts: Context[]; | ||
process: (input: Array<string>) => RunState; | ||
suggest: (input: Array<string>, partial: boolean) => string[][]; | ||
process: (input: Array<string>, { partial }?: { | ||
partial?: boolean | undefined; | ||
}) => RunState; | ||
}; | ||
} | ||
export {}; |
356
lib/core.js
@@ -10,3 +10,3 @@ 'use strict'; | ||
function debug(str) { | ||
if (constants.DEBUG) { | ||
if (constants.IS_DEBUG) { | ||
console.log(str); | ||
@@ -25,7 +25,11 @@ } | ||
selectedIndex: constants.HELP_COMMAND_INDEX, | ||
tokens: [], | ||
}; | ||
function makeStateMachine() { | ||
return { | ||
nodes: [makeNode(), makeNode(), makeNode()], | ||
const stateMachine = { | ||
nodes: [], | ||
}; | ||
for (let t = 0; t < constants.NodeType.CustomNode; ++t) | ||
stateMachine.nodes.push(makeNode()); | ||
return stateMachine; | ||
} | ||
@@ -41,6 +45,6 @@ function makeAnyOfMachine(inputs) { | ||
output.nodes.push(cloneNode(input.nodes[t], offset)); | ||
offset += input.nodes.length - 2; | ||
offset += input.nodes.length - constants.NodeType.CustomNode + 1; | ||
} | ||
for (const head of heads) | ||
registerShortcut(output, constants.NODE_INITIAL, head); | ||
registerShortcut(output, constants.NodeType.InitialNode, head); | ||
return output; | ||
@@ -91,7 +95,7 @@ } | ||
}; | ||
process(constants.NODE_INITIAL); | ||
process(constants.NodeType.InitialNode); | ||
} | ||
function debugMachine(machine, { prefix = `` } = {}) { | ||
// Don't iterate unless it's needed | ||
if (constants.DEBUG) { | ||
if (constants.IS_DEBUG) { | ||
debug(`${prefix}Nodes are:`); | ||
@@ -105,3 +109,5 @@ for (let t = 0; t < machine.nodes.length; ++t) { | ||
debug(`Running a vm on ${JSON.stringify(input)}`); | ||
let branches = [{ node: constants.NODE_INITIAL, state: { | ||
let branches = [{ | ||
node: constants.NodeType.InitialNode, | ||
state: { | ||
candidateUsage: null, | ||
@@ -116,7 +122,12 @@ requiredOptions: [], | ||
selectedIndex: null, | ||
} }]; | ||
tokens: [], | ||
}, | ||
}]; | ||
debugMachine(machine, { prefix: ` ` }); | ||
const tokens = [constants.START_OF_INPUT, ...input]; | ||
const tokens = [constants.SpecialToken.StartOfInput, ...input]; | ||
for (let t = 0; t < tokens.length; ++t) { | ||
const segment = tokens[t]; | ||
const isEOI = segment === constants.SpecialToken.EndOfInput || segment === constants.SpecialToken.EndOfPartialInput; | ||
// The -1 is because we added a START_OF_INPUT token | ||
const segmentIndex = t - 1; | ||
debug(` Processing ${JSON.stringify(segment)}`); | ||
@@ -127,3 +138,3 @@ const nextBranches = []; | ||
const nodeDef = machine.nodes[node]; | ||
if (node === constants.NODE_ERRORED) { | ||
if (node === constants.NodeType.ErrorNode) { | ||
nextBranches.push({ node, state }); | ||
@@ -138,3 +149,3 @@ continue; | ||
for (const { to, reducer } of transitions) { | ||
nextBranches.push({ node: to, state: typeof reducer !== `undefined` ? execute(reducers, reducer, state, segment) : state }); | ||
nextBranches.push({ node: to, state: typeof reducer !== `undefined` ? execute(reducers, reducer, state, segment, segmentIndex) : state }); | ||
debug(` Static transition to ${to} found`); | ||
@@ -154,3 +165,3 @@ } | ||
for (const { to, reducer } of nodeDef.statics[candidate]) { | ||
nextBranches.push({ node: to, state: typeof reducer !== `undefined` ? execute(reducers, reducer, state, segment) : state }); | ||
nextBranches.push({ node: to, state: typeof reducer !== `undefined` ? execute(reducers, reducer, state, segment, segmentIndex) : state }); | ||
debug(` Static transition to ${to} found`); | ||
@@ -171,6 +182,6 @@ } | ||
} | ||
if (segment !== constants.END_OF_INPUT) { | ||
if (!isEOI) { | ||
for (const [test, { to, reducer }] of nodeDef.dynamics) { | ||
if (execute(tests, test, state, segment)) { | ||
nextBranches.push({ node: to, state: typeof reducer !== `undefined` ? execute(reducers, reducer, state, segment) : state }); | ||
if (execute(tests, test, state, segment, segmentIndex)) { | ||
nextBranches.push({ node: to, state: typeof reducer !== `undefined` ? execute(reducers, reducer, state, segment, segmentIndex) : state }); | ||
debug(` Dynamic transition to ${to} found (via ${test})`); | ||
@@ -181,5 +192,5 @@ } | ||
} | ||
if (nextBranches.length === 0 && segment === constants.END_OF_INPUT && input.length === 1) { | ||
if (nextBranches.length === 0 && isEOI && input.length === 1) { | ||
return [{ | ||
node: constants.NODE_INITIAL, | ||
node: constants.NodeType.InitialNode, | ||
state: basicHelpState, | ||
@@ -190,3 +201,3 @@ }]; | ||
throw new errors.UnknownSyntaxError(input, branches.filter(({ node }) => { | ||
return node !== constants.NODE_ERRORED; | ||
return node !== constants.NodeType.ErrorNode; | ||
}).map(({ state }) => { | ||
@@ -196,3 +207,3 @@ return { usage: state.candidateUsage, reason: null }; | ||
} | ||
if (nextBranches.every(({ node }) => node === constants.NODE_ERRORED)) { | ||
if (nextBranches.every(({ node }) => node === constants.NodeType.ErrorNode)) { | ||
throw new errors.UnknownSyntaxError(input, nextBranches.map(({ state }) => { | ||
@@ -215,75 +226,4 @@ return { usage: state.candidateUsage, reason: state.errorMessage }; | ||
} | ||
function checkIfNodeIsFinished(node, state) { | ||
if (state.selectedIndex !== null) | ||
return true; | ||
if (Object.prototype.hasOwnProperty.call(node.statics, constants.END_OF_INPUT)) | ||
for (const { to } of node.statics[constants.END_OF_INPUT]) | ||
if (to === constants.NODE_SUCCESS) | ||
return true; | ||
return false; | ||
} | ||
function suggestMachine(machine, input, partial) { | ||
// If we're accepting partial matches, then exact matches need to be | ||
// prefixed with an extra space. | ||
const prefix = partial && input.length > 0 ? [``] : []; | ||
const branches = runMachineInternal(machine, input, partial); | ||
const suggestions = []; | ||
const suggestionsJson = new Set(); | ||
const traverseSuggestion = (suggestion, node, skipFirst = true) => { | ||
let nextNodes = [node]; | ||
while (nextNodes.length > 0) { | ||
const currentNodes = nextNodes; | ||
nextNodes = []; | ||
for (const node of currentNodes) { | ||
const nodeDef = machine.nodes[node]; | ||
const keys = Object.keys(nodeDef.statics); | ||
// The fact that `key` is unused is likely a bug, but no one has investigated it yet. | ||
// TODO: Investigate it. | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
for (const key of Object.keys(nodeDef.statics)) { | ||
const segment = keys[0]; | ||
for (const { to, reducer } of nodeDef.statics[segment]) { | ||
if (reducer !== `pushPath`) | ||
continue; | ||
if (!skipFirst) | ||
suggestion.push(segment); | ||
nextNodes.push(to); | ||
} | ||
} | ||
} | ||
skipFirst = false; | ||
} | ||
const json = JSON.stringify(suggestion); | ||
if (suggestionsJson.has(json)) | ||
return; | ||
suggestions.push(suggestion); | ||
suggestionsJson.add(json); | ||
}; | ||
for (const { node, state } of branches) { | ||
if (state.remainder !== null) { | ||
traverseSuggestion([state.remainder], node); | ||
continue; | ||
} | ||
const nodeDef = machine.nodes[node]; | ||
const isFinished = checkIfNodeIsFinished(nodeDef, state); | ||
for (const [candidate, transitions] of Object.entries(nodeDef.statics)) | ||
if ((isFinished && candidate !== constants.END_OF_INPUT) || (!candidate.startsWith(`-`) && transitions.some(({ reducer }) => reducer === `pushPath`))) | ||
traverseSuggestion([...prefix, candidate], node); | ||
if (!isFinished) | ||
continue; | ||
for (const [test, { to }] of nodeDef.dynamics) { | ||
if (to === constants.NODE_ERRORED) | ||
continue; | ||
const tokens = suggest(test, state); | ||
if (tokens === null) | ||
continue; | ||
for (const token of tokens) { | ||
traverseSuggestion([...prefix, token], node); | ||
} | ||
} | ||
} | ||
return [...suggestions].sort(); | ||
} | ||
function runMachine(machine, input) { | ||
const branches = runMachineInternal(machine, [...input, constants.END_OF_INPUT]); | ||
function runMachine(machine, input, { endToken = constants.SpecialToken.EndOfInput } = {}) { | ||
const branches = runMachineInternal(machine, [...input, endToken]); | ||
return selectBestState(input, branches.map(({ state }) => { | ||
@@ -375,7 +315,12 @@ return state; | ||
function isTerminalNode(node) { | ||
return node === constants.NODE_SUCCESS || node === constants.NODE_ERRORED; | ||
return node === constants.NodeType.SuccessNode || node === constants.NodeType.ErrorNode; | ||
} | ||
function cloneTransition(input, offset = 0) { | ||
const to = !isTerminalNode(input.to) | ||
? input.to >= constants.NodeType.CustomNode | ||
? input.to + offset - constants.NodeType.CustomNode + 1 | ||
: input.to + offset | ||
: input.to; | ||
return { | ||
to: !isTerminalNode(input.to) ? input.to > 2 ? input.to + offset - 2 : input.to + offset : input.to, | ||
to, | ||
reducer: input.reducer, | ||
@@ -409,3 +354,3 @@ }; | ||
} | ||
function execute(store, callback, state, segment) { | ||
function execute(store, callback, state, segment, segmentIndex) { | ||
// TypeScript's control flow can't properly narrow | ||
@@ -415,21 +360,8 @@ // generic conditionals for some mysterious reason | ||
const [name, ...args] = callback; | ||
return store[name](state, segment, ...args); | ||
return store[name](state, segment, segmentIndex, ...args); | ||
} | ||
else { | ||
return store[callback](state, segment); | ||
return store[callback](state, segment, segmentIndex); | ||
} | ||
} | ||
function suggest(callback, state) { | ||
const fn = Array.isArray(callback) | ||
? tests[callback[0]] | ||
: tests[callback]; | ||
// @ts-ignore | ||
if (typeof fn.suggest === `undefined`) | ||
return null; | ||
const args = Array.isArray(callback) | ||
? callback.slice(1) | ||
: []; | ||
// @ts-ignore | ||
return fn.suggest(state, ...args); | ||
} | ||
const tests = { | ||
@@ -445,15 +377,15 @@ always: () => { | ||
}, | ||
isOption: (state, segment, name, hidden) => { | ||
isOption: (state, segment, segmentIndex, name) => { | ||
return !state.ignoreOptions && segment === name; | ||
}, | ||
isBatchOption: (state, segment, names) => { | ||
return !state.ignoreOptions && constants.BATCH_REGEX.test(segment) && [...segment.slice(1)].every(name => names.includes(`-${name}`)); | ||
isBatchOption: (state, segment, segmentIndex, names) => { | ||
return !state.ignoreOptions && constants.BATCH_REGEX.test(segment) && [...segment.slice(1)].every(name => names.has(`-${name}`)); | ||
}, | ||
isBoundOption: (state, segment, names, options) => { | ||
isBoundOption: (state, segment, segmentIndex, names, options) => { | ||
const optionParsing = segment.match(constants.BINDING_REGEX); | ||
return !state.ignoreOptions && !!optionParsing && constants.OPTION_REGEX.test(optionParsing[1]) && names.includes(optionParsing[1]) | ||
return !state.ignoreOptions && !!optionParsing && constants.OPTION_REGEX.test(optionParsing[1]) && names.has(optionParsing[1]) | ||
// Disallow bound options with no arguments (i.e. booleans) | ||
&& options.filter(opt => opt.names.includes(optionParsing[1])).every(opt => opt.allowBinding); | ||
&& options.filter(opt => opt.nameSet.includes(optionParsing[1])).every(opt => opt.allowBinding); | ||
}, | ||
isNegatedOption: (state, segment, name) => { | ||
isNegatedOption: (state, segment, segmentIndex, name) => { | ||
return !state.ignoreOptions && segment === `--no-${name.slice(2)}`; | ||
@@ -464,4 +396,4 @@ }, | ||
}, | ||
isUnsupportedOption: (state, segment, names) => { | ||
return !state.ignoreOptions && segment.startsWith(`-`) && constants.OPTION_REGEX.test(segment) && !names.includes(segment); | ||
isUnsupportedOption: (state, segment, segmentIndex, names) => { | ||
return !state.ignoreOptions && segment.startsWith(`-`) && constants.OPTION_REGEX.test(segment) && !names.has(segment); | ||
}, | ||
@@ -472,53 +404,79 @@ isInvalidOption: (state, segment) => { | ||
}; | ||
// @ts-ignore | ||
tests.isOption.suggest = (state, name, hidden = true) => { | ||
return !hidden ? [name] : null; | ||
}; | ||
const reducers = { | ||
setCandidateState: (state, segment, candidateState) => { | ||
setCandidateState: (state, segment, segmentIndex, candidateState) => { | ||
return { ...state, ...candidateState }; | ||
}, | ||
setSelectedIndex: (state, segment, index) => { | ||
setSelectedIndex: (state, segment, segmentIndex, index) => { | ||
return { ...state, selectedIndex: index }; | ||
}, | ||
pushBatch: (state, segment) => { | ||
return { ...state, options: state.options.concat([...segment.slice(1)].map(name => ({ name: `-${name}`, value: true }))) }; | ||
pushBatch: (state, segment, segmentIndex, names) => { | ||
const options = state.options.slice(); | ||
const tokens = state.tokens.slice(); | ||
for (let t = 1; t < segment.length; ++t) { | ||
const name = names.get(`-${segment[t]}`); | ||
const slice = t === 1 ? [0, 2] : [t, t + 1]; | ||
options.push({ name, value: true }); | ||
tokens.push({ segmentIndex, type: `option`, option: name, slice }); | ||
} | ||
return { ...state, options, tokens }; | ||
}, | ||
pushBound: (state, segment) => { | ||
pushBound: (state, segment, segmentIndex) => { | ||
const [, name, value] = segment.match(constants.BINDING_REGEX); | ||
return { ...state, options: state.options.concat({ name, value }) }; | ||
const options = state.options.concat({ name, value }); | ||
const tokens = state.tokens.concat([ | ||
{ segmentIndex, type: `option`, slice: [0, name.length], option: name }, | ||
{ segmentIndex, type: `assign`, slice: [name.length, name.length + 1] }, | ||
{ segmentIndex, type: `value`, slice: [name.length + 1, name.length + value.length + 1] }, | ||
]); | ||
return { ...state, options, tokens }; | ||
}, | ||
pushPath: (state, segment) => { | ||
return { ...state, path: state.path.concat(segment) }; | ||
pushPath: (state, segment, segmentIndex) => { | ||
const path = state.path.concat(segment); | ||
const tokens = state.tokens.concat({ segmentIndex, type: `path` }); | ||
return { ...state, path, tokens }; | ||
}, | ||
pushPositional: (state, segment) => { | ||
return { ...state, positionals: state.positionals.concat({ value: segment, extra: false }) }; | ||
pushPositional: (state, segment, segmentIndex) => { | ||
const positionals = state.positionals.concat({ value: segment, extra: false }); | ||
const tokens = state.tokens.concat({ segmentIndex, type: `positional` }); | ||
return { ...state, positionals, tokens }; | ||
}, | ||
pushExtra: (state, segment) => { | ||
return { ...state, positionals: state.positionals.concat({ value: segment, extra: true }) }; | ||
pushExtra: (state, segment, segmentIndex) => { | ||
const positionals = state.positionals.concat({ value: segment, extra: true }); | ||
const tokens = state.tokens.concat({ segmentIndex, type: `positional` }); | ||
return { ...state, positionals, tokens }; | ||
}, | ||
pushExtraNoLimits: (state, segment) => { | ||
return { ...state, positionals: state.positionals.concat({ value: segment, extra: NoLimits }) }; | ||
pushExtraNoLimits: (state, segment, segmentIndex) => { | ||
const positionals = state.positionals.concat({ value: segment, extra: NoLimits }); | ||
const tokens = state.tokens.concat({ segmentIndex, type: `positional` }); | ||
return { ...state, positionals, tokens }; | ||
}, | ||
pushTrue: (state, segment, name = segment) => { | ||
return { ...state, options: state.options.concat({ name: segment, value: true }) }; | ||
pushTrue: (state, segment, segmentIndex, name) => { | ||
const options = state.options.concat({ name, value: true }); | ||
const tokens = state.tokens.concat({ segmentIndex, type: `option`, option: name }); | ||
return { ...state, options, tokens }; | ||
}, | ||
pushFalse: (state, segment, name = segment) => { | ||
return { ...state, options: state.options.concat({ name, value: false }) }; | ||
pushFalse: (state, segment, segmentIndex, name) => { | ||
const options = state.options.concat({ name, value: false }); | ||
const tokens = state.tokens.concat({ segmentIndex, type: `option`, option: name }); | ||
return { ...state, options, tokens }; | ||
}, | ||
pushUndefined: (state, segment) => { | ||
return { ...state, options: state.options.concat({ name: segment, value: undefined }) }; | ||
pushUndefined: (state, segment, segmentIndex, name) => { | ||
const options = state.options.concat({ name: segment, value: undefined }); | ||
const tokens = state.tokens.concat({ segmentIndex, type: `option`, option: segment }); | ||
return { ...state, options, tokens }; | ||
}, | ||
pushStringValue: (state, segment) => { | ||
pushStringValue: (state, segment, segmentIndex) => { | ||
var _a; | ||
const copy = { ...state, options: [...state.options] }; | ||
const lastOption = state.options[state.options.length - 1]; | ||
const options = state.options.slice(); | ||
const tokens = state.tokens.concat({ segmentIndex, type: `value` }); | ||
lastOption.value = ((_a = lastOption.value) !== null && _a !== void 0 ? _a : []).concat([segment]); | ||
return copy; | ||
return { ...state, options, tokens }; | ||
}, | ||
setStringValue: (state, segment) => { | ||
const copy = { ...state, options: [...state.options] }; | ||
setStringValue: (state, segment, segmentIndex) => { | ||
const lastOption = state.options[state.options.length - 1]; | ||
const options = state.options.slice(); | ||
const tokens = state.tokens.concat({ segmentIndex, type: `value` }); | ||
lastOption.value = segment; | ||
return copy; | ||
return { ...state, options, tokens }; | ||
}, | ||
@@ -528,3 +486,3 @@ inhibateOptions: (state) => { | ||
}, | ||
useHelp: (state, segment, command) => { | ||
useHelp: (state, segment, segmentIndex, command) => { | ||
const [, /* name */ , index] = segment.match(constants.HELP_REGEX); | ||
@@ -538,4 +496,4 @@ if (typeof index !== `undefined`) { | ||
}, | ||
setError: (state, segment, errorMessage) => { | ||
if (segment === constants.END_OF_INPUT) { | ||
setError: (state, segment, segmentIndex, errorMessage) => { | ||
if (segment === constants.SpecialToken.EndOfInput || segment === constants.SpecialToken.EndOfPartialInput) { | ||
return { ...state, errorMessage: `${errorMessage}.` }; | ||
@@ -556,3 +514,3 @@ } | ||
constructor(cliIndex, cliOpts) { | ||
this.allOptionNames = []; | ||
this.allOptionNames = new Map(); | ||
this.arity = { leading: [], trailing: [], extra: [], proxy: false }; | ||
@@ -598,3 +556,3 @@ this.options = []; | ||
} | ||
addOption({ names, description, arity = 0, hidden = false, required = false, allowBinding = true }) { | ||
addOption({ names: nameSet, description, arity = 0, hidden = false, required = false, allowBinding = true }) { | ||
if (!allowBinding && arity > 1) | ||
@@ -606,4 +564,8 @@ throw new Error(`The arity cannot be higher than 1 when the option only supports the --arg=value syntax`); | ||
throw new Error(`The arity must be positive, got ${arity}`); | ||
this.allOptionNames.push(...names); | ||
this.options.push({ names, description, arity, hidden, required, allowBinding }); | ||
const preferredName = nameSet.reduce((longestName, name) => { | ||
return name.length > longestName.length ? name : longestName; | ||
}, ``); | ||
for (const name of nameSet) | ||
this.allOptionNames.set(name, preferredName); | ||
this.options.push({ preferredName, nameSet, description, arity, hidden, required, allowBinding }); | ||
} | ||
@@ -619,3 +581,3 @@ setContext(context) { | ||
if (detailed) { | ||
for (const { names, arity, hidden, description, required } of this.options) { | ||
for (const { preferredName, nameSet, arity, hidden, description, required } of this.options) { | ||
if (hidden) | ||
@@ -626,5 +588,5 @@ continue; | ||
args.push(` #${t}`); | ||
const definition = `${names.join(`,`)}${args.join(``)}`; | ||
const definition = `${nameSet.join(`,`)}${args.join(``)}`; | ||
if (!inlineOptions && description) { | ||
detailedOptionList.push({ definition, description, required }); | ||
detailedOptionList.push({ preferredName, nameSet, definition, description, required }); | ||
} | ||
@@ -649,9 +611,9 @@ else { | ||
const machine = makeStateMachine(); | ||
let firstNode = constants.NODE_INITIAL; | ||
let firstNode = constants.NodeType.InitialNode; | ||
const candidateUsage = this.usage().usage; | ||
const requiredOptions = this.options | ||
.filter(opt => opt.required) | ||
.map(opt => opt.names); | ||
.map(opt => opt.nameSet); | ||
firstNode = injectNode(machine, makeNode()); | ||
registerStatic(machine, constants.NODE_INITIAL, constants.START_OF_INPUT, firstNode, [`setCandidateState`, { candidateUsage, requiredOptions }]); | ||
registerStatic(machine, constants.NodeType.InitialNode, constants.SpecialToken.StartOfInput, firstNode, [`setCandidateState`, { candidateUsage, requiredOptions }]); | ||
const positionalArgument = this.arity.proxy | ||
@@ -683,7 +645,9 @@ ? `always` | ||
registerDynamic(machine, helpNode, `always`, helpNode, `pushExtra`); | ||
registerStatic(machine, helpNode, constants.END_OF_INPUT, constants.NODE_SUCCESS, [`setSelectedIndex`, constants.HELP_COMMAND_INDEX]); | ||
registerStatic(machine, helpNode, constants.SpecialToken.EndOfInput, constants.NodeType.SuccessNode, [`setSelectedIndex`, constants.HELP_COMMAND_INDEX]); | ||
this.registerOptions(machine, lastPathNode); | ||
} | ||
if (this.arity.leading.length > 0) | ||
registerStatic(machine, lastPathNode, constants.END_OF_INPUT, constants.NODE_ERRORED, [`setError`, `Not enough positional arguments`]); | ||
if (this.arity.leading.length > 0) { | ||
registerStatic(machine, lastPathNode, constants.SpecialToken.EndOfInput, constants.NodeType.ErrorNode, [`setError`, `Not enough positional arguments`]); | ||
registerStatic(machine, lastPathNode, constants.SpecialToken.EndOfPartialInput, constants.NodeType.SuccessNode, [`setSelectedIndex`, this.cliIndex]); | ||
} | ||
let lastLeadingNode = lastPathNode; | ||
@@ -694,4 +658,6 @@ for (let t = 0; t < this.arity.leading.length; ++t) { | ||
this.registerOptions(machine, nextLeadingNode); | ||
if (this.arity.trailing.length > 0 || t + 1 !== this.arity.leading.length) | ||
registerStatic(machine, nextLeadingNode, constants.END_OF_INPUT, constants.NODE_ERRORED, [`setError`, `Not enough positional arguments`]); | ||
if (this.arity.trailing.length > 0 || t + 1 !== this.arity.leading.length) { | ||
registerStatic(machine, nextLeadingNode, constants.SpecialToken.EndOfInput, constants.NodeType.ErrorNode, [`setError`, `Not enough positional arguments`]); | ||
registerStatic(machine, nextLeadingNode, constants.SpecialToken.EndOfPartialInput, constants.NodeType.SuccessNode, [`setSelectedIndex`, this.cliIndex]); | ||
} | ||
registerDynamic(machine, lastLeadingNode, `isNotOptionLike`, nextLeadingNode, `pushPositional`); | ||
@@ -724,4 +690,6 @@ lastLeadingNode = nextLeadingNode; | ||
} | ||
if (this.arity.trailing.length > 0) | ||
registerStatic(machine, lastExtraNode, constants.END_OF_INPUT, constants.NODE_ERRORED, [`setError`, `Not enough positional arguments`]); | ||
if (this.arity.trailing.length > 0) { | ||
registerStatic(machine, lastExtraNode, constants.SpecialToken.EndOfInput, constants.NodeType.ErrorNode, [`setError`, `Not enough positional arguments`]); | ||
registerStatic(machine, lastExtraNode, constants.SpecialToken.EndOfPartialInput, constants.NodeType.SuccessNode, [`setSelectedIndex`, this.cliIndex]); | ||
} | ||
let lastTrailingNode = lastExtraNode; | ||
@@ -732,9 +700,12 @@ for (let t = 0; t < this.arity.trailing.length; ++t) { | ||
this.registerOptions(machine, nextTrailingNode); | ||
if (t + 1 < this.arity.trailing.length) | ||
registerStatic(machine, nextTrailingNode, constants.END_OF_INPUT, constants.NODE_ERRORED, [`setError`, `Not enough positional arguments`]); | ||
if (t + 1 < this.arity.trailing.length) { | ||
registerStatic(machine, nextTrailingNode, constants.SpecialToken.EndOfInput, constants.NodeType.ErrorNode, [`setError`, `Not enough positional arguments`]); | ||
registerStatic(machine, nextTrailingNode, constants.SpecialToken.EndOfPartialInput, constants.NodeType.SuccessNode, [`setSelectedIndex`, this.cliIndex]); | ||
} | ||
registerDynamic(machine, lastTrailingNode, `isNotOptionLike`, nextTrailingNode, `pushPositional`); | ||
lastTrailingNode = nextTrailingNode; | ||
} | ||
registerDynamic(machine, lastTrailingNode, positionalArgument, constants.NODE_ERRORED, [`setError`, `Extraneous positional argument`]); | ||
registerStatic(machine, lastTrailingNode, constants.END_OF_INPUT, constants.NODE_SUCCESS, [`setSelectedIndex`, this.cliIndex]); | ||
registerDynamic(machine, lastTrailingNode, positionalArgument, constants.NodeType.ErrorNode, [`setError`, `Extraneous positional argument`]); | ||
registerStatic(machine, lastTrailingNode, constants.SpecialToken.EndOfInput, constants.NodeType.SuccessNode, [`setSelectedIndex`, this.cliIndex]); | ||
registerStatic(machine, lastTrailingNode, constants.SpecialToken.EndOfPartialInput, constants.NodeType.SuccessNode, [`setSelectedIndex`, this.cliIndex]); | ||
} | ||
@@ -748,15 +719,12 @@ return { | ||
registerDynamic(machine, node, [`isOption`, `--`], node, `inhibateOptions`); | ||
registerDynamic(machine, node, [`isBatchOption`, this.allOptionNames], node, `pushBatch`); | ||
registerDynamic(machine, node, [`isBatchOption`, this.allOptionNames], node, [`pushBatch`, this.allOptionNames]); | ||
registerDynamic(machine, node, [`isBoundOption`, this.allOptionNames, this.options], node, `pushBound`); | ||
registerDynamic(machine, node, [`isUnsupportedOption`, this.allOptionNames], constants.NODE_ERRORED, [`setError`, `Unsupported option name`]); | ||
registerDynamic(machine, node, [`isInvalidOption`], constants.NODE_ERRORED, [`setError`, `Invalid option name`]); | ||
registerDynamic(machine, node, [`isUnsupportedOption`, this.allOptionNames], constants.NodeType.ErrorNode, [`setError`, `Unsupported option name`]); | ||
registerDynamic(machine, node, [`isInvalidOption`], constants.NodeType.ErrorNode, [`setError`, `Invalid option name`]); | ||
for (const option of this.options) { | ||
const longestName = option.names.reduce((longestName, name) => { | ||
return name.length > longestName.length ? name : longestName; | ||
}, ``); | ||
if (option.arity === 0) { | ||
for (const name of option.names) { | ||
registerDynamic(machine, node, [`isOption`, name, option.hidden || name !== longestName], node, `pushTrue`); | ||
for (const name of option.nameSet) { | ||
registerDynamic(machine, node, [`isOption`, name], node, [`pushTrue`, option.preferredName]); | ||
if (name.startsWith(`--`) && !name.startsWith(`--no-`)) { | ||
registerDynamic(machine, node, [`isNegatedOption`, name], node, [`pushFalse`, name]); | ||
registerDynamic(machine, node, [`isNegatedOption`, name], node, [`pushFalse`, option.preferredName]); | ||
} | ||
@@ -769,4 +737,4 @@ } | ||
// We register transitions from the starting node to this new node | ||
for (const name of option.names) | ||
registerDynamic(machine, node, [`isOption`, name, option.hidden || name !== longestName], lastNode, `pushUndefined`); | ||
for (const name of option.nameSet) | ||
registerDynamic(machine, node, [`isOption`, name], lastNode, [`pushUndefined`, option.preferredName]); | ||
// For each argument, we inject a new node at the end and we | ||
@@ -776,5 +744,6 @@ // register a transition from the current node to this new node | ||
const nextNode = injectNode(machine, makeNode()); | ||
// We can provide better errors when another option or END_OF_INPUT is encountered | ||
registerStatic(machine, lastNode, constants.END_OF_INPUT, constants.NODE_ERRORED, `setOptionArityError`); | ||
registerDynamic(machine, lastNode, `isOptionLike`, constants.NODE_ERRORED, `setOptionArityError`); | ||
// We can provide better errors when another option or EndOfInput is encountered | ||
registerStatic(machine, lastNode, constants.SpecialToken.EndOfInput, constants.NodeType.ErrorNode, `setOptionArityError`); | ||
registerStatic(machine, lastNode, constants.SpecialToken.EndOfPartialInput, constants.NodeType.ErrorNode, `setOptionArityError`); | ||
registerDynamic(machine, lastNode, `isOptionLike`, constants.NodeType.ErrorNode, `setOptionArityError`); | ||
// If the option has a single argument, no need to store it in an array | ||
@@ -830,8 +799,8 @@ const action = option.arity === 1 | ||
contexts, | ||
process: (input) => { | ||
return runMachine(machine, input); | ||
process: (input, { partial } = {}) => { | ||
const endToken = partial | ||
? constants.SpecialToken.EndOfPartialInput | ||
: constants.SpecialToken.EndOfInput; | ||
return runMachine(machine, input, { endToken }); | ||
}, | ||
suggest: (input, partial) => { | ||
return suggestMachine(machine, input, partial); | ||
}, | ||
}; | ||
@@ -862,4 +831,3 @@ } | ||
exports.simplifyMachine = simplifyMachine; | ||
exports.suggest = suggest; | ||
exports.tests = tests; | ||
exports.trimSmallerBranches = trimSmallerBranches; |
@@ -57,3 +57,3 @@ 'use strict'; | ||
const whileRunning = (input) => `While running ${input.filter(token => { | ||
return token !== constants.END_OF_INPUT; | ||
return token !== constants.SpecialToken.EndOfInput && token !== constants.SpecialToken.EndOfPartialInput; | ||
}).map(token => { | ||
@@ -60,0 +60,0 @@ const json = JSON.stringify(token); |
@@ -15,3 +15,3 @@ { | ||
], | ||
"version": "3.2.1", | ||
"version": "4.0.0-rc.1", | ||
"main": "lib/advanced/index", | ||
@@ -18,0 +18,0 @@ "license": "MIT", |
@@ -5,3 +5,3 @@ # <img src="./logo.svg" height="25" /> Clipanion | ||
[![npm version](https://img.shields.io/npm/v/clipanion.svg)](https://yarnpkg.com/package/clipanion) [![Licence](https://img.shields.io/npm/l/clipanion.svg)](https://github.com/arcanis/clipanion#license-mit) [![Yarn](https://img.shields.io/badge/developed%20with-Yarn%202-blue)](https://github.com/yarnpkg/berry) | ||
[![npm version](https://img.shields.io/npm/v/clipanion.svg)](https://yarnpkg.com/package/clipanion) [![Licence](https://img.shields.io/npm/l/clipanion.svg)](https://github.com/arcanis/clipanion#license-mit) [![Yarn](https://img.shields.io/github/package-json/packageManager/arcanis/clipanion)](https://github.com/yarnpkg/berry) | ||
@@ -8,0 +8,0 @@ ## Installation |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
230946
72
5269
1
12