complete remodelling
This commit is contained in:
		
							
								
								
									
										15
									
								
								lib/OptionType.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								lib/OptionType.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -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 <timo@scode.ovh>, May 2020 | ||||
|  */ | ||||
|  | ||||
| export type OptionType = | ||||
|   'string' | ||||
|   | 'number' | ||||
|   | 'boolean' | ||||
|   | 'file' | ||||
|   | 'folder' | ||||
|   | 'path' | ||||
|   | 'array'; | ||||
| @@ -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'); | ||||
|   } | ||||
|   | ||||
| @@ -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<T> { | ||||
|   protected readonly sources: OptionSource[] = []; | ||||
| @@ -44,8 +45,8 @@ export abstract class BaseOption<T> { | ||||
|       // 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; | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| import { TypeValidation } from '../TypeValidation/TypeValidation'; | ||||
| import { BaseOption } from './BaseOption'; | ||||
|  | ||||
| export class BooleanOption extends BaseOption { | ||||
| export class BooleanOption extends BaseOption<boolean> { | ||||
|   protected get validation ():TypeValidation { | ||||
|     return new TypeValidation ('boolean'); | ||||
|   } | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| import { PathType } from '../TypeValidation/PathType'; | ||||
| import { TypeValidation } from '../TypeValidation/TypeValidation'; | ||||
| import { StringOption } from './StringOption'; | ||||
|  | ||||
| export class FileOption extends StringOption { | ||||
|   | ||||
| @@ -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'); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| import { TypeValidation } from '../TypeValidation/TypeValidation'; | ||||
| import { BaseOption } from './BaseOption'; | ||||
|  | ||||
| export class NumberOption extends BaseOption { | ||||
| export class NumberOption extends BaseOption<number> { | ||||
|   protected get validation ():TypeValidation { | ||||
|     return new TypeValidation ('number'); | ||||
|   } | ||||
|   | ||||
| @@ -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'); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -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<string> { | ||||
|   protected get validation ():TypeValidation { | ||||
|     return new TypeValidation ('string'); | ||||
|   } | ||||
|   | ||||
| @@ -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<string, Options> { | ||||
|     const yargs_config: Record<string, Options> = { | ||||
|       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<string, Options> { | ||||
|     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<string, Options>; | ||||
|   } | ||||
|  | ||||
|   public async parse (options: OptionProcess[]): Promise<void> { | ||||
|     const yargs_config = this.create_config (options); | ||||
|   public async parse (opt: Option, val: OptionValue): Promise<void> { | ||||
|     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<unknown>) | ||||
|           .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]); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -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<void> { | ||||
|   public async parse (opt: Option, val: OptionValue): Promise<void> { | ||||
|     const data: Record<string, unknown> = {}; | ||||
|     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]); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -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<void> { | ||||
|     await Promise.all (options.map ((opt) => { | ||||
|       if ( | ||||
|         typeof opt.env !== 'undefined' | ||||
|   public async parse (opt: Option, val:OptionValue): Promise<void> { | ||||
|     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]); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -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<void> { | ||||
|     if (opt.filled) | ||||
|   private async prompt (opt: Option, val:OptionValue): Promise<void> { | ||||
|     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<void> { | ||||
|     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<void> { | ||||
|     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'); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -18,12 +18,13 @@ export abstract class OptionSource { | ||||
|   } | ||||
|  | ||||
|   protected async assign_arg ( | ||||
|     opt: OptionValue, | ||||
|     opt: Option, | ||||
|     val: OptionValue, | ||||
|     value: unknown | ||||
|   ): Promise<void> { | ||||
|     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') | ||||
|   | ||||
							
								
								
									
										7
									
								
								lib/SubConfigs.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								lib/SubConfigs.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| import { Option } from './Option'; | ||||
|  | ||||
| interface StringOptionConfig extends Option { | ||||
|   preset?: string[]; | ||||
| } | ||||
|  | ||||
| export { StringOptionConfig }; | ||||
| @@ -1,12 +0,0 @@ | ||||
| import { TypeValidation } from './TypeValidation/TypeValidation'; | ||||
| import { PathType } from './TypeValidation/PathType'; | ||||
|  | ||||
| export const types: Record<OptionType, TypeValidation> = { | ||||
|   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') | ||||
| }; | ||||
| @@ -5,4 +5,10 @@ | ||||
|  * Created by Timo Hocker <timo@scode.ovh>, 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'; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user