cli-api 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -25
- package/dist/index.d.mts +394 -0
- package/dist/index.mjs +3 -0
- package/dist/interfaces-COq24bNI.mjs +391 -0
- package/dist/run-C903J5ca.mjs +1137 -0
- package/package.json +37 -37
- package/.hgignore +0 -12
- package/.idea/$CACHE_FILE$ +0 -6
- package/.idea/clap.iml +0 -8
- package/.idea/codeStyles/codeStyleConfig.xml +0 -5
- package/.idea/deployment.xml +0 -63
- package/.idea/inspectionProfiles/Project_Default.xml +0 -10
- package/.idea/misc.xml +0 -6
- package/.idea/modules.xml +0 -8
- package/.idea/vcs.xml +0 -6
- package/Makefile +0 -14
- package/babel.config.json +0 -33
- package/dist/cjs/index.js +0 -588
- package/dist/cjs/index.js.map +0 -1
- package/dist/es/index.mjs +0 -578
- package/dist/es/index.mjs.map +0 -1
- package/dist/types/app-help.d.ts +0 -3
- package/dist/types/commands/command-help.d.ts +0 -2
- package/dist/types/commands/version.d.ts +0 -2
- package/dist/types/constants.d.ts +0 -4
- package/dist/types/index.d.ts +0 -3
- package/dist/types/interfaces.d.ts +0 -79
- package/dist/types/options.d.ts +0 -7
- package/dist/types/print-command-help.d.ts +0 -2
- package/dist/types/run.d.ts +0 -2
- package/dist/types/utils.d.ts +0 -17
- package/rollup.config.js +0 -44
- package/src/app-help.ts +0 -34
- package/src/commands/command-help.ts +0 -28
- package/src/commands/version.ts +0 -11
- package/src/constants.ts +0 -4
- package/src/index.ts +0 -3
- package/src/interfaces.ts +0 -89
- package/src/options.ts +0 -266
- package/src/print-command-help.ts +0 -78
- package/src/run.ts +0 -45
- package/src/utils.ts +0 -86
- package/tsconfig.json +0 -32
package/dist/types/index.d.ts
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
export interface Command {
|
|
2
|
-
name: string;
|
|
3
|
-
alias?: string | string[];
|
|
4
|
-
description?: string;
|
|
5
|
-
longDescription?: string;
|
|
6
|
-
flags?: Flag[];
|
|
7
|
-
options?: Option[];
|
|
8
|
-
arguments?: Argument[];
|
|
9
|
-
/**
|
|
10
|
-
* Executed when the command matches.
|
|
11
|
-
*
|
|
12
|
-
* @param options Named arguments, options, and flags.
|
|
13
|
-
* @param args Positional arguments.
|
|
14
|
-
* @param app Entire app config.
|
|
15
|
-
*/
|
|
16
|
-
execute(options: Record<string, any>, args: string[], app: App): Promise<number | void>;
|
|
17
|
-
}
|
|
18
|
-
export declare enum OptType {
|
|
19
|
-
STRING = 0,
|
|
20
|
-
BOOL = 1,
|
|
21
|
-
INT = 2,
|
|
22
|
-
FLOAT = 3,
|
|
23
|
-
/** A string, truncated and converted to lowercase. */
|
|
24
|
-
ENUM = 4,
|
|
25
|
-
/** File must be readable. Single dash will be converted to STDIN. */
|
|
26
|
-
INPUT_FILE = 5,
|
|
27
|
-
/** Directory must be readable. */
|
|
28
|
-
INPUT_DIRECTORY = 6,
|
|
29
|
-
/** File's directory must exist and be writeable. Single dash will be converted to STDOUT. */
|
|
30
|
-
OUTPUT_FILE = 7,
|
|
31
|
-
OUTPUT_DIRECTORY = 8,
|
|
32
|
-
/** An empty or non-existent directory. */
|
|
33
|
-
EMPTY_DIRECTORY = 9
|
|
34
|
-
}
|
|
35
|
-
interface ArgumentOrOptionOrFlag {
|
|
36
|
-
/** Name of the option to display in help. */
|
|
37
|
-
name: string;
|
|
38
|
-
/** Alternative name for this option. */
|
|
39
|
-
alias?: string | string[];
|
|
40
|
-
/** Description of the option. */
|
|
41
|
-
description?: string;
|
|
42
|
-
/** Default value if not provided. */
|
|
43
|
-
defaultValue?: any | ((value: string) => any);
|
|
44
|
-
/** Default value to display in help. */
|
|
45
|
-
defaultValueText?: string;
|
|
46
|
-
/** Property name to use in `execute()` options. */
|
|
47
|
-
key?: string;
|
|
48
|
-
}
|
|
49
|
-
export declare type AnyOptType = OptType | string[];
|
|
50
|
-
export interface ArgumentOrOption extends ArgumentOrOptionOrFlag {
|
|
51
|
-
/** Type to coerce the option value to. */
|
|
52
|
-
type?: AnyOptType;
|
|
53
|
-
/** Option is repeatable by specifying the flag again. Value will be an array. */
|
|
54
|
-
repeatable?: boolean;
|
|
55
|
-
/** Option is required. */
|
|
56
|
-
required?: boolean;
|
|
57
|
-
}
|
|
58
|
-
/** Boolean flag. */
|
|
59
|
-
export interface Flag extends ArgumentOrOptionOrFlag, OptionOrFlag {
|
|
60
|
-
}
|
|
61
|
-
/** Positional argument. */
|
|
62
|
-
export interface Argument extends ArgumentOrOption {
|
|
63
|
-
}
|
|
64
|
-
interface OptionOrFlag {
|
|
65
|
-
valueNotRequired?: boolean;
|
|
66
|
-
}
|
|
67
|
-
/** Option with value. */
|
|
68
|
-
export interface Option extends ArgumentOrOption, OptionOrFlag {
|
|
69
|
-
/** Placeholder value to use in help. */
|
|
70
|
-
valuePlaceholder?: string;
|
|
71
|
-
}
|
|
72
|
-
export interface App {
|
|
73
|
-
name: string;
|
|
74
|
-
argv0?: string;
|
|
75
|
-
version?: string;
|
|
76
|
-
commands: Command[];
|
|
77
|
-
globalOptions?: Option[];
|
|
78
|
-
}
|
|
79
|
-
export {};
|
package/dist/types/options.d.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { App, Command, Option } from './interfaces';
|
|
2
|
-
export declare function formatOption(opt: Option): [string, string];
|
|
3
|
-
export declare function getValuePlaceholder(opt: Option): string;
|
|
4
|
-
export declare function getOptions(cmd: Command): Option[];
|
|
5
|
-
export declare function parseArgs(cmd: Command, argv: string[]): [any[], Record<string, any>];
|
|
6
|
-
export declare function getOptName(opt: Option): string;
|
|
7
|
-
export declare function getCommand(name: string, app: App): Command;
|
package/dist/types/run.d.ts
DELETED
package/dist/types/utils.d.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
2
|
-
import { App } from './interfaces';
|
|
3
|
-
import FileSys from 'fs';
|
|
4
|
-
export declare const print: {
|
|
5
|
-
(buffer: string | Uint8Array, cb?: ((err?: Error | undefined) => void) | undefined): boolean;
|
|
6
|
-
(str: string | Uint8Array, encoding?: string | undefined, cb?: ((err?: Error | undefined) => void) | undefined): boolean;
|
|
7
|
-
};
|
|
8
|
-
export declare const printLn: (message?: any, ...optionalParams: any[]) => void;
|
|
9
|
-
export declare function abort(message: string, code?: number): never;
|
|
10
|
-
export declare function toArray<T>(x: T | T[]): readonly T[];
|
|
11
|
-
export declare function resolve<T>(x: any): T;
|
|
12
|
-
export declare function toBool(str: string | boolean): boolean;
|
|
13
|
-
export declare function space(len: number, str?: string): string;
|
|
14
|
-
export declare function getProcName(app: App): string;
|
|
15
|
-
export declare function includes(needle: string, haystack: string | string[] | undefined): boolean;
|
|
16
|
-
export declare function statSync(path: string): FileSys.Stats | null;
|
|
17
|
-
export declare function sortBy<T>(arr: T[], cmp: (x: T) => string): T[];
|
package/rollup.config.js
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import babel from '@rollup/plugin-babel';
|
|
2
|
-
import nodeResolve from '@rollup/plugin-node-resolve';
|
|
3
|
-
import nodeExternals from 'rollup-plugin-node-externals'
|
|
4
|
-
import * as tsconfig from './tsconfig.json';
|
|
5
|
-
import * as pkg from './package.json'
|
|
6
|
-
import json from '@rollup/plugin-json';
|
|
7
|
-
const extensions = ['.ts'];
|
|
8
|
-
|
|
9
|
-
export default {
|
|
10
|
-
input: tsconfig.files,
|
|
11
|
-
plugins: [
|
|
12
|
-
nodeResolve({
|
|
13
|
-
extensions,
|
|
14
|
-
}),
|
|
15
|
-
nodeExternals({
|
|
16
|
-
builtins: true,
|
|
17
|
-
deps: true,
|
|
18
|
-
devDeps: false,
|
|
19
|
-
peerDeps: true,
|
|
20
|
-
optDeps: true,
|
|
21
|
-
}),
|
|
22
|
-
json(),
|
|
23
|
-
// TODO: change to typescript2 like node-mysql3c
|
|
24
|
-
babel({
|
|
25
|
-
exclude: 'node_modules/**',
|
|
26
|
-
extensions,
|
|
27
|
-
comments: false,
|
|
28
|
-
babelHelpers: 'bundled',
|
|
29
|
-
}),
|
|
30
|
-
],
|
|
31
|
-
output: [
|
|
32
|
-
{
|
|
33
|
-
file: pkg.main,
|
|
34
|
-
format: 'cjs',
|
|
35
|
-
sourcemap: true,
|
|
36
|
-
exports: 'named',
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
file: pkg.module,
|
|
40
|
-
format: 'es',
|
|
41
|
-
sourcemap: true,
|
|
42
|
-
},
|
|
43
|
-
],
|
|
44
|
-
}
|
package/src/app-help.ts
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import {App} from './interfaces'
|
|
2
|
-
import {getProcName, print, printLn, space} from './utils'
|
|
3
|
-
import Chalk from 'chalk'
|
|
4
|
-
import stringWidth from 'string-width'
|
|
5
|
-
|
|
6
|
-
export function printHelp(app: App) {
|
|
7
|
-
print(Chalk.green(app.name))
|
|
8
|
-
if (app.version) {
|
|
9
|
-
print(` version ${Chalk.yellow(app.version)}`)
|
|
10
|
-
}
|
|
11
|
-
print('\n\n')
|
|
12
|
-
printLn(Chalk.yellow("Usage:"))
|
|
13
|
-
printLn(` ${Chalk.cyan(getProcName(app))} ${Chalk.gray('<')}command${Chalk.gray('>')} ${Chalk.gray(`[options] [arguments]`)}\n`)
|
|
14
|
-
|
|
15
|
-
if (app.globalOptions) {
|
|
16
|
-
printLn("TODO")
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
printAvailableCommands(app)
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function printAvailableCommands(app: App) {
|
|
23
|
-
printLn(Chalk.yellow("Available commands:"))
|
|
24
|
-
const width = Math.max(...app.commands.map(c => stringWidth(c.name))) + 2
|
|
25
|
-
for (const cmd of app.commands) {
|
|
26
|
-
print(` ${Chalk.green(cmd.name)}`)
|
|
27
|
-
if (cmd.description) {
|
|
28
|
-
print(`${space(width, cmd.name)}${cmd.description}`)
|
|
29
|
-
}
|
|
30
|
-
printLn()
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// printLn()
|
|
34
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import {App, Command} from '../interfaces'
|
|
2
|
-
import {getCommand} from '../options'
|
|
3
|
-
import {printCommandHelp} from '../print-command-help'
|
|
4
|
-
import {printAvailableCommands} from '../app-help'
|
|
5
|
-
import {printLn} from '../utils'
|
|
6
|
-
|
|
7
|
-
export const helpCommand: Command = {
|
|
8
|
-
name: 'help',
|
|
9
|
-
// alias: '--help',
|
|
10
|
-
description: "Displays help for a command",
|
|
11
|
-
arguments: [
|
|
12
|
-
{
|
|
13
|
-
name: "command",
|
|
14
|
-
description: "The command name.",
|
|
15
|
-
required: false,
|
|
16
|
-
}
|
|
17
|
-
],
|
|
18
|
-
async execute(options: Record<string, string>, [commandName]: string[], app: App) {
|
|
19
|
-
if(commandName) {
|
|
20
|
-
printCommandHelp(app, getCommand(commandName, app))
|
|
21
|
-
} else {
|
|
22
|
-
printCommandHelp(app, getCommand('help', app))
|
|
23
|
-
printLn()
|
|
24
|
-
printAvailableCommands(app)
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
package/src/commands/version.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import {Command} from '../interfaces'
|
|
2
|
-
import {printLn} from '../utils'
|
|
3
|
-
|
|
4
|
-
export const versionCommand: Command = {
|
|
5
|
-
name: 'version',
|
|
6
|
-
// alias: '--version',
|
|
7
|
-
description: "Displays current version",
|
|
8
|
-
async execute(opts, args, app) {
|
|
9
|
-
printLn(app.version)
|
|
10
|
-
}
|
|
11
|
-
}
|
package/src/constants.ts
DELETED
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
export const EMPTY_ARRAY: ReadonlyArray<any> = Object.freeze([])
|
|
2
|
-
export const EMPTY_OBJECT: Record<string,any> = Object.freeze(Object.create({__proto__:null}))
|
|
3
|
-
export const TRUE_VALUES = new Set(['y', 'yes', 't', 'true', '1', 'on'])
|
|
4
|
-
export const FALSE_VALUES = new Set(['n', 'no', 'f', 'false', '0', 'off'])
|
package/src/index.ts
DELETED
package/src/interfaces.ts
DELETED
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
export interface Command {
|
|
2
|
-
name: string
|
|
3
|
-
alias?: string|string[]
|
|
4
|
-
description?: string
|
|
5
|
-
longDescription?: string
|
|
6
|
-
flags?: Flag[]
|
|
7
|
-
options?: Option[]
|
|
8
|
-
arguments?: Argument[]
|
|
9
|
-
/**
|
|
10
|
-
* Executed when the command matches.
|
|
11
|
-
*
|
|
12
|
-
* @param options Named arguments, options, and flags.
|
|
13
|
-
* @param args Positional arguments.
|
|
14
|
-
* @param app Entire app config.
|
|
15
|
-
*/
|
|
16
|
-
execute(options: Record<string,any>, args: string[], app: App): Promise<number|void>
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export enum OptType {
|
|
20
|
-
STRING,
|
|
21
|
-
BOOL,
|
|
22
|
-
INT,
|
|
23
|
-
FLOAT,
|
|
24
|
-
/** A string, truncated and converted to lowercase. */
|
|
25
|
-
ENUM,
|
|
26
|
-
/** File must be readable. Single dash will be converted to STDIN. */
|
|
27
|
-
INPUT_FILE,
|
|
28
|
-
/** Directory must be readable. */
|
|
29
|
-
INPUT_DIRECTORY,
|
|
30
|
-
/** File's directory must exist and be writeable. Single dash will be converted to STDOUT. */
|
|
31
|
-
OUTPUT_FILE,
|
|
32
|
-
OUTPUT_DIRECTORY,
|
|
33
|
-
/** An empty or non-existent directory. */
|
|
34
|
-
EMPTY_DIRECTORY,
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
interface ArgumentOrOptionOrFlag {
|
|
38
|
-
/** Name of the option to display in help. */
|
|
39
|
-
name: string
|
|
40
|
-
/** Alternative name for this option. */
|
|
41
|
-
alias?: string|string[]
|
|
42
|
-
/** Description of the option. */
|
|
43
|
-
description?: string
|
|
44
|
-
/** Default value if not provided. */
|
|
45
|
-
defaultValue?: any|((value:string)=>any)
|
|
46
|
-
/** Default value to display in help. */
|
|
47
|
-
defaultValueText?: string
|
|
48
|
-
/** Property name to use in `execute()` options. */
|
|
49
|
-
key?: string
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export type AnyOptType = OptType | string[] // | ((value:string)=>any)
|
|
53
|
-
|
|
54
|
-
export interface ArgumentOrOption extends ArgumentOrOptionOrFlag {
|
|
55
|
-
/** Type to coerce the option value to. */
|
|
56
|
-
type?: AnyOptType
|
|
57
|
-
/** Option is repeatable by specifying the flag again. Value will be an array. */
|
|
58
|
-
repeatable?: boolean
|
|
59
|
-
/** Option is required. */
|
|
60
|
-
required?: boolean
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/** Boolean flag. */
|
|
64
|
-
export interface Flag extends ArgumentOrOptionOrFlag,OptionOrFlag {
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/** Positional argument. */
|
|
69
|
-
export interface Argument extends ArgumentOrOption {
|
|
70
|
-
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
interface OptionOrFlag {
|
|
74
|
-
valueNotRequired?: boolean
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/** Option with value. */
|
|
78
|
-
export interface Option extends ArgumentOrOption,OptionOrFlag {
|
|
79
|
-
/** Placeholder value to use in help. */
|
|
80
|
-
valuePlaceholder?: string
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export interface App {
|
|
84
|
-
name: string
|
|
85
|
-
argv0?: string
|
|
86
|
-
version?: string
|
|
87
|
-
commands: Command[]
|
|
88
|
-
globalOptions?: Option[]
|
|
89
|
-
}
|
package/src/options.ts
DELETED
|
@@ -1,266 +0,0 @@
|
|
|
1
|
-
import {AnyOptType, App, Command, Option, OptType} from './interfaces'
|
|
2
|
-
import {abort, includes, resolve, statSync, toArray, toBool} from './utils'
|
|
3
|
-
import Chalk from 'chalk'
|
|
4
|
-
import Path from 'path'
|
|
5
|
-
import FileSys from 'fs'
|
|
6
|
-
|
|
7
|
-
export function formatOption(opt: Option): [string, string] {
|
|
8
|
-
const aliases: string[] = []
|
|
9
|
-
if (opt.alias) {
|
|
10
|
-
if (Array.isArray(opt.alias)) {
|
|
11
|
-
aliases.push(...opt.alias)
|
|
12
|
-
} else {
|
|
13
|
-
aliases.push(opt.alias)
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
aliases.push(opt.name)
|
|
17
|
-
let flags = aliases.map(a => Chalk.green(a.length === 1 ? `-${a}` : `--${a}`)).join(', ')
|
|
18
|
-
let valuePlaceholder = getValuePlaceholder(opt)
|
|
19
|
-
if (opt.type !== OptType.BOOL) {
|
|
20
|
-
flags += `=${valuePlaceholder}`
|
|
21
|
-
}
|
|
22
|
-
let desc = opt.description ?? ''
|
|
23
|
-
let defaultValueText = opt.defaultValueText
|
|
24
|
-
if (defaultValueText === undefined && opt.defaultValue !== undefined) {
|
|
25
|
-
defaultValueText = JSON.stringify(resolve(opt.defaultValue))
|
|
26
|
-
}
|
|
27
|
-
if (defaultValueText !== undefined) {
|
|
28
|
-
desc += Chalk.yellow(` [default: ${defaultValueText}]`)
|
|
29
|
-
}
|
|
30
|
-
return [flags, desc]
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function getValuePlaceholder(opt: Option): string {
|
|
34
|
-
if (opt.valuePlaceholder !== undefined) {
|
|
35
|
-
return opt.valuePlaceholder
|
|
36
|
-
}
|
|
37
|
-
if (Array.isArray(opt.type)) {
|
|
38
|
-
return opt.type.join('|')
|
|
39
|
-
} else if (opt.type == OptType.BOOL) {
|
|
40
|
-
return JSON.stringify(!resolve(opt.defaultValue))
|
|
41
|
-
} else if (opt.type === OptType.INT || opt.type === OptType.FLOAT) {
|
|
42
|
-
return '#'
|
|
43
|
-
} else if (opt.type === OptType.INPUT_FILE || opt.type === OptType.OUTPUT_FILE) {
|
|
44
|
-
return 'FILE'
|
|
45
|
-
} else if (opt.type === OptType.INPUT_DIRECTORY || opt.type === OptType.OUTPUT_DIRECTORY || opt.type === OptType.EMPTY_DIRECTORY) {
|
|
46
|
-
return 'DIR'
|
|
47
|
-
} else {
|
|
48
|
-
return opt.name
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export function getOptions(cmd: Command): Option[] {
|
|
53
|
-
return [
|
|
54
|
-
...toArray(cmd.options),
|
|
55
|
-
...toArray(cmd.flags).map(f => ({
|
|
56
|
-
...f,
|
|
57
|
-
valueNotRequired: true,
|
|
58
|
-
type: OptType.BOOL,
|
|
59
|
-
|
|
60
|
-
})),
|
|
61
|
-
// {
|
|
62
|
-
// name: 'help',
|
|
63
|
-
// description: "Print help for this command",
|
|
64
|
-
// valueNotRequired: true,
|
|
65
|
-
// type: OptType.BOOL,
|
|
66
|
-
// }
|
|
67
|
-
] as Option[]
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export function parseArgs(cmd: Command, argv: string[]): [any[], Record<string, any>] {
|
|
71
|
-
const args: any[] = []
|
|
72
|
-
const opts: Record<string, any> = Object.create(null)
|
|
73
|
-
let parseFlags = true
|
|
74
|
-
// TODO: initialize all repeatables to empty arrays
|
|
75
|
-
|
|
76
|
-
const allOptions = getOptions(cmd)
|
|
77
|
-
|
|
78
|
-
let argIdx = 0
|
|
79
|
-
for (let i = 0; i < argv.length; ++i) {
|
|
80
|
-
let arg = argv[i]
|
|
81
|
-
|
|
82
|
-
if (parseFlags && arg === '--') {
|
|
83
|
-
parseFlags = false
|
|
84
|
-
continue
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
if (parseFlags && arg.length >= 2 && arg.startsWith('-')) {
|
|
88
|
-
let name: string
|
|
89
|
-
let value: any
|
|
90
|
-
if (arg.includes('=')) {
|
|
91
|
-
[arg, value] = arg.split('=', 2)
|
|
92
|
-
} /*else if(i < argv.length - 2 && argv[i+1] === '=') {
|
|
93
|
-
value = argv[i+2]
|
|
94
|
-
i += 2
|
|
95
|
-
}*/
|
|
96
|
-
if (arg.startsWith('--')) {
|
|
97
|
-
name = arg.slice(2)
|
|
98
|
-
} else {
|
|
99
|
-
if (arg.length > 2) {
|
|
100
|
-
if (value !== undefined) {
|
|
101
|
-
abort(`Malformed option "${arg}"`)
|
|
102
|
-
}
|
|
103
|
-
value = arg.slice(2)
|
|
104
|
-
arg = arg.slice(0, 2)
|
|
105
|
-
}
|
|
106
|
-
name = arg.slice(1)
|
|
107
|
-
// TODO: parse multiple single-char flags
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const opt = allOptions.find(opt => opt.name === name || includes(name, opt.alias))
|
|
111
|
-
if (!opt) {
|
|
112
|
-
abort(`"${cmd.name}" command does not have option "${name}".`)
|
|
113
|
-
}
|
|
114
|
-
if (value === undefined) {
|
|
115
|
-
if (opt.valueNotRequired) {
|
|
116
|
-
value = !resolve(opt.defaultValue)
|
|
117
|
-
} else if (i < argv.length - 1) {
|
|
118
|
-
value = argv[++i]
|
|
119
|
-
} else {
|
|
120
|
-
abort(`Missing required value for option "${arg}"`)
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
if (opt.type != null) {
|
|
124
|
-
value = coerceType(value, opt.type)
|
|
125
|
-
}
|
|
126
|
-
opts[opt.key ?? opt.name] = value
|
|
127
|
-
} else {
|
|
128
|
-
// TODO: examine cmd.arguments
|
|
129
|
-
let value: any = arg
|
|
130
|
-
|
|
131
|
-
if (cmd.arguments && cmd.arguments.length > argIdx) {
|
|
132
|
-
const cmdArg = cmd.arguments[argIdx]
|
|
133
|
-
if (cmdArg.type != null) {
|
|
134
|
-
value = coerceType(value, cmdArg.type)
|
|
135
|
-
}
|
|
136
|
-
if (cmdArg.key) {
|
|
137
|
-
opts[cmdArg.key] = value
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
args.push(value)
|
|
141
|
-
++argIdx
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
if (allOptions.length) {
|
|
146
|
-
for (const opt of allOptions) {
|
|
147
|
-
const k = opt.key ?? opt.name
|
|
148
|
-
if (opts[k] === undefined) {
|
|
149
|
-
if (opt.defaultValue !== undefined) {
|
|
150
|
-
opts[k] = resolve(opt.defaultValue)
|
|
151
|
-
} else if (opt.required) {
|
|
152
|
-
throw new Error(`"${getOptName(opt)}" option is required`)
|
|
153
|
-
} else {
|
|
154
|
-
// TODO: should we fill in undefined options? with `null` or `undefined`?
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
if (cmd.arguments?.length) {
|
|
161
|
-
for (let i = 0; i < cmd.arguments.length; ++i) {
|
|
162
|
-
if (cmd.arguments[i].required && argIdx <= i) {
|
|
163
|
-
throw new Error(`"${cmd.arguments[i].name}" argument is required`)
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// TODO: fill global options into opts
|
|
169
|
-
// TODO: copy named arguments into opts too
|
|
170
|
-
return [args, opts]
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
function coerceType(value: string, type: AnyOptType) {
|
|
174
|
-
if (Array.isArray(type)) {
|
|
175
|
-
// TODO: search for closest match of value in type or throw error
|
|
176
|
-
return String(value).trim().toLowerCase()
|
|
177
|
-
}
|
|
178
|
-
switch (type) {
|
|
179
|
-
case OptType.BOOL:
|
|
180
|
-
return toBool(value)
|
|
181
|
-
case OptType.INT:
|
|
182
|
-
return Math.trunc(Number(value))
|
|
183
|
-
case OptType.FLOAT:
|
|
184
|
-
return Number(value)
|
|
185
|
-
case OptType.ENUM:
|
|
186
|
-
return String(value).trim().toLowerCase()
|
|
187
|
-
case OptType.STRING:
|
|
188
|
-
return String(value)
|
|
189
|
-
case OptType.INPUT_FILE: {
|
|
190
|
-
if (value === '-') return '/dev/stdin' // TODO: support windows
|
|
191
|
-
const file = Path.normalize(value)
|
|
192
|
-
const fullPath = Path.resolve(file)
|
|
193
|
-
const stat = statSync(file)
|
|
194
|
-
if (!stat) {
|
|
195
|
-
throw new Error(`File ${Chalk.underline(fullPath)} does not exist`)
|
|
196
|
-
}
|
|
197
|
-
if (!stat.isFile()) {
|
|
198
|
-
throw new Error(`${Chalk.underline(fullPath)} is not a file`)
|
|
199
|
-
}
|
|
200
|
-
try {
|
|
201
|
-
FileSys.accessSync(file, FileSys.constants.R_OK)
|
|
202
|
-
} catch (err) {
|
|
203
|
-
throw new Error(`${Chalk.underline(fullPath)} is not readable`)
|
|
204
|
-
}
|
|
205
|
-
return file
|
|
206
|
-
}
|
|
207
|
-
case OptType.INPUT_DIRECTORY:
|
|
208
|
-
const dir = Path.normalize(value)
|
|
209
|
-
FileSys.accessSync(dir, FileSys.constants.X_OK)
|
|
210
|
-
return dir
|
|
211
|
-
case OptType.OUTPUT_FILE: {
|
|
212
|
-
if (value === '-') return '/dev/stdout' // TODO: support windows
|
|
213
|
-
const file = Path.normalize(value)
|
|
214
|
-
const stat = statSync(file)
|
|
215
|
-
if (stat) {
|
|
216
|
-
if (!stat.isFile()) {
|
|
217
|
-
throw new Error(`'${file}' is not a file`)
|
|
218
|
-
}
|
|
219
|
-
// if((stat.mode & 0x222) === 0) { // TODO: does this work?
|
|
220
|
-
// throw new Error(`'${value}' is not writeable`);
|
|
221
|
-
// }
|
|
222
|
-
FileSys.accessSync(file, FileSys.constants.W_OK)
|
|
223
|
-
} else {
|
|
224
|
-
FileSys.accessSync(Path.dirname(file), FileSys.constants.W_OK)
|
|
225
|
-
}
|
|
226
|
-
return file
|
|
227
|
-
}
|
|
228
|
-
case OptType.OUTPUT_DIRECTORY: {
|
|
229
|
-
FileSys.accessSync(value, FileSys.constants.W_OK)
|
|
230
|
-
return Path.normalize(value)
|
|
231
|
-
}
|
|
232
|
-
case OptType.EMPTY_DIRECTORY: {
|
|
233
|
-
const dir = Path.normalize(value)
|
|
234
|
-
let files = []
|
|
235
|
-
try {
|
|
236
|
-
files = FileSys.readdirSync(dir)
|
|
237
|
-
} catch (err) {
|
|
238
|
-
if (err.code === 'ENOENT') {
|
|
239
|
-
FileSys.accessSync(Path.dirname(dir), FileSys.constants.W_OK)
|
|
240
|
-
} else {
|
|
241
|
-
throw err
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
if (files.length) {
|
|
245
|
-
throw new Error(`${Chalk.underline(dir)} is not empty`)
|
|
246
|
-
}
|
|
247
|
-
return dir
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
return value
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
export function getOptName(opt: Option) {
|
|
255
|
-
return (opt.name.length > 1 ? '--' : '-') + opt.name
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
export function getCommand(name: string, app: App): Command {
|
|
259
|
-
const cmdName = String(name).trim().replace(/^-{1,2}/,'').toLowerCase()
|
|
260
|
-
const cmd = app.commands.find(c => c.name === cmdName || includes(cmdName, c.alias))
|
|
261
|
-
if (cmd === undefined) {
|
|
262
|
-
// TODO: levenshtein search for closest match? "Did you mean...?"
|
|
263
|
-
throw new Error(`Command "${name}" does not exist.`)
|
|
264
|
-
}
|
|
265
|
-
return cmd
|
|
266
|
-
}
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import {App, Command, OptType} from './interfaces'
|
|
2
|
-
import {getProcName, print, printLn, space, toArray} from './utils'
|
|
3
|
-
import Chalk from 'chalk'
|
|
4
|
-
import {formatOption, getOptions, getOptName, getValuePlaceholder} from './options'
|
|
5
|
-
import stringWidth from 'string-width'
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
export function printCommandHelp(app: App, cmd: Command) {
|
|
9
|
-
if (cmd.description) {
|
|
10
|
-
printLn(cmd.description)
|
|
11
|
-
printLn()
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
printLn(Chalk.yellow("Usage:"))
|
|
15
|
-
print(` ${Chalk.cyan(getProcName(app))} ${cmd.name}`)
|
|
16
|
-
|
|
17
|
-
const allOptions = getOptions(cmd)
|
|
18
|
-
|
|
19
|
-
if (allOptions.length) {
|
|
20
|
-
let otherOptions = 0
|
|
21
|
-
for (let opt of allOptions) {
|
|
22
|
-
if (opt.required) {
|
|
23
|
-
print(` ${getOptName(opt)}`)
|
|
24
|
-
if (opt.type !== OptType.BOOL) {
|
|
25
|
-
print(`=${getValuePlaceholder(opt)}`)
|
|
26
|
-
}
|
|
27
|
-
} else {
|
|
28
|
-
++otherOptions
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
if (otherOptions) {
|
|
32
|
-
print(` ${Chalk.gray('[')}options${Chalk.gray(']')}`)
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
if (cmd.arguments?.length) {
|
|
36
|
-
print(` ${Chalk.grey('[')}--${Chalk.grey(']')}`)
|
|
37
|
-
for (const arg of cmd.arguments) {
|
|
38
|
-
print(' ')
|
|
39
|
-
print(Chalk.grey(arg.required ? '<' : '['))
|
|
40
|
-
if (arg.repeatable) {
|
|
41
|
-
print(Chalk.grey('...'))
|
|
42
|
-
}
|
|
43
|
-
print(arg.name)
|
|
44
|
-
print(Chalk.grey(arg.required ? '>' : ']'))
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
printLn()
|
|
48
|
-
|
|
49
|
-
if (allOptions.length) {
|
|
50
|
-
printLn(Chalk.yellow("\nOptions:"))
|
|
51
|
-
const lines = allOptions.map(formatOption)
|
|
52
|
-
const width = Math.max(...lines.map(l => stringWidth(l[0])))
|
|
53
|
-
for (const line of lines) {
|
|
54
|
-
printLn(' ' + line[0] + space(width + 2, line[0]) + line[1])
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
if (cmd.arguments?.length) {
|
|
59
|
-
printLn(Chalk.yellow("\nArguments:"))
|
|
60
|
-
const width = Math.max(...cmd.arguments.map(a => stringWidth(a.name)))
|
|
61
|
-
for (const arg of cmd.arguments) {
|
|
62
|
-
print(' ' + Chalk.green(arg.name))
|
|
63
|
-
if (arg.description) {
|
|
64
|
-
print(space(width + 2, arg.name) + arg.description)
|
|
65
|
-
}
|
|
66
|
-
printLn()
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
if (cmd.alias) {
|
|
71
|
-
const alaises = toArray(cmd.alias)
|
|
72
|
-
printLn(Chalk.yellow(`\nAlias${alaises.length !== 1 ? 'es' : ''}: `) + toArray(cmd.alias).join(Chalk.gray(', ')))
|
|
73
|
-
}
|
|
74
|
-
if (cmd.longDescription) {
|
|
75
|
-
printLn(Chalk.yellow("\nDescription:"))
|
|
76
|
-
printLn(' ' + cmd.longDescription)
|
|
77
|
-
}
|
|
78
|
-
}
|