diff --git a/lib/OptionType.ts b/lib/OptionType.ts new file mode 100644 index 0000000..5eb678e --- /dev/null +++ b/lib/OptionType.ts @@ -0,0 +1,15 @@ +/* + * Copyright (C) Sapphirecode - All Rights Reserved + * This file is part of console-app which is released under MIT. + * See file 'LICENSE' for full license details. + * Created by Timo Hocker , May 2020 + */ + +export type OptionType = + 'string' + | 'number' + | 'boolean' + | 'file' + | 'folder' + | 'path' + | 'array'; diff --git a/lib/Options/ArrayOption.ts b/lib/Options/ArrayOption.ts index 0da4c9f..f1cd4b2 100644 --- a/lib/Options/ArrayOption.ts +++ b/lib/Options/ArrayOption.ts @@ -1,7 +1,7 @@ import { TypeValidation } from '../TypeValidation/TypeValidation'; import { BaseOption } from './BaseOption'; -export class ArrayOption extends BaseOption { +export class ArrayOption extends BaseOption<(string|number|boolean)[]> { protected get validation ():TypeValidation { return new TypeValidation ('array'); } diff --git a/lib/Options/BaseOption.ts b/lib/Options/BaseOption.ts index a8771dc..906b2cf 100644 --- a/lib/Options/BaseOption.ts +++ b/lib/Options/BaseOption.ts @@ -5,6 +5,7 @@ import { ErrorCallback } from '../ErrorCallback'; import { ArgSource } from '../Sources/ArgSource'; import { ConfigSource } from '../Sources/ConfigSource'; import { TypeValidation } from '../TypeValidation/TypeValidation'; +import { InteractiveSource } from '../Sources/InteractiveSource'; export abstract class BaseOption { protected readonly sources: OptionSource[] = []; @@ -44,8 +45,8 @@ export abstract class BaseOption { // eslint-disable-next-line no-await-in-loop await source.parse (this._config, val); - if (!val.assinged) - return this._config.default; - return val.value; + if (!val.filled) + return this._config.default as T; + return val.value as T; } } diff --git a/lib/Options/BooleanOption.ts b/lib/Options/BooleanOption.ts index c0834de..e8e3278 100644 --- a/lib/Options/BooleanOption.ts +++ b/lib/Options/BooleanOption.ts @@ -1,7 +1,7 @@ import { TypeValidation } from '../TypeValidation/TypeValidation'; import { BaseOption } from './BaseOption'; -export class BooleanOption extends BaseOption { +export class BooleanOption extends BaseOption { protected get validation ():TypeValidation { return new TypeValidation ('boolean'); } diff --git a/lib/Options/FileOption.ts b/lib/Options/FileOption.ts index 6bae6e4..765bdb9 100644 --- a/lib/Options/FileOption.ts +++ b/lib/Options/FileOption.ts @@ -1,4 +1,5 @@ import { PathType } from '../TypeValidation/PathType'; +import { TypeValidation } from '../TypeValidation/TypeValidation'; import { StringOption } from './StringOption'; export class FileOption extends StringOption { diff --git a/lib/Options/FolderOption.ts b/lib/Options/FolderOption.ts index 8281e67..ec41ecb 100644 --- a/lib/Options/FolderOption.ts +++ b/lib/Options/FolderOption.ts @@ -1,8 +1,9 @@ import { PathType } from '../TypeValidation/PathType'; +import { TypeValidation } from '../TypeValidation/TypeValidation'; import { StringOption } from './StringOption'; export class FolderOption extends StringOption { - protected get validation ():TypeValidation { + protected get validation (): TypeValidation { return new PathType ('folder'); } } diff --git a/lib/Options/NumberOption.ts b/lib/Options/NumberOption.ts index f7b44e1..c26a0c1 100644 --- a/lib/Options/NumberOption.ts +++ b/lib/Options/NumberOption.ts @@ -1,7 +1,7 @@ import { TypeValidation } from '../TypeValidation/TypeValidation'; import { BaseOption } from './BaseOption'; -export class NumberOption extends BaseOption { +export class NumberOption extends BaseOption { protected get validation ():TypeValidation { return new TypeValidation ('number'); } diff --git a/lib/Options/PathOption.ts b/lib/Options/PathOption.ts index 48d44cc..fd660fa 100644 --- a/lib/Options/PathOption.ts +++ b/lib/Options/PathOption.ts @@ -1,8 +1,9 @@ import { PathType } from '../TypeValidation/PathType'; +import { TypeValidation } from '../TypeValidation/TypeValidation'; import { StringOption } from './StringOption'; export class PathOption extends StringOption { - protected get validation ():TypeValidation { + protected get validation (): TypeValidation { return new PathType ('path'); } } diff --git a/lib/Options/StringOption.ts b/lib/Options/StringOption.ts index 780f492..e8133e9 100644 --- a/lib/Options/StringOption.ts +++ b/lib/Options/StringOption.ts @@ -1,12 +1,8 @@ import { TypeValidation } from '../TypeValidation/TypeValidation'; -import { Option } from '../Option'; +import { StringOptionConfig } from '../SubConfigs'; import { BaseOption } from './BaseOption'; -interface StringOptionConfig extends Option { - preset?: string[] -} - -export class StringOption extends BaseOption { +export class StringOption extends BaseOption { protected get validation ():TypeValidation { return new TypeValidation ('string'); } diff --git a/lib/Sources/ArgSource.ts b/lib/Sources/ArgSource.ts index 74efc64..46265e9 100644 --- a/lib/Sources/ArgSource.ts +++ b/lib/Sources/ArgSource.ts @@ -8,68 +8,43 @@ /* eslint-disable no-console */ /* eslint-disable no-process-exit */ import yargs, { Options } from 'yargs'; -import { OptionProcess } from '../Option'; +import { Option, OptionValue } from '../Option'; import { OptionSource } from './OptionSource'; export class ArgSource extends OptionSource { - private create_config (options: OptionProcess[]): Record { - const yargs_config: Record = { - quiet: { - alias: 'q', - default: false, - type: 'boolean', - describe: 'do not ask for options interactively' - }, - help: { - alias: 'h', - default: false, - type: 'boolean', - describe: '' - } - }; - for (const opt of options) { - const type = opt.type_validation.persistent_type; - yargs_config[opt.name] = { - alias: opt.alias, + private create_config ( + config: Option, + persistent_type: string + ): Record { + return { + [config.name]: { + alias: config.alias, // eslint-disable-next-line no-undefined - default: type === 'boolean' ? undefined : opt.default, - type, - describe: opt.description - }; - } - return yargs_config; + default: undefined, + type: persistent_type + } + } as Record; } - public async parse (options: OptionProcess[]): Promise { - const yargs_config = this.create_config (options); + public async parse (opt: Option, val: OptionValue): Promise { + const yargs_config = this.create_config ( + opt, + val.type_validation.persistent_type + ); + const argv = yargs.options (yargs_config) .parse (); - if (argv.help) { - yargs.options (yargs_config) - .showHelp (); - process.exit (0); - } - await Promise.all (options.map ((opt) => { - if (typeof argv[opt.name] === 'undefined') - return Promise.resolve (); - if ( - opt.type === 'array' + if (typeof argv[opt.name] === 'undefined') + return; + + if ( + val.type_validation.option_type === 'array' && (argv[opt.name] as Array) .filter ((v) => typeof v !== 'undefined').length <= 0 - ) - return Promise.resolve (); - return this.assign_arg (opt, argv[opt.name]); - })); + ) + return; - if (argv.quiet) { - const missing = options.filter ((o) => !o.filled && o.required) - .map ((o) => o.name); - if (missing.length > 0) { - console.error ('missing arguments:'); - console.error (missing.join (', ')); - process.exit (0); - } - } + await this.assign_arg (opt, val, argv[opt.name]); } } diff --git a/lib/Sources/ConfigSource.ts b/lib/Sources/ConfigSource.ts index 74e2309..62e40fd 100644 --- a/lib/Sources/ConfigSource.ts +++ b/lib/Sources/ConfigSource.ts @@ -10,7 +10,7 @@ import { dirname, join } from 'path'; import fs from 'fs-extra'; import { run_regex } from '@sapphirecode/utilities'; import hjson from 'hjson'; -import { OptionProcess } from '../Option'; +import { OptionValue, Option } from '../Option'; import { ErrorCallback } from '../ErrorCallback'; import { OptionSource } from './OptionSource'; @@ -46,7 +46,7 @@ export class ConfigSource extends OptionSource { return obj; } - public async parse (options: OptionProcess[]): Promise { + public async parse (opt: Option, val: OptionValue): Promise { const data: Record = {}; for (const f of this._config_files) { try { @@ -63,9 +63,7 @@ export class ConfigSource extends OptionSource { const keys = Object.keys (data); - for (const opt of options) { - if (keys.includes (opt.name)) - await this.assign_arg (opt, data[opt.name]); - } + if (keys.includes (opt.name)) + await this.assign_arg (opt, val, data[opt.name]); } } diff --git a/lib/Sources/EnvSource.ts b/lib/Sources/EnvSource.ts index 2f978b6..d943d7b 100644 --- a/lib/Sources/EnvSource.ts +++ b/lib/Sources/EnvSource.ts @@ -6,18 +6,15 @@ */ /* eslint-disable no-process-env */ -import { OptionProcess } from '../Option'; +import { Option, OptionValue } from '../Option'; import { OptionSource } from './OptionSource'; export class EnvSource extends OptionSource { - public async parse (options: OptionProcess[]): Promise { - await Promise.all (options.map ((opt) => { - if ( - typeof opt.env !== 'undefined' + public async parse (opt: Option, val:OptionValue): Promise { + if ( + typeof opt.env !== 'undefined' && typeof process.env[opt.env] !== 'undefined' - ) - return this.assign_arg (opt, process.env[opt.env]); - return Promise.resolve (); - })); + ) + await this.assign_arg (opt, val, process.env[opt.env]); } } diff --git a/lib/Sources/InteractiveSource.ts b/lib/Sources/InteractiveSource.ts index b26e4f0..fe090d3 100644 --- a/lib/Sources/InteractiveSource.ts +++ b/lib/Sources/InteractiveSource.ts @@ -8,8 +8,9 @@ /* eslint-disable no-console */ /* eslint-disable no-process-exit */ import { Confirm, Input, List, AutoComplete } from 'enquirer'; -import { OptionProcess, Option } from '../Option'; import { ErrorCallback } from '../ErrorCallback'; +import { Option, OptionValue } from '../Option'; +import { StringOptionConfig } from '../SubConfigs'; import { OptionSource } from './OptionSource'; export class InteractiveSource extends OptionSource { @@ -29,18 +30,20 @@ export class InteractiveSource extends OptionSource { : opt.message; } - private async prompt (opt: OptionProcess): Promise { - if (opt.filled) + private async prompt (opt: Option, val:OptionValue): Promise { + if (val.filled) return; let value = null; + const { option_type } = val.type_validation; + const { preset } = opt as StringOptionConfig; if ( - opt.type === 'string' - || opt.type === 'file' - || opt.type === 'folder' - || opt.type === 'path' - || opt.type === 'number' + option_type === 'string' + || option_type === 'file' + || option_type === 'folder' + || option_type === 'path' + || option_type === 'number' ) { - if (typeof opt.preset === 'undefined') { + if (typeof preset === 'undefined') { value = await new Input ({ message: this.get_message (opt), default: opt.default @@ -51,14 +54,14 @@ export class InteractiveSource extends OptionSource { value = await new AutoComplete ({ message: this.get_message (opt), default: opt.default, - choices: opt.preset, + choices: preset, limit: 10 }) .run (); } } if ( - opt.type === 'boolean' + option_type === 'boolean' ) { value = await new Confirm ({ message: this.get_message (opt), @@ -66,7 +69,7 @@ export class InteractiveSource extends OptionSource { }) .run (); } - if (opt.type === 'array') { + if (option_type === 'array') { value = await new List ({ message: this.get_message (opt), default: opt.default @@ -76,22 +79,20 @@ export class InteractiveSource extends OptionSource { if (value === null) return; - await this.assign_arg (opt, value); + await this.assign_arg (opt, val, value); } - public async parse (options: OptionProcess[]): Promise { - for (const opt of options) { - while (!opt.filled) { - // eslint-disable-next-line no-await-in-loop - await this.prompt (opt) - .catch ((e) => { - if (this._exit_on_interrupt) - process.exit (0); - throw e; - }); - if (!opt.filled) - console.log (opt.error || 'input was invalid'); - } + public async parse (opt: Option, val:OptionValue): Promise { + while (!val.filled) { + // eslint-disable-next-line no-await-in-loop + await this.prompt (opt, val) + .catch ((e) => { + if (this._exit_on_interrupt) + process.exit (0); + throw e; + }); + if (!val.filled) + console.log (opt.error || 'input was invalid'); } } } diff --git a/lib/Sources/OptionSource.ts b/lib/Sources/OptionSource.ts index 6cd19f9..9a4f742 100644 --- a/lib/Sources/OptionSource.ts +++ b/lib/Sources/OptionSource.ts @@ -18,12 +18,13 @@ export abstract class OptionSource { } protected async assign_arg ( - opt: OptionValue, + opt: Option, + val: OptionValue, value: unknown ): Promise { try { - opt.value = await opt.type_validation.to_type (value); - opt.filled = true; + val.value = await val.type_validation.to_type (value); + val.filled = true; } catch (e) { if (typeof this.error_callback !== 'undefined') diff --git a/lib/SubConfigs.ts b/lib/SubConfigs.ts new file mode 100644 index 0000000..1d9b952 --- /dev/null +++ b/lib/SubConfigs.ts @@ -0,0 +1,7 @@ +import { Option } from './Option'; + +interface StringOptionConfig extends Option { + preset?: string[]; +} + +export { StringOptionConfig }; diff --git a/lib/TypeRegister.ts b/lib/TypeRegister.ts deleted file mode 100644 index 67f16d0..0000000 --- a/lib/TypeRegister.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { TypeValidation } from './TypeValidation/TypeValidation'; -import { PathType } from './TypeValidation/PathType'; - -export const types: Record = { - string: new TypeValidation ('string'), - number: new TypeValidation ('number'), - boolean: new TypeValidation ('boolean'), - file: new PathType ('file'), - folder: new PathType ('folder'), - path: new PathType ('path'), - array: new TypeValidation ('array') -}; diff --git a/lib/index.ts b/lib/index.ts index dabfd57..6f22a89 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -5,4 +5,10 @@ * Created by Timo Hocker , May 2020 */ -export { InteractiveOptions } from './InteractiveOptions'; +export { ArrayOption } from './Options/ArrayOption'; +export { BooleanOption } from './Options/BooleanOption'; +export { FileOption } from './Options/FileOption'; +export { FolderOption } from './Options/FolderOption'; +export { NumberOption } from './Options/NumberOption'; +export { PathOption } from './Options/PathOption'; +export { StringOption } from './Options/StringOption';