|
|
@ -1,10 +1,10 @@ |
|
|
|
import {Injectable, Inject} from "../di" |
|
|
|
import {infer, ErrorWithContext} from "../util" |
|
|
|
import {CLIOption} from "./directive/options/CLIOption" |
|
|
|
import {PositionalOption} from "./directive/options/PositionalOption"; |
|
|
|
import {FlagOption} from "./directive/options/FlagOption"; |
|
|
|
import {AppClass} from "../lifecycle/AppClass"; |
|
|
|
import {Logging} from "../service/Logging"; |
|
|
|
import {Injectable, Inject} from '../di' |
|
|
|
import {infer, ErrorWithContext} from '../util' |
|
|
|
import {CLIOption} from './directive/options/CLIOption' |
|
|
|
import {PositionalOption} from './directive/options/PositionalOption' |
|
|
|
import {FlagOption} from './directive/options/FlagOption' |
|
|
|
import {AppClass} from '../lifecycle/AppClass' |
|
|
|
import {Logging} from '../service/Logging' |
|
|
|
|
|
|
|
/** |
|
|
|
* Type alias for a definition of a command-line option. |
|
|
@ -35,7 +35,7 @@ export abstract class Directive extends AppClass { |
|
|
|
protected readonly logging!: Logging |
|
|
|
|
|
|
|
/** Parsed option values. */ |
|
|
|
private _optionValues: any |
|
|
|
private optionValues: any |
|
|
|
|
|
|
|
/** |
|
|
|
* Get the keyword or array of keywords that will specify this directive. |
|
|
@ -84,8 +84,8 @@ export abstract class Directive extends AppClass { |
|
|
|
* @param optionValues |
|
|
|
* @private |
|
|
|
*/ |
|
|
|
private _setOptionValues(optionValues: any) { |
|
|
|
this._optionValues = optionValues; |
|
|
|
private setOptionValues(optionValues: any) { |
|
|
|
this.optionValues = optionValues |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
@ -93,9 +93,9 @@ export abstract class Directive extends AppClass { |
|
|
|
* @param name |
|
|
|
* @param defaultValue |
|
|
|
*/ |
|
|
|
public option(name: string, defaultValue?: any) { |
|
|
|
if ( name in this._optionValues ) { |
|
|
|
return this._optionValues[name] |
|
|
|
public option(name: string, defaultValue?: unknown): any { |
|
|
|
if ( name in this.optionValues ) { |
|
|
|
return this.optionValues[name] |
|
|
|
} |
|
|
|
|
|
|
|
return defaultValue |
|
|
@ -110,20 +110,28 @@ export abstract class Directive extends AppClass { |
|
|
|
* |
|
|
|
* @param argv |
|
|
|
*/ |
|
|
|
async invoke(argv: string[]) { |
|
|
|
async invoke(argv: string[]): Promise<void> { |
|
|
|
const options = this.getResolvedOptions() |
|
|
|
|
|
|
|
if ( this.didRequestUsage(argv) ) { |
|
|
|
// @ts-ignore
|
|
|
|
const positionalArguments: PositionalOption<any>[] = options.filter(opt => opt instanceof PositionalOption) |
|
|
|
const positionalArguments: PositionalOption<any>[] = [] |
|
|
|
options.forEach(opt => { |
|
|
|
if ( opt instanceof PositionalOption ) { |
|
|
|
positionalArguments.push(opt) |
|
|
|
} |
|
|
|
}) |
|
|
|
|
|
|
|
// @ts-ignore
|
|
|
|
const flagArguments: FlagOption<any>[] = options.filter(opt => opt instanceof FlagOption) |
|
|
|
const flagArguments: FlagOption<any>[] = [] |
|
|
|
options.forEach(opt => { |
|
|
|
if ( opt instanceof FlagOption ) { |
|
|
|
flagArguments.push(opt) |
|
|
|
} |
|
|
|
}) |
|
|
|
|
|
|
|
const positionalDisplay: string = positionalArguments.map(x => `<${x.getArgumentName()}>`).join(' ') |
|
|
|
const flagDisplay: string = flagArguments.length ? ' [...flags]' : '' |
|
|
|
|
|
|
|
console.log([ |
|
|
|
this.nativeOutput([ |
|
|
|
'', |
|
|
|
`DIRECTIVE: ${this.getMainKeyword()} - ${this.getDescription()}`, |
|
|
|
'', |
|
|
@ -131,7 +139,7 @@ export abstract class Directive extends AppClass { |
|
|
|
].join('\n')) |
|
|
|
|
|
|
|
if ( positionalArguments.length ) { |
|
|
|
console.log([ |
|
|
|
this.nativeOutput([ |
|
|
|
'', |
|
|
|
`POSITIONAL ARGUMENTS:`, |
|
|
|
...(positionalArguments.map(arg => { |
|
|
@ -141,7 +149,7 @@ export abstract class Directive extends AppClass { |
|
|
|
} |
|
|
|
|
|
|
|
if ( flagArguments.length ) { |
|
|
|
console.log([ |
|
|
|
this.nativeOutput([ |
|
|
|
'', |
|
|
|
`FLAGS:`, |
|
|
|
...(flagArguments.map(arg => { |
|
|
@ -152,34 +160,34 @@ export abstract class Directive extends AppClass { |
|
|
|
|
|
|
|
const help = this.getHelpText() |
|
|
|
if ( help ) { |
|
|
|
console.log('\n' + help) |
|
|
|
this.nativeOutput('\n' + help) |
|
|
|
} |
|
|
|
|
|
|
|
console.log('\n') |
|
|
|
this.nativeOutput('\n') |
|
|
|
} else { |
|
|
|
try { |
|
|
|
const optionValues = this.parseOptions(options, argv) |
|
|
|
this._setOptionValues(optionValues) |
|
|
|
this.setOptionValues(optionValues) |
|
|
|
await this.handle(argv) |
|
|
|
} catch (e) { |
|
|
|
console.error(e.message) |
|
|
|
this.nativeOutput(e.message) |
|
|
|
if ( e instanceof OptionValidationError ) { |
|
|
|
// expecting, value, requirements
|
|
|
|
if ( e.context.expecting ) { |
|
|
|
console.error(` - Expecting: ${e.context.expecting}`) |
|
|
|
this.nativeOutput(` - Expecting: ${e.context.expecting}`) |
|
|
|
} |
|
|
|
|
|
|
|
if ( e.context.requirements && Array.isArray(e.context.requirements) ) { |
|
|
|
for ( const req of e.context.requirements ) { |
|
|
|
console.error(` - ${req}`) |
|
|
|
this.nativeOutput(` - ${req}`) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if ( e.context.value ) { |
|
|
|
console.error(` - ${e.context.value}`) |
|
|
|
this.nativeOutput(` - ${e.context.value}`) |
|
|
|
} |
|
|
|
} |
|
|
|
console.error('\nUse --help for more info.') |
|
|
|
this.nativeOutput('\nUse --help for more info.') |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
@ -217,9 +225,11 @@ export abstract class Directive extends AppClass { |
|
|
|
* Returns true if the given keyword should invoke this directive. |
|
|
|
* @param name |
|
|
|
*/ |
|
|
|
public matchesKeyword(name: string) { |
|
|
|
public matchesKeyword(name: string): boolean { |
|
|
|
let kws = this.getKeywords() |
|
|
|
if ( !Array.isArray(kws) ) kws = [kws] |
|
|
|
if ( !Array.isArray(kws) ) { |
|
|
|
kws = [kws] |
|
|
|
} |
|
|
|
return kws.includes(name) |
|
|
|
} |
|
|
|
|
|
|
@ -227,7 +237,7 @@ export abstract class Directive extends AppClass { |
|
|
|
* Print the given output to the log as success text. |
|
|
|
* @param output |
|
|
|
*/ |
|
|
|
success(output: any) { |
|
|
|
success(output: unknown): void { |
|
|
|
this.logging.success(output, true) |
|
|
|
} |
|
|
|
|
|
|
@ -235,7 +245,7 @@ export abstract class Directive extends AppClass { |
|
|
|
* Print the given output to the log as error text. |
|
|
|
* @param output |
|
|
|
*/ |
|
|
|
error(output: any) { |
|
|
|
error(output: unknown): void { |
|
|
|
this.logging.error(output, true) |
|
|
|
} |
|
|
|
|
|
|
@ -243,7 +253,7 @@ export abstract class Directive extends AppClass { |
|
|
|
* Print the given output to the log as warning text. |
|
|
|
* @param output |
|
|
|
*/ |
|
|
|
warn(output: any) { |
|
|
|
warn(output: unknown): void { |
|
|
|
this.logging.warn(output, true) |
|
|
|
} |
|
|
|
|
|
|
@ -251,7 +261,7 @@ export abstract class Directive extends AppClass { |
|
|
|
* Print the given output to the log as info text. |
|
|
|
* @param output |
|
|
|
*/ |
|
|
|
info(output: any) { |
|
|
|
info(output: unknown): void { |
|
|
|
this.logging.info(output, true) |
|
|
|
} |
|
|
|
|
|
|
@ -259,7 +269,7 @@ export abstract class Directive extends AppClass { |
|
|
|
* Print the given output to the log as debugging text. |
|
|
|
* @param output |
|
|
|
*/ |
|
|
|
debug(output: any) { |
|
|
|
debug(output: unknown): void { |
|
|
|
this.logging.debug(output, true) |
|
|
|
} |
|
|
|
|
|
|
@ -267,7 +277,7 @@ export abstract class Directive extends AppClass { |
|
|
|
* Print the given output to the log as verbose text. |
|
|
|
* @param output |
|
|
|
*/ |
|
|
|
verbose(output: any) { |
|
|
|
verbose(output: unknown): void { |
|
|
|
this.logging.verbose(output, true) |
|
|
|
} |
|
|
|
|
|
|
@ -275,7 +285,7 @@ export abstract class Directive extends AppClass { |
|
|
|
* Get the flag option that signals help. Usually, this is named 'help' |
|
|
|
* and supports the flags '--help' and '-?'. |
|
|
|
*/ |
|
|
|
getHelpOption() { |
|
|
|
getHelpOption(): FlagOption<any> { |
|
|
|
return new FlagOption('--help', '-?', 'usage information about this directive') |
|
|
|
} |
|
|
|
|
|
|
@ -283,12 +293,21 @@ export abstract class Directive extends AppClass { |
|
|
|
* Process the raw CLI arguments using an array of option class instances to build |
|
|
|
* a mapping of option names to provided values. |
|
|
|
*/ |
|
|
|
parseOptions(options: CLIOption<any>[], args: string[]) { |
|
|
|
// @ts-ignore
|
|
|
|
let positionalArguments: PositionalOption<any>[] = options.filter(cls => cls instanceof PositionalOption) |
|
|
|
parseOptions(options: CLIOption<any>[], args: string[]): {[key: string]: any} { |
|
|
|
let positionalArguments: PositionalOption<any>[] = [] |
|
|
|
options.forEach(opt => { |
|
|
|
if ( opt instanceof PositionalOption ) { |
|
|
|
positionalArguments.push(opt) |
|
|
|
} |
|
|
|
}) |
|
|
|
|
|
|
|
const flagArguments: FlagOption<any>[] = [] |
|
|
|
options.forEach(opt => { |
|
|
|
if ( opt instanceof FlagOption ) { |
|
|
|
flagArguments.push(opt) |
|
|
|
} |
|
|
|
}) |
|
|
|
|
|
|
|
// @ts-ignore
|
|
|
|
const flagArguments: FlagOption<any>[] = options.filter(cls => cls instanceof FlagOption) |
|
|
|
const optionValue: any = {} |
|
|
|
|
|
|
|
flagArguments.push(this.getHelpOption()) |
|
|
@ -325,7 +344,7 @@ export abstract class Directive extends AppClass { |
|
|
|
const flagArgument = flagArguments.filter(x => x.shortFlag === value) |
|
|
|
if ( flagArgument.length < 1 ) { |
|
|
|
throw new OptionValidationError(`Unknown flag argument: ${value}`, { |
|
|
|
value |
|
|
|
value, |
|
|
|
}) |
|
|
|
} else { |
|
|
|
if ( flagArgument[0].argumentDescription ) { |
|
|
@ -350,7 +369,7 @@ export abstract class Directive extends AppClass { |
|
|
|
} else { |
|
|
|
if ( positionalArguments.length < 1 ) { |
|
|
|
throw new OptionValidationError(`Unknown positional argument: ${value}`, { |
|
|
|
value |
|
|
|
value, |
|
|
|
}) |
|
|
|
} else { |
|
|
|
const inferredValue = infer(value) |
|
|
@ -368,13 +387,13 @@ export abstract class Directive extends AppClass { |
|
|
|
|
|
|
|
if ( expectingFlagArgument ) { |
|
|
|
throw new OptionValidationError(`Missing argument for flag: ${positionalFlagName}`, { |
|
|
|
expecting: positionalFlagName |
|
|
|
expecting: positionalFlagName, |
|
|
|
}) |
|
|
|
} |
|
|
|
|
|
|
|
if ( positionalArguments.length > 0 ) { |
|
|
|
throw new OptionValidationError(`Missing required argument: ${positionalArguments[0].getArgumentName()}`, { |
|
|
|
expecting: positionalArguments[0].getArgumentName() |
|
|
|
expecting: positionalArguments[0].getArgumentName(), |
|
|
|
}) |
|
|
|
} |
|
|
|
|
|
|
@ -430,14 +449,18 @@ export abstract class Directive extends AppClass { |
|
|
|
* Determines if, at any point in the arguments, the help option's short or long flag appears. |
|
|
|
* @returns {boolean} - true if the help flag appeared |
|
|
|
*/ |
|
|
|
didRequestUsage(argv: string[]) { |
|
|
|
const help_option = this.getHelpOption() |
|
|
|
didRequestUsage(argv: string[]): boolean { |
|
|
|
const helpOption = this.getHelpOption() |
|
|
|
for ( const arg of argv ) { |
|
|
|
if ( arg.trim() === help_option.longFlag || arg.trim() === help_option.shortFlag ) { |
|
|
|
if ( arg.trim() === helpOption.longFlag || arg.trim() === helpOption.shortFlag ) { |
|
|
|
return true |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return false |
|
|
|
} |
|
|
|
|
|
|
|
protected nativeOutput(...outputs: any[]): void { |
|
|
|
console.log(...outputs) // eslint-disable-line no-console
|
|
|
|
} |
|
|
|
} |
|
|
|