Skip to content

Commit

Permalink
feat: export parser interfaces in Interfaces (#46)
Browse files Browse the repository at this point in the history
  • Loading branch information
RasPhilCo committed Sep 18, 2020
1 parent 5d4212f commit d5ad46d
Show file tree
Hide file tree
Showing 19 changed files with 241 additions and 242 deletions.
7 changes: 3 additions & 4 deletions src/command.ts
Expand Up @@ -6,7 +6,6 @@ import * as Errors from './errors'
import {PrettyPrintableError} from './errors'
import * as Parser from './parser'
import {HelpBase, getHelpClass} from './help'
import * as flags from './flags'
import {sortBy, uniqBy} from './util'

const pjson = require('../package.json')
Expand Down Expand Up @@ -58,10 +57,10 @@ export default abstract class Command {
static parse = true

/** A hash of flags for the command */
static flags?: flags.Input<any>
static flags?: Interfaces.FlagInput<any>

/** An order-dependent array of arguments for the command */
static args?: Parser.args.Input
static args?: Interfaces.ArgInput

static plugin: Interfaces.Plugin | undefined

Expand Down Expand Up @@ -158,7 +157,7 @@ export default abstract class Command {
if (this._helpOverride()) return this._help()
}

protected parse<F, A extends {[name: string]: any}>(options?: Parser.Input<F>, argv = this.argv): Parser.Output<F, A> {
protected parse<F, A extends { [name: string]: any }>(options?: Interfaces.Input<F>, argv = this.argv): Interfaces.ParserOutput<F, A> {
if (!options) options = this.constructor as any
return Parser.parse(argv, {context: this, ...options})
}
Expand Down
47 changes: 9 additions & 38 deletions src/flags.ts
@@ -1,63 +1,34 @@
import {Config} from './interfaces/config'
import {OptionFlag, Definition, BooleanFlag, EnumFlagOptions} from './interfaces'
import * as Parser from './parser'
import Command from './command'

export type ICompletionContext = {
args?: { [name: string]: string };
flags?: { [name: string]: string };
argv?: string[];
config: Config;
}

export type ICompletion = {
skipCache?: boolean;
cacheDuration?: number;
cacheKey?(ctx: ICompletionContext): Promise<string>;
options(ctx: ICompletionContext): Promise<string[]>;
}

export type IOptionFlag<T> = Parser.flags.IOptionFlag<T> & {
completion?: ICompletion;
}

export type IFlag<T> = Parser.flags.IBooleanFlag<T> | IOptionFlag<T>

export type Output = Parser.flags.Output
export type Input<T extends Output> = { [P in keyof T]: IFlag<T[P]> }

export type Definition<T> = {
(options: {multiple: true} & Partial<IOptionFlag<T[]>>): IOptionFlag<T[]>;
(options: ({required: true} | {default: Parser.flags.Default<T>}) & Partial<IOptionFlag<T>>): IOptionFlag<T>;
(options?: Partial<IOptionFlag<T>>): IOptionFlag<T | undefined>;
}

export function build<T>(defaults: {parse: IOptionFlag<T>['parse']} & Partial<IOptionFlag<T>>): Definition<T>
export function build(defaults: Partial<IOptionFlag<string>>): Definition<string>
export function build<T>(defaults: Partial<IOptionFlag<T>>): Definition<T> {
export function build<T>(defaults: {parse: OptionFlag<T>['parse']} & Partial<OptionFlag<T>>): Definition<T>
export function build(defaults: Partial<OptionFlag<string>>): Definition<string>
export function build<T>(defaults: Partial<OptionFlag<T>>): Definition<T> {
return Parser.flags.build<T>(defaults as any)
}

export function option<T>(options: {parse: IOptionFlag<T>['parse']} & Partial<IOptionFlag<T>>) {
export function option<T>(options: {parse: OptionFlag<T>['parse']} & Partial<OptionFlag<T>>) {
return build<T>(options)()
}

const _enum = <T = string>(opts: Parser.flags.EnumFlagOptions<T>): IOptionFlag<T> => {
const _enum = <T = string>(opts: EnumFlagOptions<T>): OptionFlag<T> => {
return build<T>({
parse(input) {
if (!opts.options.includes(input)) throw new Error(`Expected --${this.name}=${input} to be one of: ${opts.options.join(', ')}`)
return input as unknown as T
},
helpValue: `(${opts.options.join('|')})`,
...opts,
})() as IOptionFlag<T>
})() as OptionFlag<T>
}
export {_enum as enum}

const stringFlag = build({})
export {stringFlag as string}
export {boolean, integer} from './parser'

export const version = (opts: Partial<Parser.flags.IBooleanFlag<boolean>> = {}) => {
export const version = (opts: Partial<BooleanFlag<boolean>> = {}) => {
return Parser.flags.boolean({
// char: 'v',
description: 'show CLI version',
Expand All @@ -69,7 +40,7 @@ export const version = (opts: Partial<Parser.flags.IBooleanFlag<boolean>> = {})
})
}

export const help = (opts: Partial<Parser.flags.IBooleanFlag<boolean>> = {}) => {
export const help = (opts: Partial<BooleanFlag<boolean>> = {}) => {
return Parser.flags.boolean({
// char: 'h',
description: 'show CLI help',
Expand Down
File renamed without changes.
7 changes: 3 additions & 4 deletions src/interfaces/command.ts
@@ -1,7 +1,6 @@
import * as Parser from '../parser'

import {Config, LoadOptions} from './config'
import {Plugin} from './plugin'
import {ArgInput, FlagInput} from './parser'

export interface Command {
id: string;
Expand Down Expand Up @@ -66,8 +65,8 @@ export namespace Command {

export interface Class extends Base {
plugin?: Plugin;
flags?: Parser.flags.Input<any>;
args?: Parser.args.Input;
flags?: FlagInput<any>;
args?: ArgInput;
new(argv: string[], config: Config): Instance;
run(argv?: string[], config?: LoadOptions): PromiseLike<any>;
}
Expand Down
Empty file added src/interfaces/flags.ts
Empty file.
10 changes: 10 additions & 0 deletions src/interfaces/index.ts
@@ -1,9 +1,19 @@
export {AlphabetLowercase, AlphabetUppercase} from './alphabet'
export {Config, ArchTypes, PlatformTypes, LoadOptions} from './config'
export {Command} from './command'
export {OclifError, PrettyPrintableError} from './errors'
export {HelpOptions} from './help'
export {Hook, HookKeyOrOptions, Hooks} from './hooks'
export {Manifest} from './manifest'
export {
ParserArg, Arg, ParseFn, ParserOutput, ParserInput, ArgToken,
OptionalArg, FlagOutput, OutputArgs, OutputFlags, FlagUsageOptions,
CLIParseErrorOptions, ArgInput, RequiredArg, Metadata, ParsingToken,
FlagToken, List, ListItem, BooleanFlag, Flag, FlagBase, OptionFlag,
Input, EnumFlagOptions, DefaultContext, Default, Definition,
CompletableOptionFlag, Completion, CompletionContext, FlagInput,
CompletableFlag,
} from './parser'
export {PJSON} from './pjson'
export {Plugin, PluginOptions, Options} from './plugin'
export {Topic} from './topic'
Expand Down
169 changes: 169 additions & 0 deletions src/interfaces/parser.ts
@@ -0,0 +1,169 @@
import {AlphabetLowercase, AlphabetUppercase} from './alphabet'
import {Config} from './config'

export type ParseFn<T> = (input: string) => T

export interface Arg<T = string> {
name: string;
description?: string;
required?: boolean;
hidden?: boolean;
parse?: ParseFn<T>;
default?: T | (() => T);
options?: string[];
}

export interface ArgBase<T> {
name?: string;
description?: string;
hidden?: boolean;
parse: ParseFn<T>;
default?: T | (() => T);
input?: string;
options?: string[];
}

export type RequiredArg<T> = ArgBase<T> & {
required: true;
value: T;
}

export type OptionalArg<T> = ArgBase<T> & {
required: false;
value?: T;
}

export type ParserArg<T> = RequiredArg<T> | OptionalArg<T>

export interface FlagOutput { [name: string]: any }
export type ArgInput = Arg<any>[]

export interface CLIParseErrorOptions {
parse: {
input?: ParserInput;
output?: ParserOutput<any, any>;
};
}

export type OutputArgs<T extends ParserInput['args']> = { [P in keyof T]: any }
export type OutputFlags<T extends ParserInput['flags']> = { [P in keyof T]: any }
export type ParserOutput<TFlags extends OutputFlags<any>, TArgs extends OutputArgs<any>> = {
flags: TFlags;
args: TArgs;
argv: string[];
raw: ParsingToken[];
metadata: Metadata;
}

export type ArgToken = { type: 'arg'; input: string }
export type FlagToken = { type: 'flag'; flag: string; input: string }
export type ParsingToken = ArgToken | FlagToken

export interface FlagUsageOptions { displayRequired?: boolean }

export type Metadata = {
flags: { [key: string]: MetadataFlag };
}

type MetadataFlag = {
setFromDefault?: boolean;
}

export type ListItem = [string, string | undefined]
export type List = ListItem[]

export type DefaultContext<T> = {
options: OptionFlag<T>;
flags: { [k: string]: string };
}

export type Default<T> = T | ((context: DefaultContext<T>) => T)

export type FlagBase<T, I> = {
name: string;
char?: AlphabetLowercase | AlphabetUppercase;
description?: string;
helpLabel?: string;
hidden?: boolean;
required?: boolean;
dependsOn?: string[];
exclusive?: string[];
exactlyOne?: string[];
/**
* also accept an environment variable as input
*/
env?: string;
parse(input: I, context: any): T;
}

export type BooleanFlag<T> = FlagBase<T, boolean> & {
type: 'boolean';
allowNo: boolean;
/**
* specifying a default of false is the same not specifying a default
*/
default?: Default<boolean>;
}

export type OptionFlag<T> = FlagBase<T, string> & {
type: 'option';
helpValue?: string;
default?: Default<T | undefined>;
multiple: boolean;
input: string[];
options?: string[];
}

export type Definition<T> = {
(options: { multiple: true } & Partial<OptionFlag<T[]>>): OptionFlag<T[]>;
(
options: ({ required: true } | { default: Default<T> }) &
Partial<OptionFlag<T>>,
): OptionFlag<T>;
(options?: Partial<OptionFlag<T>>): OptionFlag<T | undefined>;
}

export type EnumFlagOptions<T> = Partial<OptionFlag<T>> & {
options: T[];
}

export type Flag<T> = BooleanFlag<T> | OptionFlag<T>

export type Input<TFlags extends FlagOutput> = {
flags?: FlagInput<TFlags>;
args?: ArgInput;
strict?: boolean;
context?: any;
'--'?: boolean;
}

export interface ParserInput {
argv: string[];
flags: FlagInput<any>;
args: ParserArg<any>[];
strict: boolean;
context: any;
'--'?: boolean;
}

export type CompletionContext = {
args?: { [name: string]: string };
flags?: { [name: string]: string };
argv?: string[];
config: Config;
}

export type Completion = {
skipCache?: boolean;
cacheDuration?: number;
cacheKey?(ctx: CompletionContext): Promise<string>;
options(ctx: CompletionContext): Promise<string[]>;
}

export type CompletableOptionFlag<T> = OptionFlag<T> & {
completion?: Completion;
}

export type CompletableFlag<T> = BooleanFlag<T> | CompletableOptionFlag<T>

export type FlagInput<T extends FlagOutput> = { [P in keyof T]: CompletableFlag<T[P]> }
44 changes: 4 additions & 40 deletions src/parser/args.ts
@@ -1,47 +1,11 @@
export type ParseFn<T> = (input: string) => T
import {ParserArg, Arg, ParseFn} from '../interfaces'

// eslint-disable-next-line @typescript-eslint/interface-name-prefix
export interface IArg<T = string> {
name: string;
description?: string;
required?: boolean;
hidden?: boolean;
parse?: ParseFn<T>;
default?: T | (() => T);
options?: string[];
}

export interface ArgBase<T> {
name?: string;
description?: string;
hidden?: boolean;
parse: ParseFn<T>;
default?: T | (() => T);
input?: string;
options?: string[];
}

export type RequiredArg<T> = ArgBase<T> & {
required: true;
value: T;
}

export type OptionalArg<T> = ArgBase<T> & {
required: false;
value?: T;
}

export type Arg<T> = RequiredArg<T> | OptionalArg<T>

export function newArg<T>(arg: IArg<T> & { Parse: ParseFn<T> }): Arg<T>
export function newArg(arg: IArg): Arg<string>
export function newArg(arg: IArg<any>): any {
export function newArg<T>(arg: Arg & { Parse: ParseFn<T> }): ParserArg<T>
export function newArg(arg: Arg): ParserArg<string>
export function newArg(arg: Arg<any>): any {
return {
parse: (i: string) => i,
...arg,
required: Boolean(arg.required),
}
}

export interface Output {[name: string]: any}
export type Input = IArg<any>[]

0 comments on commit d5ad46d

Please sign in to comment.