Comparing version 2.1.6 to 2.2.1
@@ -40,3 +40,3 @@ /// <reference types="node" /> | ||
runExit(input: Command<Context> | string[], context: Context): Promise<void>; | ||
suggestFor(input: string[]): Promise<Set<unknown>>; | ||
suggestFor(input: string[], partial: boolean): Promise<string[][]>; | ||
definitions(): { | ||
@@ -43,0 +43,0 @@ path: string; |
@@ -96,5 +96,5 @@ "use strict"; | ||
} | ||
async suggestFor(input) { | ||
async suggestFor(input, partial) { | ||
const { contexts, process, suggest } = this.builder.compile(); | ||
return suggest(input); | ||
return suggest(input, partial); | ||
} | ||
@@ -101,0 +101,0 @@ definitions() { |
@@ -29,2 +29,3 @@ export declare const NODE_INITIAL = 0; | ||
}[]; | ||
remainder: string | null; | ||
selectedIndex: number | null; | ||
@@ -39,3 +40,3 @@ }; | ||
}): void; | ||
export declare function runMachineInternal(machine: StateMachine, input: string[]): { | ||
export declare function runMachineInternal(machine: StateMachine, input: string[], partial?: boolean): { | ||
node: number; | ||
@@ -83,3 +84,3 @@ state: RunState; | ||
isNotOptionLike: (state: RunState, segment: string) => boolean; | ||
isOption: (state: RunState, segment: string, name: string) => boolean; | ||
isOption: (state: RunState, segment: string, name: string, hidden: boolean) => boolean; | ||
isBatchOption: (state: RunState, segment: string, names: string[]) => boolean; | ||
@@ -106,2 +107,3 @@ isBoundOption: (state: RunState, segment: string, names: string[]) => boolean; | ||
}[]; | ||
remainder: string | null; | ||
selectedIndex: number | null; | ||
@@ -123,2 +125,3 @@ }; | ||
}[]; | ||
remainder: string | null; | ||
}; | ||
@@ -138,2 +141,3 @@ pushBatch: (state: RunState, segment: string) => { | ||
}[]; | ||
remainder: string | null; | ||
selectedIndex: number | null; | ||
@@ -154,2 +158,3 @@ }; | ||
}[]; | ||
remainder: string | null; | ||
selectedIndex: number | null; | ||
@@ -170,2 +175,3 @@ }; | ||
}[]; | ||
remainder: string | null; | ||
selectedIndex: number | null; | ||
@@ -186,2 +192,3 @@ }; | ||
path: string[]; | ||
remainder: string | null; | ||
selectedIndex: number | null; | ||
@@ -202,2 +209,3 @@ }; | ||
path: string[]; | ||
remainder: string | null; | ||
selectedIndex: number | null; | ||
@@ -218,2 +226,3 @@ }; | ||
}[]; | ||
remainder: string | null; | ||
selectedIndex: number | null; | ||
@@ -234,2 +243,3 @@ }; | ||
}[]; | ||
remainder: string | null; | ||
selectedIndex: number | null; | ||
@@ -250,2 +260,3 @@ }; | ||
}[]; | ||
remainder: string | null; | ||
selectedIndex: number | null; | ||
@@ -266,2 +277,3 @@ }; | ||
}[]; | ||
remainder: string | null; | ||
selectedIndex: number | null; | ||
@@ -282,2 +294,3 @@ }; | ||
}[]; | ||
remainder: string | null; | ||
selectedIndex: number | null; | ||
@@ -298,2 +311,3 @@ }; | ||
}[]; | ||
remainder: string | null; | ||
selectedIndex: number | null; | ||
@@ -314,2 +328,3 @@ }; | ||
}[]; | ||
remainder: string | null; | ||
selectedIndex: number | null; | ||
@@ -374,3 +389,3 @@ }; | ||
process: (input: string[]) => RunState; | ||
suggest: (input: string[]) => Set<unknown>; | ||
suggest: (input: string[], partial: boolean) => string[][]; | ||
}; | ||
@@ -385,5 +400,5 @@ constructor({ binaryName }?: Partial<CliOptions>); | ||
process: (input: string[]) => RunState; | ||
suggest: (input: string[]) => Set<unknown>; | ||
suggest: (input: string[], partial: boolean) => string[][]; | ||
}; | ||
} | ||
export {}; |
128
lib/core.js
@@ -105,3 +105,3 @@ "use strict"; | ||
exports.debugMachine = debugMachine; | ||
function runMachineInternal(machine, input) { | ||
function runMachineInternal(machine, input, partial = false) { | ||
debug(`Running a vm on ${JSON.stringify(input)}`); | ||
@@ -115,6 +115,9 @@ let branches = [{ node: exports.NODE_INITIAL, state: { | ||
positionals: [], | ||
remainder: null, | ||
selectedIndex: null, | ||
} }]; | ||
debugMachine(machine, { prefix: ` ` }); | ||
for (const segment of [exports.START_OF_INPUT, ...input]) { | ||
const tokens = [exports.START_OF_INPUT, ...input]; | ||
for (let t = 0; t < tokens.length; ++t) { | ||
const segment = tokens[t]; | ||
debug(` Processing ${JSON.stringify(segment)}`); | ||
@@ -130,11 +133,37 @@ const nextBranches = []; | ||
console.assert(nodeDef.shortcuts.length === 0, `Shortcuts should have been eliminated by now`); | ||
if (Object.prototype.hasOwnProperty.call(nodeDef.statics, segment)) { | ||
const transitions = nodeDef.statics[segment]; | ||
for (const { to, reducer } of transitions) { | ||
nextBranches.push({ node: to, state: typeof reducer !== `undefined` ? execute(exports.reducers, reducer, state, segment) : state }); | ||
debug(` Static transition to ${to} found`); | ||
const hasExactMatch = Object.prototype.hasOwnProperty.call(nodeDef.statics, segment); | ||
if (!partial || t < tokens.length - 1 || hasExactMatch) { | ||
if (hasExactMatch) { | ||
const transitions = nodeDef.statics[segment]; | ||
for (const { to, reducer } of transitions) { | ||
nextBranches.push({ node: to, state: typeof reducer !== `undefined` ? execute(exports.reducers, reducer, state, segment) : state }); | ||
debug(` Static transition to ${to} found`); | ||
} | ||
} | ||
else { | ||
debug(` No static transition found`); | ||
} | ||
} | ||
else { | ||
debug(` No static transition found`); | ||
let hasMatches = false; | ||
for (const candidate of Object.keys(nodeDef.statics)) { | ||
if (!candidate.startsWith(segment)) | ||
continue; | ||
if (segment === candidate) { | ||
for (const { to, reducer } of nodeDef.statics[candidate]) { | ||
nextBranches.push({ node: to, state: typeof reducer !== `undefined` ? execute(exports.reducers, reducer, state, segment) : state }); | ||
debug(` Static transition to ${to} found`); | ||
} | ||
} | ||
else { | ||
for (const { to, reducer } of nodeDef.statics[candidate]) { | ||
nextBranches.push({ node: to, state: Object.assign(Object.assign({}, state), { remainder: candidate.slice(segment.length) }) }); | ||
debug(` Static transition to ${to} found (partial match)`); | ||
} | ||
} | ||
hasMatches = true; | ||
} | ||
if (!hasMatches) { | ||
debug(` No partial static transition found`); | ||
} | ||
} | ||
@@ -176,9 +205,57 @@ if (segment !== exports.END_OF_INPUT) { | ||
exports.runMachineInternal = runMachineInternal; | ||
function suggestMachine(machine, input) { | ||
const branches = runMachineInternal(machine, input); | ||
const suggestions = new Set(); | ||
function checkIfNodeIsFinished(node, state) { | ||
if (state.selectedIndex !== null) | ||
return true; | ||
if (Object.prototype.hasOwnProperty.call(node.statics, exports.END_OF_INPUT)) | ||
for (const { to } of node.statics[exports.END_OF_INPUT]) | ||
if (to === exports.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); | ||
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]; | ||
for (const candidate of Object.keys(nodeDef.statics)) | ||
suggestions.add(candidate); | ||
const isFinished = checkIfNodeIsFinished(nodeDef, state); | ||
for (const [candidate, transitions] of Object.entries(nodeDef.statics)) | ||
if ((isFinished && candidate !== exports.END_OF_INPUT) || (!candidate.startsWith(`-`) && transitions.some(({ reducer }) => reducer === `pushPath`))) | ||
traverseSuggestion([...prefix, candidate], node); | ||
if (!isFinished) | ||
continue; | ||
for (const [test, { to }] of nodeDef.dynamics) { | ||
@@ -191,8 +268,7 @@ if (to === exports.NODE_ERRORED) | ||
for (const token of tokens) { | ||
suggestions.add(token); | ||
traverseSuggestion([...prefix, token], node); | ||
} | ||
} | ||
} | ||
suggestions.delete(exports.END_OF_INPUT); | ||
return suggestions; | ||
return [...suggestions].sort(); | ||
} | ||
@@ -268,2 +344,3 @@ function runMachine(machine, input) { | ||
options: helps, | ||
remainder: null, | ||
selectedIndex: exports.HELP_COMMAND_INDEX, | ||
@@ -351,3 +428,3 @@ }); | ||
}, | ||
isOption: (state, segment, name) => { | ||
isOption: (state, segment, name, hidden) => { | ||
return !state.ignoreOptions && segment === name; | ||
@@ -376,4 +453,4 @@ }, | ||
// @ts-ignore | ||
exports.tests.isOption.suggest = (state, name) => { | ||
return [name]; | ||
exports.tests.isOption.suggest = (state, name, hidden = true) => { | ||
return !hidden ? [name] : null; | ||
}; | ||
@@ -608,7 +685,10 @@ exports.reducers = { | ||
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], node, `pushTrue`); | ||
registerDynamic(machine, node, [`isOption`, name, option.hidden || name !== longestName], node, `pushTrue`); | ||
if (name.startsWith(`--`)) { | ||
registerDynamic(machine, node, [`isNegatedOption`, name], node, [`pushFalse`, name]); | ||
registerDynamic(machine, node, [`isNegatedOption`, name, option.hidden || name !== longestName], node, [`pushFalse`, name]); | ||
} | ||
@@ -621,3 +701,3 @@ } | ||
for (const name of option.names) { | ||
registerDynamic(machine, node, [`isOption`, name], argNode, `pushUndefined`); | ||
registerDynamic(machine, node, [`isOption`, name, option.hidden || name !== longestName], argNode, `pushUndefined`); | ||
} | ||
@@ -671,4 +751,4 @@ } | ||
}, | ||
suggest: (input) => { | ||
return suggestMachine(machine, input); | ||
suggest: (input, partial) => { | ||
return suggestMachine(machine, input, partial); | ||
}, | ||
@@ -675,0 +755,0 @@ }; |
{ | ||
"name": "clipanion", | ||
"version": "2.1.6", | ||
"version": "2.2.1", | ||
"main": "lib/advanced", | ||
@@ -5,0 +5,0 @@ "license": "MIT", |
@@ -34,3 +34,3 @@ # <img src="./logo.svg" height="25" /> Clipanion | ||
```ts | ||
import {Cli, Command, Context} from 'clipanion'; | ||
import {Cli, Command} from 'clipanion'; | ||
import * as yup from 'yup'; | ||
@@ -46,7 +46,7 @@ | ||
@Command.Path(`greet`) | ||
async execute(cli: Cli, context: Context) { | ||
async execute() { | ||
if (typeof this.name === `undefined`) { | ||
context.stdout.write(`You're not registered.\n`); | ||
this.context.stdout.write(`You're not registered.\n`); | ||
} else { | ||
context.stdout.write(`Hello, ${this.name}!\n`); | ||
this.context.stdout.write(`Hello, ${this.name}!\n`); | ||
} | ||
@@ -64,3 +64,3 @@ } | ||
@Command.Path(`fibo`) | ||
async execute(cli: Cli, context: Context) { | ||
async execute() { | ||
// ... | ||
@@ -97,3 +97,3 @@ } | ||
class GreetCommand extends Command { | ||
async execute(cli, context) { | ||
async execute() { | ||
// ... | ||
@@ -139,4 +139,8 @@ } | ||
Specifies that the command | ||
Specifies that the command accepts a boolean flag as an option. | ||
#### `@Command.Array(optionNames: string)` | ||
Specifies that the command accepts a set of string arguments (`--arg value1 --arg value2`). | ||
## Command Help Pages | ||
@@ -143,0 +147,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
91074
1941
247