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 { TypeValidation } from '../TypeValidation/TypeValidation'; | ||||||
| import { BaseOption } from './BaseOption'; | import { BaseOption } from './BaseOption'; | ||||||
|  |  | ||||||
| export class ArrayOption extends BaseOption { | export class ArrayOption extends BaseOption<(string|number|boolean)[]> { | ||||||
|   protected get validation ():TypeValidation { |   protected get validation ():TypeValidation { | ||||||
|     return new TypeValidation ('array'); |     return new TypeValidation ('array'); | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ import { ErrorCallback } from '../ErrorCallback'; | |||||||
| import { ArgSource } from '../Sources/ArgSource'; | import { ArgSource } from '../Sources/ArgSource'; | ||||||
| import { ConfigSource } from '../Sources/ConfigSource'; | import { ConfigSource } from '../Sources/ConfigSource'; | ||||||
| import { TypeValidation } from '../TypeValidation/TypeValidation'; | import { TypeValidation } from '../TypeValidation/TypeValidation'; | ||||||
|  | import { InteractiveSource } from '../Sources/InteractiveSource'; | ||||||
|  |  | ||||||
| export abstract class BaseOption<T> { | export abstract class BaseOption<T> { | ||||||
|   protected readonly sources: OptionSource[] = []; |   protected readonly sources: OptionSource[] = []; | ||||||
| @@ -44,8 +45,8 @@ export abstract class BaseOption<T> { | |||||||
|       // eslint-disable-next-line no-await-in-loop |       // eslint-disable-next-line no-await-in-loop | ||||||
|       await source.parse (this._config, val); |       await source.parse (this._config, val); | ||||||
|  |  | ||||||
|     if (!val.assinged) |     if (!val.filled) | ||||||
|       return this._config.default; |       return this._config.default as T; | ||||||
|     return val.value; |     return val.value as T; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| import { TypeValidation } from '../TypeValidation/TypeValidation'; | import { TypeValidation } from '../TypeValidation/TypeValidation'; | ||||||
| import { BaseOption } from './BaseOption'; | import { BaseOption } from './BaseOption'; | ||||||
|  |  | ||||||
| export class BooleanOption extends BaseOption { | export class BooleanOption extends BaseOption<boolean> { | ||||||
|   protected get validation ():TypeValidation { |   protected get validation ():TypeValidation { | ||||||
|     return new TypeValidation ('boolean'); |     return new TypeValidation ('boolean'); | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| import { PathType } from '../TypeValidation/PathType'; | import { PathType } from '../TypeValidation/PathType'; | ||||||
|  | import { TypeValidation } from '../TypeValidation/TypeValidation'; | ||||||
| import { StringOption } from './StringOption'; | import { StringOption } from './StringOption'; | ||||||
|  |  | ||||||
| export class FileOption extends StringOption { | export class FileOption extends StringOption { | ||||||
|   | |||||||
| @@ -1,8 +1,9 @@ | |||||||
| import { PathType } from '../TypeValidation/PathType'; | import { PathType } from '../TypeValidation/PathType'; | ||||||
|  | import { TypeValidation } from '../TypeValidation/TypeValidation'; | ||||||
| import { StringOption } from './StringOption'; | import { StringOption } from './StringOption'; | ||||||
|  |  | ||||||
| export class FolderOption extends StringOption { | export class FolderOption extends StringOption { | ||||||
|   protected get validation ():TypeValidation { |   protected get validation (): TypeValidation { | ||||||
|     return new PathType ('folder'); |     return new PathType ('folder'); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| import { TypeValidation } from '../TypeValidation/TypeValidation'; | import { TypeValidation } from '../TypeValidation/TypeValidation'; | ||||||
| import { BaseOption } from './BaseOption'; | import { BaseOption } from './BaseOption'; | ||||||
|  |  | ||||||
| export class NumberOption extends BaseOption { | export class NumberOption extends BaseOption<number> { | ||||||
|   protected get validation ():TypeValidation { |   protected get validation ():TypeValidation { | ||||||
|     return new TypeValidation ('number'); |     return new TypeValidation ('number'); | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -1,8 +1,9 @@ | |||||||
| import { PathType } from '../TypeValidation/PathType'; | import { PathType } from '../TypeValidation/PathType'; | ||||||
|  | import { TypeValidation } from '../TypeValidation/TypeValidation'; | ||||||
| import { StringOption } from './StringOption'; | import { StringOption } from './StringOption'; | ||||||
|  |  | ||||||
| export class PathOption extends StringOption { | export class PathOption extends StringOption { | ||||||
|   protected get validation ():TypeValidation { |   protected get validation (): TypeValidation { | ||||||
|     return new PathType ('path'); |     return new PathType ('path'); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,12 +1,8 @@ | |||||||
| import { TypeValidation } from '../TypeValidation/TypeValidation'; | import { TypeValidation } from '../TypeValidation/TypeValidation'; | ||||||
| import { Option } from '../Option'; | import { StringOptionConfig } from '../SubConfigs'; | ||||||
| import { BaseOption } from './BaseOption'; | import { BaseOption } from './BaseOption'; | ||||||
|  |  | ||||||
| interface StringOptionConfig extends Option { | export class StringOption extends BaseOption<string> { | ||||||
|   preset?: string[] |  | ||||||
| } |  | ||||||
|  |  | ||||||
| export class StringOption extends BaseOption { |  | ||||||
|   protected get validation ():TypeValidation { |   protected get validation ():TypeValidation { | ||||||
|     return new TypeValidation ('string'); |     return new TypeValidation ('string'); | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -8,68 +8,43 @@ | |||||||
| /* eslint-disable no-console */ | /* eslint-disable no-console */ | ||||||
| /* eslint-disable no-process-exit */ | /* eslint-disable no-process-exit */ | ||||||
| import yargs, { Options } from 'yargs'; | import yargs, { Options } from 'yargs'; | ||||||
| import { OptionProcess } from '../Option'; | import { Option, OptionValue } from '../Option'; | ||||||
| import { OptionSource } from './OptionSource'; | import { OptionSource } from './OptionSource'; | ||||||
|  |  | ||||||
| export class ArgSource extends OptionSource { | export class ArgSource extends OptionSource { | ||||||
|   private create_config (options: OptionProcess[]): Record<string, Options> { |   private create_config ( | ||||||
|     const yargs_config: Record<string, Options> = { |     config: Option, | ||||||
|       quiet: { |     persistent_type: string | ||||||
|         alias:    'q', |   ): Record<string, Options> { | ||||||
|         default:  false, |     return { | ||||||
|         type:     'boolean', |       [config.name]: { | ||||||
|         describe: 'do not ask for options interactively' |         alias:    config.alias, | ||||||
|       }, |  | ||||||
|       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, |  | ||||||
|         // eslint-disable-next-line no-undefined |         // eslint-disable-next-line no-undefined | ||||||
|         default:  type === 'boolean' ? undefined : opt.default, |         default:  undefined, | ||||||
|         type, |         type:    persistent_type | ||||||
|         describe: opt.description |       } | ||||||
|       }; |     } as Record<string, Options>; | ||||||
|     } |  | ||||||
|     return yargs_config; |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   public async parse (options: OptionProcess[]): Promise<void> { |   public async parse (opt: Option, val: OptionValue): Promise<void> { | ||||||
|     const yargs_config = this.create_config (options); |     const yargs_config = this.create_config ( | ||||||
|  |       opt, | ||||||
|  |       val.type_validation.persistent_type | ||||||
|  |     ); | ||||||
|  |  | ||||||
|     const argv = yargs.options (yargs_config) |     const argv = yargs.options (yargs_config) | ||||||
|       .parse (); |       .parse (); | ||||||
|     if (argv.help) { |  | ||||||
|       yargs.options (yargs_config) |  | ||||||
|         .showHelp (); |  | ||||||
|       process.exit (0); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     await Promise.all (options.map ((opt) => { |     if (typeof argv[opt.name] === 'undefined') | ||||||
|       if (typeof argv[opt.name] === 'undefined') |       return; | ||||||
|         return Promise.resolve (); |  | ||||||
|       if ( |     if ( | ||||||
|         opt.type === 'array' |       val.type_validation.option_type === 'array' | ||||||
|         && (argv[opt.name] as Array<unknown>) |         && (argv[opt.name] as Array<unknown>) | ||||||
|           .filter ((v) => typeof v !== 'undefined').length <= 0 |           .filter ((v) => typeof v !== 'undefined').length <= 0 | ||||||
|       ) |     ) | ||||||
|         return Promise.resolve (); |       return; | ||||||
|       return this.assign_arg (opt, argv[opt.name]); |  | ||||||
|     })); |  | ||||||
|  |  | ||||||
|     if (argv.quiet) { |     await this.assign_arg (opt, val, argv[opt.name]); | ||||||
|       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); |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -10,7 +10,7 @@ import { dirname, join } from 'path'; | |||||||
| import fs from 'fs-extra'; | import fs from 'fs-extra'; | ||||||
| import { run_regex } from '@sapphirecode/utilities'; | import { run_regex } from '@sapphirecode/utilities'; | ||||||
| import hjson from 'hjson'; | import hjson from 'hjson'; | ||||||
| import { OptionProcess } from '../Option'; | import { OptionValue, Option } from '../Option'; | ||||||
| import { ErrorCallback } from '../ErrorCallback'; | import { ErrorCallback } from '../ErrorCallback'; | ||||||
| import { OptionSource } from './OptionSource'; | import { OptionSource } from './OptionSource'; | ||||||
|  |  | ||||||
| @@ -46,7 +46,7 @@ export class ConfigSource extends OptionSource { | |||||||
|     return obj; |     return obj; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   public async parse (options: OptionProcess[]): Promise<void> { |   public async parse (opt: Option, val: OptionValue): Promise<void> { | ||||||
|     const data: Record<string, unknown> = {}; |     const data: Record<string, unknown> = {}; | ||||||
|     for (const f of this._config_files) { |     for (const f of this._config_files) { | ||||||
|       try { |       try { | ||||||
| @@ -63,9 +63,7 @@ export class ConfigSource extends OptionSource { | |||||||
|  |  | ||||||
|     const keys = Object.keys (data); |     const keys = Object.keys (data); | ||||||
|  |  | ||||||
|     for (const opt of options) { |     if (keys.includes (opt.name)) | ||||||
|       if (keys.includes (opt.name)) |       await this.assign_arg (opt, val, data[opt.name]); | ||||||
|         await this.assign_arg (opt, data[opt.name]); |  | ||||||
|     } |  | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -6,18 +6,15 @@ | |||||||
|  */ |  */ | ||||||
|  |  | ||||||
| /* eslint-disable no-process-env */ | /* eslint-disable no-process-env */ | ||||||
| import { OptionProcess } from '../Option'; | import { Option, OptionValue } from '../Option'; | ||||||
| import { OptionSource } from './OptionSource'; | import { OptionSource } from './OptionSource'; | ||||||
|  |  | ||||||
| export class EnvSource extends OptionSource { | export class EnvSource extends OptionSource { | ||||||
|   public async parse (options: OptionProcess[]): Promise<void> { |   public async parse (opt: Option, val:OptionValue): Promise<void> { | ||||||
|     await Promise.all (options.map ((opt) => { |     if ( | ||||||
|       if ( |       typeof opt.env !== 'undefined' | ||||||
|         typeof opt.env !== 'undefined' |  | ||||||
|         && typeof process.env[opt.env] !== 'undefined' |         && typeof process.env[opt.env] !== 'undefined' | ||||||
|       ) |     ) | ||||||
|         return this.assign_arg (opt, process.env[opt.env]); |       await this.assign_arg (opt, val, process.env[opt.env]); | ||||||
|       return Promise.resolve (); |  | ||||||
|     })); |  | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -8,8 +8,9 @@ | |||||||
| /* eslint-disable no-console */ | /* eslint-disable no-console */ | ||||||
| /* eslint-disable no-process-exit */ | /* eslint-disable no-process-exit */ | ||||||
| import { Confirm, Input, List, AutoComplete } from 'enquirer'; | import { Confirm, Input, List, AutoComplete } from 'enquirer'; | ||||||
| import { OptionProcess, Option } from '../Option'; |  | ||||||
| import { ErrorCallback } from '../ErrorCallback'; | import { ErrorCallback } from '../ErrorCallback'; | ||||||
|  | import { Option, OptionValue } from '../Option'; | ||||||
|  | import { StringOptionConfig } from '../SubConfigs'; | ||||||
| import { OptionSource } from './OptionSource'; | import { OptionSource } from './OptionSource'; | ||||||
|  |  | ||||||
| export class InteractiveSource extends OptionSource { | export class InteractiveSource extends OptionSource { | ||||||
| @@ -29,18 +30,20 @@ export class InteractiveSource extends OptionSource { | |||||||
|       : opt.message; |       : opt.message; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   private async prompt (opt: OptionProcess): Promise<void> { |   private async prompt (opt: Option, val:OptionValue): Promise<void> { | ||||||
|     if (opt.filled) |     if (val.filled) | ||||||
|       return; |       return; | ||||||
|     let value = null; |     let value = null; | ||||||
|  |     const { option_type } = val.type_validation; | ||||||
|  |     const { preset } = opt as StringOptionConfig; | ||||||
|     if ( |     if ( | ||||||
|       opt.type === 'string' |       option_type === 'string' | ||||||
|       || opt.type === 'file' |       || option_type === 'file' | ||||||
|       || opt.type === 'folder' |       || option_type === 'folder' | ||||||
|       || opt.type === 'path' |       || option_type === 'path' | ||||||
|       || opt.type === 'number' |       || option_type === 'number' | ||||||
|     ) { |     ) { | ||||||
|       if (typeof opt.preset === 'undefined') { |       if (typeof preset === 'undefined') { | ||||||
|         value = await new Input ({ |         value = await new Input ({ | ||||||
|           message: this.get_message (opt), |           message: this.get_message (opt), | ||||||
|           default: opt.default |           default: opt.default | ||||||
| @@ -51,14 +54,14 @@ export class InteractiveSource extends OptionSource { | |||||||
|         value = await new AutoComplete ({ |         value = await new AutoComplete ({ | ||||||
|           message: this.get_message (opt), |           message: this.get_message (opt), | ||||||
|           default: opt.default, |           default: opt.default, | ||||||
|           choices: opt.preset, |           choices: preset, | ||||||
|           limit:   10 |           limit:   10 | ||||||
|         }) |         }) | ||||||
|           .run (); |           .run (); | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|     if ( |     if ( | ||||||
|       opt.type === 'boolean' |       option_type === 'boolean' | ||||||
|     ) { |     ) { | ||||||
|       value = await new Confirm ({ |       value = await new Confirm ({ | ||||||
|         message: this.get_message (opt), |         message: this.get_message (opt), | ||||||
| @@ -66,7 +69,7 @@ export class InteractiveSource extends OptionSource { | |||||||
|       }) |       }) | ||||||
|         .run (); |         .run (); | ||||||
|     } |     } | ||||||
|     if (opt.type === 'array') { |     if (option_type === 'array') { | ||||||
|       value = await new List ({ |       value = await new List ({ | ||||||
|         message: this.get_message (opt), |         message: this.get_message (opt), | ||||||
|         default: opt.default |         default: opt.default | ||||||
| @@ -76,22 +79,20 @@ export class InteractiveSource extends OptionSource { | |||||||
|     if (value === null) |     if (value === null) | ||||||
|       return; |       return; | ||||||
|  |  | ||||||
|     await this.assign_arg (opt, value); |     await this.assign_arg (opt, val, value); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   public async parse (options: OptionProcess[]): Promise<void> { |   public async parse (opt: Option, val:OptionValue): Promise<void> { | ||||||
|     for (const opt of options) { |     while (!val.filled) { | ||||||
|       while (!opt.filled) { |       // eslint-disable-next-line no-await-in-loop | ||||||
|         // eslint-disable-next-line no-await-in-loop |       await this.prompt (opt, val) | ||||||
|         await this.prompt (opt) |         .catch ((e) => { | ||||||
|           .catch ((e) => { |           if (this._exit_on_interrupt) | ||||||
|             if (this._exit_on_interrupt) |             process.exit (0); | ||||||
|               process.exit (0); |           throw e; | ||||||
|             throw e; |         }); | ||||||
|           }); |       if (!val.filled) | ||||||
|         if (!opt.filled) |         console.log (opt.error || 'input was invalid'); | ||||||
|           console.log (opt.error || 'input was invalid'); |  | ||||||
|       } |  | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -18,12 +18,13 @@ export abstract class OptionSource { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   protected async assign_arg ( |   protected async assign_arg ( | ||||||
|     opt: OptionValue, |     opt: Option, | ||||||
|  |     val: OptionValue, | ||||||
|     value: unknown |     value: unknown | ||||||
|   ): Promise<void> { |   ): Promise<void> { | ||||||
|     try { |     try { | ||||||
|       opt.value = await opt.type_validation.to_type (value); |       val.value = await val.type_validation.to_type (value); | ||||||
|       opt.filled = true; |       val.filled = true; | ||||||
|     } |     } | ||||||
|     catch (e) { |     catch (e) { | ||||||
|       if (typeof this.error_callback !== 'undefined') |       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 |  * 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