Comparing version 2.0.0-rc.0 to 2.0.0-rc.1
/// <reference types="node" /> | ||
import { Readable, Writable } from 'stream'; | ||
import * as core from '../core'; | ||
import { Command } from './Command'; | ||
export interface Context { | ||
export interface DefaultContext { | ||
stdin: Readable; | ||
@@ -9,3 +10,11 @@ stdout: Writable; | ||
} | ||
export declare class Cli { | ||
interface CommandFactory<Context extends DefaultContext> { | ||
new (): Command<Context>; | ||
compile(): core.Command<(cli: Cli<Context>, context: Context) => Command<Context>>; | ||
} | ||
export interface MiniCli<Context extends DefaultContext> { | ||
process(argv: string[]): Command<Context>; | ||
run(argv: string[], subContext?: Partial<Context>): Promise<number | void>; | ||
} | ||
export declare class Cli<Context extends DefaultContext = DefaultContext> { | ||
private readonly core; | ||
@@ -16,9 +25,10 @@ readonly name?: string; | ||
}); | ||
register(command: typeof Command): this; | ||
start(): import("../core/Machine").Machine<Command>; | ||
process(argv: string[]): Command; | ||
run(argv: string[], context?: Partial<Context>): Promise<number>; | ||
runExit(argv: string[], context?: Partial<Context>): Promise<void>; | ||
register(command: CommandFactory<Context>): this; | ||
start(): import("../core/Machine").Machine<(cli: Cli<Context>, context: Context) => Command<Context>>; | ||
process(argv: string[]): (cli: Cli<Context>, context: Context) => Command<Context>; | ||
run(argv: string[], context: Context): Promise<number>; | ||
runExit(argv: string[], context: Context): Promise<void>; | ||
private usage; | ||
private error; | ||
} | ||
export {}; |
@@ -32,13 +32,8 @@ "use strict"; | ||
async run(argv, context) { | ||
const fullContext = Object.assign({ | ||
stdin: process.stdin, | ||
stdout: process.stdout, | ||
stderr: process.stderr, | ||
}, context); | ||
const miniApi = { | ||
const miniCli = { | ||
process: (argv) => { | ||
return this.process(argv); | ||
return this.process(argv)(this, context); | ||
}, | ||
run: (argv, subContext) => { | ||
return this.run(argv, Object.assign({}, fullContext, subContext)); | ||
return this.run(argv, Object.assign({}, context, subContext)); | ||
}, | ||
@@ -48,3 +43,3 @@ }; | ||
try { | ||
command = this.core.process(argv); | ||
command = this.core.process(argv)(miniCli, context); | ||
} | ||
@@ -54,6 +49,6 @@ catch (error) { | ||
error.setBinaryName(this.name); | ||
return this.error(fullContext, error, null); | ||
return this.error(context, error, null); | ||
} | ||
if (command.help) { | ||
fullContext.stdout.write(this.usage(command, true)); | ||
context.stdout.write(this.usage(command, true)); | ||
return 0; | ||
@@ -63,6 +58,6 @@ } | ||
try { | ||
exitCode = await command.execute(miniApi, fullContext); | ||
exitCode = await command.execute(); | ||
} | ||
catch (error) { | ||
return this.error(fullContext, error, command); | ||
return this.error(context, error, command); | ||
} | ||
@@ -69,0 +64,0 @@ if (typeof exitCode === `undefined`) |
import * as core from '../core'; | ||
import { Cli, Context } from './Cli'; | ||
import { Cli, DefaultContext, MiniCli } from './Cli'; | ||
export declare type Help = { | ||
@@ -15,10 +15,13 @@ description: string; | ||
}; | ||
export declare abstract class Command { | ||
export declare abstract class Command<Context extends DefaultContext = DefaultContext> { | ||
static meta: Meta | undefined; | ||
static getMeta(from?: Command): Meta; | ||
static getMeta<Context extends DefaultContext>(from?: Command<Context>): Meta; | ||
static Validate(schema: any): (klass: any) => void; | ||
static Array(descriptor: string): (prototype: Command, propertyName: string) => void; | ||
static Rest(): (prototype: Command, propertyName: string) => void; | ||
static Proxy(): (prototype: Command, propertyName: string) => void; | ||
static Boolean(descriptor: string): (prototype: Command, propertyName: string) => void; | ||
static Array(descriptor: string): <Context extends DefaultContext>(prototype: Command<Context>, propertyName: string) => void; | ||
static Rest(): PropertyDecorator; | ||
static Rest(opts: { | ||
required: number; | ||
}): PropertyDecorator; | ||
static Proxy(): <Context extends DefaultContext>(prototype: Command<Context>, propertyName: string) => void; | ||
static Boolean(descriptor: string): <Context extends DefaultContext>(prototype: Command<Context>, propertyName: string) => void; | ||
static String(): PropertyDecorator; | ||
@@ -29,8 +32,17 @@ static String(opts: { | ||
static String(descriptor: string): PropertyDecorator; | ||
static Path(...segments: string[]): (prototype: Command, propertyName: "execute") => void; | ||
static Path(...segments: string[]): <Context extends DefaultContext>(prototype: Command<Context>, propertyName: "execute") => void; | ||
static Usage({ description, details }?: Partial<Help>): () => string; | ||
static compile(): core.Command<any>; | ||
static compile<Context extends DefaultContext>(): core.Command<(cli: Cli<Context>, context: Context) => Command<Context>>; | ||
cli: MiniCli<Context>; | ||
context: Context; | ||
help: boolean; | ||
/** | ||
* If defined, must contain a function that returns the string displayed | ||
* when the command help message is generated. | ||
*/ | ||
usage: (() => string) | undefined; | ||
abstract execute(cli: Cli, context: Context): Promise<number | void>; | ||
/** | ||
* Executed by Cli#run and Cli#runExit. | ||
*/ | ||
abstract execute(): Promise<number | void>; | ||
} |
@@ -13,2 +13,4 @@ "use strict"; | ||
constructor() { | ||
// This option is automatically added (it needs an extra logic to support | ||
// ignoring the overall syntax of the command) | ||
this.help = false; | ||
@@ -76,6 +78,7 @@ } | ||
} | ||
static Rest() { | ||
static Rest({ required = 0 } = {}) { | ||
return (prototype, propertyName) => { | ||
const { definition, transformers } = prototype.constructor.getMeta(); | ||
const index = definition.positionals.maximum; | ||
definition.positionals.minimum += required; | ||
definition.positionals.maximum = Infinity; | ||
@@ -174,6 +177,8 @@ transformers.push((command, parsed) => { | ||
const { definition, transformers } = this.getMeta(); | ||
return new core.Command(definition, parsed => { | ||
return new core.Command(definition, parsed => (cli, context) => { | ||
// @ts-ignore: In practice, "this" will be the subclass that | ||
// inherit from Command (and thus not an abstract) | ||
const bag = new this(); | ||
bag.cli = cli; | ||
bag.context = context; | ||
for (const transformer of transformers) | ||
@@ -180,0 +185,0 @@ transformer(bag, parsed); |
@@ -1,2 +0,2 @@ | ||
export { Cli, Context } from './Cli'; | ||
export { Cli, DefaultContext } from './Cli'; | ||
export { Command } from './Command'; |
import { Command } from './Command'; | ||
import { Machine } from './Machine'; | ||
export declare class Cli<T> { | ||
static fromCache<T>(cache?: string): Cli<T>; | ||
private commands; | ||
register(command: Command<T>): this; | ||
register(command: Command<T>): void; | ||
getCache(): string; | ||
start(): Machine<T>; | ||
process(argv: string[]): T; | ||
} |
@@ -8,6 +8,33 @@ "use strict"; | ||
} | ||
static fromCache(cache) { | ||
const cli = new Cli(); | ||
if (typeof cache === `undefined`) | ||
return cli; | ||
const { nodes } = JSON.parse(cache); | ||
cli.register = (command) => { | ||
command.nodes = nodes.shift(); | ||
command.compiled = true; | ||
for (const node of command.nodes) | ||
node.transitions = new Map(node.transitions); | ||
cli.commands.push(command); | ||
}; | ||
cli.start = () => { | ||
return new Machine_1.Machine(cli.commands); | ||
}; | ||
return cli; | ||
} | ||
register(command) { | ||
this.commands.push(command); | ||
return this; | ||
} | ||
getCache() { | ||
return JSON.stringify({ | ||
nodes: this.commands.map(command => command.nodes), | ||
}, function (k, v) { | ||
if (v instanceof Map) | ||
return [...v]; | ||
if (typeof v === `object` && v != null && v.label) | ||
return Object.assign({}, v, { label: `` }); | ||
return v; | ||
}); | ||
} | ||
start() { | ||
@@ -14,0 +41,0 @@ const proxyByPaths = new Map(); |
@@ -1,24 +0,22 @@ | ||
import { Parsed, RecursivePartial } from './helpers'; | ||
export declare type ParseEntry = { | ||
type: `positional`; | ||
value: string; | ||
} | { | ||
type: `option`; | ||
name: string; | ||
value: any; | ||
}; | ||
export declare type Builder = (segment: string) => ParseEntry | ParseEntry[]; | ||
import { RecursivePartial } from '../mpl'; | ||
import { builders } from './builders'; | ||
import { conditions } from './conditions'; | ||
import { Parsed } from './helpers'; | ||
export declare const HELP_OPTIONS: Set<string>; | ||
export declare const NODE_INITIAL = 0; | ||
export declare const NODE_SUCCESS = 1; | ||
export declare const NODE_ERRORED = 2; | ||
export declare type Node = { | ||
dynamics: { | ||
condition: keyof typeof conditions; | ||
target: number; | ||
builder?: keyof typeof builders; | ||
}[]; | ||
label: string; | ||
weight: number; | ||
terminal: boolean; | ||
transitions: Map<string, { | ||
suggested: boolean; | ||
transitions: Map<string | null, { | ||
target: number; | ||
builder?: Builder; | ||
builder?: keyof typeof builders; | ||
}>; | ||
dynamics: { | ||
condition: (segment: string) => boolean; | ||
target: number; | ||
builder?: Builder; | ||
}[]; | ||
weight: number; | ||
}; | ||
@@ -41,6 +39,3 @@ export declare type Definition = { | ||
nodes: Node[]; | ||
isOptionBatch: (segment: string) => boolean; | ||
isUnsupportedOption: (segment: string) => boolean; | ||
isInlineOption: (segment: string) => boolean; | ||
private compiled; | ||
compiled: boolean; | ||
constructor(definition: RecursivePartial<Definition>, transform: (parsed: Parsed) => T); | ||
@@ -50,7 +45,11 @@ compile({ proxyStart }: { | ||
}): void; | ||
createNode(weight: number, label: string): number; | ||
createNode({ weight, label, suggested }: { | ||
weight: number; | ||
label: string; | ||
suggested?: boolean; | ||
}): number; | ||
markTerminal(node: number | null): void; | ||
registerTransition(node: number | null, segment: string, target: number, builder?: Builder): void; | ||
registerDynamicTransition(node: number | null, condition: (segment: string, ...args: any[]) => any, target: number, builder?: Builder): void; | ||
registerTransition(node: number | null, segment: string | null, target: number, builder?: keyof typeof builders): void; | ||
registerDynamicTransition(node: number | null, condition: keyof typeof conditions, target: number, builder?: keyof typeof builders): void; | ||
registerOptions(node: number | null): void; | ||
} |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const helpers_1 = require("./helpers"); | ||
exports.HELP_OPTIONS = new Set([`-h`, `--help`]); | ||
exports.NODE_INITIAL = 0; | ||
exports.NODE_SUCCESS = 1; | ||
exports.NODE_ERRORED = 2; | ||
const DEFAULT_COMMAND_OPTIONS = { | ||
@@ -16,89 +20,8 @@ path: [], | ||
}; | ||
function isPositionalArgument(segment) { | ||
return !segment.startsWith(`-`); | ||
} | ||
const OPTION_REGEXP = /^-[a-z]|--[a-z]+(-[a-z]+)*$/; | ||
function isOptionLike(segment) { | ||
return OPTION_REGEXP.test(segment); | ||
} | ||
function always() { | ||
return true; | ||
} | ||
const HELP_OPTIONS = new Set([`-h`, `--help`]); | ||
function isNotHelpNorSeparator(segment) { | ||
return !HELP_OPTIONS.has(segment) && segment !== `--`; | ||
} | ||
function isOptionLikeButNotHelp(segment) { | ||
return isOptionLike(segment) && !HELP_OPTIONS.has(segment); | ||
} | ||
const BATCH_REGEXP = /^-[a-z0-9]{2,}$/; | ||
function makeIsOptionBatch(definition) { | ||
return (segment) => { | ||
if (!BATCH_REGEXP.test(segment)) | ||
return false; | ||
for (let t = 1; t < segment.length; ++t) | ||
if (!definition.options.simple.has(`-${segment.charAt(t)}`)) | ||
return false; | ||
return true; | ||
}; | ||
} | ||
function makeIsUnsupportedOption(definition) { | ||
const options = new Set([ | ||
...definition.options.simple, | ||
...definition.options.complex, | ||
]); | ||
return function isUnsupportedOption(segment) { | ||
if (isPositionalArgument(segment) || segment === `--`) | ||
return false; | ||
const idx = segment.indexOf(`=`); | ||
const name = idx === -1 ? segment : segment.substr(0, idx); | ||
return !options.has(name); | ||
}; | ||
} | ||
function makeIsInlineOption(definition) { | ||
const options = new Set([ | ||
...definition.options.simple, | ||
...definition.options.complex, | ||
]); | ||
return function isInlineOption(segment) { | ||
if (isPositionalArgument(segment)) | ||
return false; | ||
const idx = segment.indexOf(`=`); | ||
if (idx === -1) | ||
return false; | ||
const name = segment.substr(0, idx); | ||
if (!options.has(name)) | ||
return false; | ||
return true; | ||
}; | ||
} | ||
function generatePositional(segment) { | ||
return { type: `positional`, value: segment }; | ||
} | ||
function generateBoolean(segment) { | ||
return { type: `option`, name: segment, value: true }; | ||
} | ||
function generateString(key, segment) { | ||
return { type: `option`, name: key, value: segment }; | ||
} | ||
function generateStringFromInline(segment) { | ||
const idx = segment.indexOf(`=`); | ||
return generateString(segment.substr(0, idx), segment.substr(idx + 1)); | ||
} | ||
function generateBooleansFromBatch(segment) { | ||
let results = []; | ||
for (let t = 1; t < segment.length; ++t) | ||
results.push(generateBoolean(`-${segment.charAt(t)}`)); | ||
return results; | ||
} | ||
class Command { | ||
constructor(definition, transform) { | ||
this.transform = transform; | ||
this.nodes = []; | ||
this.compiled = false; | ||
this.definition = helpers_1.deepMerge({}, DEFAULT_COMMAND_OPTIONS, definition); | ||
this.nodes = []; | ||
this.createNode(0, `initial`); | ||
this.isOptionBatch = makeIsOptionBatch(this.definition); | ||
this.isUnsupportedOption = makeIsUnsupportedOption(this.definition); | ||
this.isInlineOption = makeIsInlineOption(this.definition); | ||
} | ||
@@ -108,10 +31,18 @@ compile({ proxyStart }) { | ||
return; | ||
this.createNode({ weight: 0, label: `initial` }); | ||
this.createNode({ weight: 0, label: `success` }); | ||
this.createNode({ weight: 0, label: `errored` }); | ||
// We can't ever leave the error state, even at the end of the line | ||
this.registerDynamicTransition(exports.NODE_ERRORED, `always`, exports.NODE_ERRORED); | ||
this.registerTransition(exports.NODE_ERRORED, null, exports.NODE_ERRORED); | ||
this.compiled = true; | ||
const initialDD = this.createNode({ weight: 0, label: `initial (no opts)` }); | ||
this.registerTransition(0, `--`, initialDD); | ||
const allPathNodes = []; | ||
const allPathNodesDD = []; | ||
let lastPathNode = 0; | ||
let lastPathNodeDD = null; | ||
let lastPathNode = exports.NODE_INITIAL; | ||
let lastPathNodeDD = initialDD; | ||
for (const segment of this.definition.path) { | ||
const currentPathNode = this.createNode(0x100, `consuming path`); | ||
const currentPathNodeDD = this.createNode(0x100, `consuming path (no opts)`); | ||
const currentPathNode = this.createNode({ weight: 0x100, label: `consuming path` }); | ||
const currentPathNodeDD = this.createNode({ weight: 0x100, label: `consuming path (no opts)` }); | ||
this.registerTransition(lastPathNode, segment, currentPathNode); | ||
@@ -125,36 +56,4 @@ this.registerTransition(lastPathNodeDD, segment, currentPathNodeDD); | ||
} | ||
/* The following block exclusively adds support for -h and --help */ { | ||
// We can't support -h,--help if a proxy starts immediately after the command | ||
if (typeof proxyStart === `undefined` || proxyStart >= 1) { | ||
// This node will allow us to ignore every token coming after -h,--help | ||
const afterHelpNode = this.createNode(0, `consuming after help`); | ||
this.markTerminal(afterHelpNode); | ||
this.registerDynamicTransition(afterHelpNode, always, afterHelpNode); | ||
// Register the transition from the path to the help | ||
for (const optName of HELP_OPTIONS) | ||
this.registerTransition(lastPathNode, optName, afterHelpNode, generateBoolean); | ||
// If there are no proxy we can just consume everything until -h,--help | ||
if (typeof proxyStart === `undefined`) { | ||
const searchHelpNode = this.createNode(0, `looking for -h,--help`); | ||
this.registerDynamicTransition(lastPathNode, isNotHelpNorSeparator, searchHelpNode); | ||
this.registerDynamicTransition(searchHelpNode, isNotHelpNorSeparator, searchHelpNode); | ||
for (const optName of HELP_OPTIONS) { | ||
this.registerTransition(searchHelpNode, optName, afterHelpNode, generateBoolean); | ||
} | ||
// Otherwise we need to count what we eat so that we don't enter the Proxy Zoneā¢ | ||
} | ||
else { | ||
const lookAheadSize = proxyStart - this.definition.path.length - this.definition.positionals.minimum - 1; | ||
let lastHelpNode = lastPathNode; | ||
for (let t = 0; t < lookAheadSize; ++t) { | ||
const currentHelpNode = this.createNode(0, `looking for help (${t + 1}/${proxyStart})`); | ||
this.registerDynamicTransition(lastHelpNode, isPositionalArgument, currentHelpNode); | ||
this.registerDynamicTransition(currentHelpNode, isOptionLikeButNotHelp, currentHelpNode); | ||
for (const optName of HELP_OPTIONS) | ||
this.registerTransition(currentHelpNode, optName, afterHelpNode, generateBoolean); | ||
lastHelpNode = currentHelpNode; | ||
} | ||
} | ||
} | ||
} | ||
if (this.definition.positionals.minimum > 0) | ||
this.registerTransition(lastPathNode, null, exports.NODE_ERRORED, `generateMissingPositionalArgument`); | ||
const allMinNodes = []; | ||
@@ -165,6 +64,8 @@ const allMinNodesDD = []; | ||
for (let t = 0; t < this.definition.positionals.minimum; ++t) { | ||
const currentMinNode = this.createNode(0x1, `consuming required positionals`); | ||
const currentMinNodeDD = this.createNode(0x1, `consuming required positionals (no opts)`); | ||
this.registerDynamicTransition(lastMinNode, isPositionalArgument, currentMinNode, generatePositional); | ||
this.registerDynamicTransition(lastMinNodeDD, always, currentMinNodeDD, generatePositional); | ||
const currentMinNode = this.createNode({ weight: 0x1, label: `consuming required positionals` }); | ||
const currentMinNodeDD = this.createNode({ weight: 0x1, label: `consuming required positionals (no opts)` }); | ||
this.registerDynamicTransition(lastMinNode, `isPositionalArgument`, currentMinNode, `generatePositional`); | ||
this.registerDynamicTransition(lastMinNodeDD, `always`, currentMinNodeDD, `generatePositional`); | ||
if (t + 1 < this.definition.positionals.minimum) | ||
this.registerTransition(currentMinNode, null, exports.NODE_ERRORED, `generateMissingPositionalArgument`); | ||
this.registerTransition(currentMinNode, `--`, currentMinNodeDD); | ||
@@ -182,16 +83,16 @@ allMinNodes.push(currentMinNode); | ||
if (this.definition.positionals.proxy) { | ||
const proxyNode = this.createNode(0, `consuming everything through a proxy`); | ||
const proxyNode = this.createNode({ weight: 0, label: `consuming everything through a proxy` }); | ||
this.markTerminal(proxyNode); | ||
this.registerDynamicTransition(lastMaxNode, isPositionalArgument, proxyNode, generatePositional); | ||
this.registerDynamicTransition(lastMaxNode, this.isUnsupportedOption, proxyNode, generatePositional); | ||
this.registerDynamicTransition(proxyNode, always, proxyNode, generatePositional); | ||
this.registerDynamicTransition(lastMaxNode, `isPositionalArgument`, proxyNode, `generatePositional`); | ||
this.registerDynamicTransition(lastMaxNode, `isUnsupportedOption`, proxyNode, `generatePositional`); | ||
this.registerDynamicTransition(proxyNode, `always`, proxyNode, `generatePositional`); | ||
this.registerTransition(lastMaxNode, `--`, proxyNode); | ||
} | ||
else { | ||
const currentMaxNode = this.createNode(0, `consuming optional positionals`); | ||
const currentMaxNodeDD = this.createNode(0, `consuming optional positionals`); | ||
this.registerDynamicTransition(lastMaxNode, isPositionalArgument, currentMaxNode, generatePositional); | ||
this.registerDynamicTransition(lastMaxNodeDD, always, currentMaxNodeDD, generatePositional); | ||
this.registerDynamicTransition(currentMaxNode, isPositionalArgument, currentMaxNode, generatePositional); | ||
this.registerDynamicTransition(currentMaxNodeDD, always, currentMaxNodeDD, generatePositional); | ||
const currentMaxNode = this.createNode({ weight: 0, label: `consuming optional positionals` }); | ||
const currentMaxNodeDD = this.createNode({ weight: 0, label: `consuming optional positionals` }); | ||
this.registerDynamicTransition(lastMaxNode, `isPositionalArgument`, currentMaxNode, `generatePositional`); | ||
this.registerDynamicTransition(lastMaxNodeDD, `always`, currentMaxNodeDD, `generatePositional`); | ||
this.registerDynamicTransition(currentMaxNode, `isPositionalArgument`, currentMaxNode, `generatePositional`); | ||
this.registerDynamicTransition(currentMaxNodeDD, `always`, currentMaxNodeDD, `generatePositional`); | ||
this.registerTransition(lastMaxNode, `--`, currentMaxNodeDD); | ||
@@ -206,6 +107,6 @@ allMaxNodes.push(currentMaxNode); | ||
for (let t = this.definition.positionals.minimum; t < this.definition.positionals.maximum; ++t) { | ||
const currentMaxNode = this.createNode(0, `consuming optional positionals`); | ||
const currentMaxNodeDD = this.createNode(0, `consuming optional positionals`); | ||
this.registerDynamicTransition(lastMaxNode, isPositionalArgument, currentMaxNode, generatePositional); | ||
this.registerDynamicTransition(lastMaxNodeDD, always, currentMaxNodeDD, generatePositional); | ||
const currentMaxNode = this.createNode({ weight: 0, label: `consuming optional positionals` }); | ||
const currentMaxNodeDD = this.createNode({ weight: 0, label: `consuming optional positionals` }); | ||
this.registerDynamicTransition(lastMaxNode, `isPositionalArgument`, currentMaxNode, `generatePositional`); | ||
this.registerDynamicTransition(lastMaxNodeDD, `always`, currentMaxNodeDD, `generatePositional`); | ||
this.registerTransition(lastMaxNode, `--`, currentMaxNodeDD); | ||
@@ -218,2 +119,46 @@ allMaxNodes.push(currentMaxNode); | ||
} | ||
this.registerDynamicTransition(lastMaxNode, `isPositionalArgument`, exports.NODE_ERRORED, `generateExtraneousPositionalArgument`); | ||
this.registerDynamicTransition(lastMaxNodeDD, `always`, exports.NODE_ERRORED, `generateExtraneousPositionalArgument`); | ||
/* The following block exclusively adds support for -h and --help */ { | ||
// We can't support -h,--help if a proxy starts immediately after the command | ||
if (typeof proxyStart === `undefined` || proxyStart >= 1) { | ||
// This node will allow us to ignore every token coming after -h,--help | ||
const afterHelpNode = this.createNode({ weight: 0, label: `consuming after help`, suggested: false }); | ||
this.markTerminal(afterHelpNode); | ||
this.registerDynamicTransition(afterHelpNode, `always`, afterHelpNode); | ||
// Register the transition from the path to the help | ||
for (const optName of exports.HELP_OPTIONS) | ||
this.registerTransition(lastPathNode, optName, afterHelpNode, `generateBoolean`); | ||
// Same thing for the mandatory positional arguments, but we give them some weight | ||
for (let t = 0; t < this.definition.positionals.minimum; ++t) { | ||
for (const optName of exports.HELP_OPTIONS) { | ||
// Note that we don't add this transition to allMinNodesDD, since we don't want to find -h,--help if behind -- | ||
this.registerTransition(allMinNodes[t], optName, afterHelpNode, `generateBoolean`); | ||
} | ||
} | ||
// If there are no proxy we can just consume everything until -h,--help | ||
if (typeof proxyStart === `undefined`) { | ||
const searchHelpNode = this.createNode({ weight: 0, label: `looking for -h,--help`, suggested: false }); | ||
this.registerDynamicTransition(lastMinNode, `isUnsupportedOption`, searchHelpNode); | ||
this.registerDynamicTransition(lastMaxNode, `isPositionalArgument`, searchHelpNode); | ||
this.registerDynamicTransition(searchHelpNode, `isNotHelpNorSeparator`, searchHelpNode); | ||
for (const optName of exports.HELP_OPTIONS) { | ||
this.registerTransition(searchHelpNode, optName, afterHelpNode, `generateBoolean`); | ||
} | ||
// Otherwise we need to count what we eat so that we don't enter the Proxy Zoneā¢ | ||
} | ||
else { | ||
const lookAheadSize = proxyStart - this.definition.path.length - this.definition.positionals.minimum - 1; | ||
let lastHelpNode = lastPathNode; | ||
for (let t = 0; t < lookAheadSize; ++t) { | ||
const currentHelpNode = this.createNode({ weight: 0, label: `looking for help (${t + 1}/${proxyStart})`, suggested: false }); | ||
this.registerDynamicTransition(lastHelpNode, `isPositionalArgument`, currentHelpNode); | ||
this.registerDynamicTransition(currentHelpNode, `isOptionLikeButNotHelp`, currentHelpNode); | ||
for (const optName of exports.HELP_OPTIONS) | ||
this.registerTransition(currentHelpNode, optName, afterHelpNode, `generateBoolean`); | ||
lastHelpNode = currentHelpNode; | ||
} | ||
} | ||
} | ||
} | ||
this.registerOptions(lastPathNode); | ||
@@ -232,10 +177,8 @@ this.markTerminal(lastMinNode); | ||
} | ||
createNode(weight, label) { | ||
this.nodes.push({ label, weight, terminal: false, transitions: new Map(), dynamics: [] }); | ||
createNode({ weight, label, suggested = true }) { | ||
this.nodes.push({ label, suggested, weight, transitions: new Map(), dynamics: [] }); | ||
return this.nodes.length - 1; | ||
} | ||
markTerminal(node) { | ||
if (node !== null) { | ||
this.nodes[node].terminal = true; | ||
} | ||
this.registerTransition(node, null, 1); | ||
} | ||
@@ -255,14 +198,15 @@ registerTransition(node, segment, target, builder) { | ||
return; | ||
this.registerDynamicTransition(node, `isUnsupportedOption`, exports.NODE_ERRORED, `generateUnsupportedOption`); | ||
if (this.definition.options.simple.size > 0) { | ||
this.registerDynamicTransition(node, this.isOptionBatch, node, generateBooleansFromBatch); | ||
this.registerDynamicTransition(node, `isOptionBatch`, node, `generateBooleansFromBatch`); | ||
for (const optName of this.definition.options.simple) { | ||
this.registerTransition(node, optName, node, generateBoolean); | ||
this.registerTransition(node, optName, node, `generateBoolean`); | ||
} | ||
} | ||
if (this.definition.options.complex.size > 0) { | ||
this.registerDynamicTransition(node, this.isInlineOption, node, generateStringFromInline); | ||
this.registerDynamicTransition(node, `isInlineOption`, node, `generateStringFromInline`); | ||
for (const optName of this.definition.options.complex) { | ||
const argNode = this.createNode(0, `consuming argument`); | ||
this.registerDynamicTransition(argNode, isPositionalArgument, node, generateString.bind(null, optName)); | ||
this.registerTransition(node, optName, argNode); | ||
const argNode = this.createNode({ weight: 0, label: `consuming argument` }); | ||
this.registerDynamicTransition(argNode, `isPositionalArgument`, node, `generateStringValue`); | ||
this.registerTransition(node, optName, argNode, `generateStringKey`); | ||
} | ||
@@ -269,0 +213,0 @@ } |
@@ -7,2 +7,8 @@ import { Definition } from './Command'; | ||
}; | ||
export declare class UnknownSyntaxError extends Error { | ||
readonly definitions: [Definition, string | null][]; | ||
clipanion: ErrorMeta; | ||
constructor(definitions: [Definition, string | null][]); | ||
setBinaryName(binaryName: string | undefined): void; | ||
} | ||
export declare class AmbiguousSyntaxError extends Error { | ||
@@ -9,0 +15,0 @@ readonly definitions: Definition[]; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const pretty_1 = require("./pretty"); | ||
function getTokenName(name) { | ||
if (name === null) { | ||
return `<eol>`; | ||
} | ||
else { | ||
return `"${name}"`; | ||
} | ||
} | ||
class UnknownSyntaxError extends Error { | ||
constructor(definitions) { | ||
super(); | ||
this.definitions = definitions; | ||
this.clipanion = { type: `none` }; | ||
this.name = `UnknownSyntaxError`; | ||
this.setBinaryName(undefined); | ||
} | ||
setBinaryName(binaryName) { | ||
if (this.definitions.length > 1) { | ||
this.message = `Command not found; did you mean one of:\n\n${this.definitions.map(([definition], index) => { | ||
return ` ${index}. ${pretty_1.prettyCommand(definition, { binaryName })}`; | ||
}).join(`\n`)}`; | ||
} | ||
else { | ||
const [[definition, reason]] = this.definitions; | ||
this.message = `${reason}\n\n$ ${pretty_1.prettyCommand(definition, { binaryName })}`; | ||
} | ||
} | ||
} | ||
exports.UnknownSyntaxError = UnknownSyntaxError; | ||
class AmbiguousSyntaxError extends Error { | ||
@@ -13,4 +42,4 @@ constructor(definitions) { | ||
setBinaryName(binaryName) { | ||
this.message = `Cannot find who to pick amongst the following alternatives:\n${this.definitions.map((definition, index) => { | ||
return `${index}. ${pretty_1.prettyCommand(definition, { binaryName })}`; | ||
this.message = `Cannot find who to pick amongst the following alternatives:\n\n${this.definitions.map((definition, index) => { | ||
return ` ${index}. ${pretty_1.prettyCommand(definition, { binaryName })}`; | ||
}).join(`\n`)}`; | ||
@@ -17,0 +46,0 @@ } |
@@ -1,6 +0,3 @@ | ||
import { ParseEntry } from './Command'; | ||
import { ParseEntry } from './builders'; | ||
export declare const DEBUG: boolean; | ||
export declare type RecursivePartial<T> = { | ||
[P in keyof T]?: T[P] extends (infer U)[] ? RecursivePartial<U>[] : T[P] extends object ? RecursivePartial<T[P]> : T[P]; | ||
}; | ||
export declare function deepMerge<T extends any>(target: T, ...sources: ({ | ||
@@ -7,0 +4,0 @@ [key: string]: any; |
@@ -36,2 +36,7 @@ "use strict"; | ||
break; | ||
case `patch`: | ||
{ | ||
result.options[result.options.length - 1].value = source.value; | ||
} | ||
break; | ||
} | ||
@@ -38,0 +43,0 @@ } |
@@ -5,4 +5,5 @@ import { Command } from './Command'; | ||
constructor(commands: Command<T>[]); | ||
write(segment: string): void; | ||
write(segment: string): this; | ||
private _write; | ||
digest(): T; | ||
} |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const Command_1 = require("./Command"); | ||
const builders_1 = require("./builders"); | ||
const conditions_1 = require("./conditions"); | ||
const errors_1 = require("./errors"); | ||
@@ -15,7 +18,14 @@ const helpers_1 = require("./helpers"); | ||
write(segment) { | ||
this._write(segment); | ||
return this; | ||
} | ||
_write(segment) { | ||
if (helpers_1.DEBUG) | ||
console.log(`=== ${segment}`); | ||
for (const state of this.states) | ||
state.write(segment); | ||
let maxWeight = 0; | ||
for (const state of this.states) | ||
maxWeight = Math.max(maxWeight, state.write(segment)); | ||
for (const { weight } of state.options) | ||
maxWeight = Math.max(maxWeight, weight); | ||
for (const state of this.states) { | ||
@@ -26,11 +36,12 @@ state.trimLighterBranches(maxWeight); | ||
digest() { | ||
const candidates = []; | ||
this._write(null); | ||
const successes = []; | ||
const failures = []; | ||
for (const state of this.states) | ||
for (const candidate of state.digest()) | ||
candidates.push(candidate); | ||
if (candidates.length < 1) | ||
throw new Error(`No matches`); | ||
if (candidates.length > 1) | ||
throw new errors_1.AmbiguousSyntaxError(candidates.map(({ command }) => command.definition)); | ||
const [{ command, parsed }] = candidates; | ||
state.digest({ successes, failures }); | ||
if (successes.length < 1) | ||
throw new errors_1.UnknownSyntaxError(failures.map(({ command, reason }) => [command.definition, reason])); | ||
if (successes.length > 1) | ||
throw new errors_1.AmbiguousSyntaxError(successes.map(({ command }) => command.definition)); | ||
const [{ command, parsed }] = successes; | ||
if (helpers_1.DEBUG) | ||
@@ -49,12 +60,14 @@ console.log(`Selected ${pretty_1.prettyCommand(command.definition)}`); | ||
if (helpers_1.DEBUG) | ||
console.log(` ${pretty_1.prettyCommand(this.command.definition)}`); | ||
console.log(` [${pretty_1.prettyCommand(this.command.definition)}]`); | ||
const options = this.options; | ||
this.options = []; | ||
for (const { weight, node, values } of options) { | ||
const { transitions, dynamics, terminal } = this.command.nodes[node]; | ||
const { transitions, dynamics, suggested } = this.command.nodes[node]; | ||
const transition = transitions.get(segment); | ||
let transitioned = false; | ||
if (helpers_1.DEBUG) | ||
console.log(` ${this.command.nodes[node].label}`); | ||
if (typeof transition !== `undefined`) { | ||
const nextValues = typeof transition.builder === `undefined` ? values : values.concat(transition.builder(segment)); | ||
transitioned = true; | ||
const nextValues = typeof transition.builder === `undefined` ? values : values.concat(builders_1.builders[transition.builder](segment)); | ||
this.options.push({ weight: weight + this.command.nodes[transition.target].weight, node: transition.target, values: nextValues }); | ||
@@ -65,23 +78,30 @@ if (helpers_1.DEBUG) { | ||
} | ||
for (const { condition, target, builder } of dynamics) { | ||
if (condition(segment)) { | ||
const nextValues = typeof builder === `undefined` ? values : values.concat(builder(segment)); | ||
this.options.push({ weight: weight + this.command.nodes[target].weight, node: target, values: nextValues }); | ||
if (helpers_1.DEBUG) { | ||
console.log(` -> ${this.command.nodes[target].label} (dynamic, via ${condition.name})`); | ||
if (segment !== null) { | ||
for (const { condition, target, builder } of dynamics) { | ||
if (conditions_1.conditions[condition](segment, this.command.definition)) { | ||
if (this.command.nodes[target].suggested) | ||
transitioned = true; | ||
const nextValues = typeof builder === `undefined` ? values : values.concat(builders_1.builders[builder](segment)); | ||
this.options.push({ weight: weight + this.command.nodes[target].weight, node: target, values: nextValues }); | ||
if (helpers_1.DEBUG) { | ||
console.log(` -> ${this.command.nodes[target].label} (dynamic, via ${condition})`); | ||
} | ||
} | ||
} | ||
else { | ||
if (helpers_1.DEBUG) { | ||
console.log(` -> doesn't match ${condition.name}`); | ||
else { | ||
if (helpers_1.DEBUG) { | ||
console.log(` -> doesn't match ${condition}`); | ||
} | ||
} | ||
} | ||
} | ||
if (!transitioned && suggested) { | ||
this.options.push({ weight, node: Command_1.NODE_ERRORED, values: values.concat({ reason: `Invalid token "${segment}"` }) }); | ||
if (helpers_1.DEBUG) { | ||
console.log(` -> entering an error state`); | ||
} | ||
} | ||
} | ||
if (helpers_1.DEBUG) | ||
console.log(` Went from ${options.length} -> ${this.options.length}`); | ||
let maxWeight = 0; | ||
for (const option of this.options) | ||
maxWeight = Math.max(maxWeight, option.weight); | ||
return maxWeight; | ||
return this.options; | ||
} | ||
@@ -91,9 +111,18 @@ trimLighterBranches(requirement) { | ||
} | ||
digest() { | ||
const candidates = []; | ||
for (const { node, values } of this.options) | ||
if (this.command.nodes[node].terminal) | ||
candidates.push({ command: this.command, parsed: helpers_1.reconciliateValues(values) }); | ||
return candidates; | ||
digest({ successes, failures }) { | ||
for (const { node, values } of this.options) { | ||
switch (node) { | ||
case Command_1.NODE_SUCCESS: | ||
{ | ||
successes.push({ command: this.command, parsed: helpers_1.reconciliateValues(values) }); | ||
} | ||
break; | ||
case Command_1.NODE_ERRORED: | ||
{ | ||
failures.push({ command: this.command, reason: values[values.length - 1].reason }); | ||
} | ||
break; | ||
} | ||
} | ||
} | ||
} |
{ | ||
"name": "clipanion", | ||
"version": "2.0.0-rc.0", | ||
"version": "2.0.0-rc.1", | ||
"main": "lib/advanced", | ||
@@ -12,2 +12,3 @@ "license": "MIT", | ||
"@types/chai": "^4.1.7", | ||
"@types/chai-as-promised": "^7.1.0", | ||
"@types/chalk": "^2.2.0", | ||
@@ -18,2 +19,4 @@ "@types/mocha": "^5.2.7", | ||
"chai": "^4.2.0", | ||
"chai-as-promised": "^7.1.1", | ||
"get-stream": "^5.1.0", | ||
"mocha": "^6.1.4", | ||
@@ -20,0 +23,0 @@ "ts-node": "^8.3.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
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
49680
27
1185
0
14