refactoring
This commit is contained in:
		
							
								
								
									
										15
									
								
								AppTest.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								AppTest.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | |||||||
|  | /* eslint-disable no-console */ | ||||||
|  | 'use strict'; | ||||||
|  |  | ||||||
|  | // eslint-disable-next-line id-match | ||||||
|  | const { InteractiveOptions } = require ('./dist/lib/index.js'); | ||||||
|  |  | ||||||
|  | (async () => { | ||||||
|  |   const reader = new InteractiveOptions ([ | ||||||
|  |     { name: 'str', type: 'string' }, | ||||||
|  |     { name: 'bool', type: 'boolean' }, | ||||||
|  |     { name: 'num', type: 'number' } | ||||||
|  |   ]); | ||||||
|  |   await reader.parse (); | ||||||
|  |   console.log (reader.serialize (true)); | ||||||
|  | }) (); | ||||||
| @@ -12,242 +12,73 @@ | |||||||
| /* eslint-disable max-statements */ | /* eslint-disable max-statements */ | ||||||
| /* eslint-disable no-process-env */ | /* eslint-disable no-process-env */ | ||||||
| import { Persistent } from '@sapphirecode/modelling'; | import { Persistent } from '@sapphirecode/modelling'; | ||||||
| import fs from 'fs-extra'; | import { TypeValidation } from './Types/TypeValidation'; | ||||||
| import yargs, { Options } from 'yargs'; | import { PathType } from './Types/PathType'; | ||||||
| import { Confirm, Input } from 'enquirer'; | import { Option, OptionProcess, OptionType } from './Types'; | ||||||
|  | import { OptionSource } from './Sources/OptionSource'; | ||||||
|  | import { EnvSource } from './Sources/EnvSource'; | ||||||
|  | import { ArgSource } from './Sources/ArgSource'; | ||||||
|  | import { InteractiveSource } from './Sources/InteractiveSource'; | ||||||
|  |  | ||||||
| type OptionType = | const types: Record<OptionType, TypeValidation> = { | ||||||
|   'string' |   string:  new TypeValidation ('string'), | ||||||
|   | 'number' |   number:  new TypeValidation ('number'), | ||||||
|   | 'boolean' |   boolean: new TypeValidation ('boolean'), | ||||||
|   | 'file' |   file:    new PathType ('file'), | ||||||
|   | 'folder' |   folder:  new PathType ('folder'), | ||||||
|   | 'path'; |   path:    new PathType ('path') | ||||||
|  | }; | ||||||
|  |  | ||||||
| interface Option { | interface SourceConfig { | ||||||
|   name: string; |   env: boolean; | ||||||
|   type: OptionType; |   args: boolean; | ||||||
|   required?: boolean; |   interactive: boolean; | ||||||
|   default?: unknown; |  | ||||||
|   alias?: string; |  | ||||||
|   env?: string; |  | ||||||
|   description?: string; |  | ||||||
|   message?: string; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| interface OptionProcess extends Option { |  | ||||||
|   filled: boolean; |  | ||||||
|   value?: unknown; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| function get_string_type (type: OptionType): 'string'|'number'|'boolean' { |  | ||||||
|   if ([ |  | ||||||
|     'string', |  | ||||||
|     'number', |  | ||||||
|     'boolean' |  | ||||||
|   ].includes (type)) |  | ||||||
|     return type as ('string'|'number'|'boolean'); |  | ||||||
|   if ([ |  | ||||||
|     'file', |  | ||||||
|     'folder', |  | ||||||
|     'path' |  | ||||||
|   ].includes (type)) |  | ||||||
|     return 'string'; |  | ||||||
|   throw new Error (`unknown option type ${type}`); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| export class InteractiveOptions extends Persistent { | export class InteractiveOptions extends Persistent { | ||||||
|   protected options: Array<OptionProcess>; |   protected options: Array<OptionProcess>; | ||||||
|   protected quiet = false; |   protected quiet = false; | ||||||
|  |   protected sources: OptionSource[] = []; | ||||||
|  |  | ||||||
|   public constructor (options: Array<Option>) { |   public constructor ( | ||||||
|  |     options: Array<Option>, | ||||||
|  |     source_config: SourceConfig = { args: true, env: true, interactive: true } | ||||||
|  |   ) { | ||||||
|     super (); |     super (); | ||||||
|     this.options = options |     this.options = options | ||||||
|       .map ((v) => ({ filled: false, ...v } as OptionProcess)); |       .map ((v) => ({ | ||||||
|  |         filled:          false, | ||||||
|  |         type_validation: types[v.type], | ||||||
|  |         ...v | ||||||
|  |       } as OptionProcess)); | ||||||
|     for (const option of this.options) { |     for (const option of this.options) { | ||||||
|       if ( |       if ( | ||||||
|         typeof option.default !== 'undefined' |         typeof option.default !== 'undefined' | ||||||
|         && typeof option.default !== get_string_type (option.type) |         && typeof option.default !== option.type_validation.string_type | ||||||
|       ) { |       ) { | ||||||
|         throw new Error ( |         throw new Error ( | ||||||
|           `default does not match option type on ${option.name}` |           `default does not match option type on ${option.name}` | ||||||
|         ); |         ); | ||||||
|       } |       } | ||||||
|  |  | ||||||
|       this.properties[option.name] = get_string_type (option.type); |       this.properties[option.name] = option.type_validation.string_type; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     if (source_config.env) | ||||||
|  |       this.sources.push (new EnvSource); | ||||||
|  |     if (source_config.args) | ||||||
|  |       this.sources.push (new ArgSource); | ||||||
|  |     if (source_config.interactive) | ||||||
|  |       this.sources.push (new InteractiveSource); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   public async parse (): Promise<Record<string, unknown>> { |   public async parse (): Promise<Record<string, unknown>> { | ||||||
|     await this.get_env_options (); |     for (const src of this.sources) | ||||||
|     await this.get_args_options (); |       // eslint-disable-next-line no-await-in-loop | ||||||
|     await this.get_interactive_options (); |       await src.parse (this.options); | ||||||
|     for (const opt of this.options) |     for (const opt of this.options) | ||||||
|       this.set (opt.name, opt.value); |       this.set (opt.name, opt.value); | ||||||
|  |  | ||||||
|     return this.to_object (); |     return this.to_object (); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   private async assign_arg (opt: OptionProcess, value: unknown): Promise<void> { |  | ||||||
|     if (opt.type === 'string') { |  | ||||||
|       opt.value = String (value); |  | ||||||
|       opt.filled = true; |  | ||||||
|       return; |  | ||||||
|     } |  | ||||||
|     if (opt.type === 'number') { |  | ||||||
|       if (![ |  | ||||||
|         'string', |  | ||||||
|         'number' |  | ||||||
|       ].includes (typeof value)) |  | ||||||
|         return; |  | ||||||
|  |  | ||||||
|       const as_num = parseInt (String (value)); |  | ||||||
|       const is_num = !isNaN (as_num); |  | ||||||
|       if (is_num) { |  | ||||||
|         opt.value = as_num; |  | ||||||
|         opt.filled = true; |  | ||||||
|       } |  | ||||||
|       return; |  | ||||||
|     } |  | ||||||
|     if (opt.type === 'boolean') { |  | ||||||
|       if (![ |  | ||||||
|         'string', |  | ||||||
|         'boolean', |  | ||||||
|         'number' |  | ||||||
|       ].includes (typeof value)) |  | ||||||
|         return; |  | ||||||
|  |  | ||||||
|       const is_bool = [ |  | ||||||
|         0, |  | ||||||
|         1 |  | ||||||
|       ].includes (parseInt (String (value))) |  | ||||||
|       || (/^(?:true|false)$/ui).test (value as string); |  | ||||||
|       if (is_bool) { |  | ||||||
|         const as_bool = value === 1 || (/true/ui).test (value as string); |  | ||||||
|         opt.value = as_bool; |  | ||||||
|         opt.filled = true; |  | ||||||
|       } |  | ||||||
|       return; |  | ||||||
|     } |  | ||||||
|     if ( |  | ||||||
|       opt.type === 'path' |  | ||||||
|       || opt.type === 'file' |  | ||||||
|       || opt.type === 'folder' |  | ||||||
|     ) { |  | ||||||
|       if (typeof value !== 'string' || !await fs.pathExists (value)) |  | ||||||
|         return; |  | ||||||
|       if (opt.type === 'path') { |  | ||||||
|         opt.value = value; |  | ||||||
|         opt.filled = true; |  | ||||||
|         return; |  | ||||||
|       } |  | ||||||
|       const stat = await fs.stat (value); |  | ||||||
|       if (stat.isDirectory () === (opt.type === 'folder')) { |  | ||||||
|         opt.value = value; |  | ||||||
|         opt.filled = true; |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   private async get_env_options (): Promise<void> { |  | ||||||
|     await Promise.all (this.options.map ((opt) => { |  | ||||||
|       if ( |  | ||||||
|         typeof opt.env !== 'undefined' |  | ||||||
|         && typeof process.env[opt.env] !== 'undefined' |  | ||||||
|       ) |  | ||||||
|         return this.assign_arg (opt, process.env[opt.env]); |  | ||||||
|       return Promise.resolve (); |  | ||||||
|     })); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   private async get_args_options (): Promise<void> { |  | ||||||
|     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 this.options) { |  | ||||||
|       yargs_config[opt.name] = { |  | ||||||
|         alias:    opt.alias, |  | ||||||
|         default:  opt.default, |  | ||||||
|         type:     get_string_type (opt.type), |  | ||||||
|         describe: opt.description |  | ||||||
|       }; |  | ||||||
|     } |  | ||||||
|     const argv = yargs.options (yargs_config) |  | ||||||
|       .parse (); |  | ||||||
|     if (argv.help) { |  | ||||||
|       yargs.options (yargs_config) |  | ||||||
|         .showHelp (); |  | ||||||
|       process.exit (0); |  | ||||||
|     } |  | ||||||
|     this.quiet = argv.quiet as boolean; |  | ||||||
|  |  | ||||||
|     await Promise.all (this.options.map ((opt) => { |  | ||||||
|       if (typeof argv[opt.name] !== 'undefined') |  | ||||||
|         return this.assign_arg (opt, argv[opt.name]); |  | ||||||
|       return Promise.resolve (); |  | ||||||
|     })); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   private async prompt (opt: OptionProcess): Promise<void> { |  | ||||||
|     if (opt.filled) |  | ||||||
|       return; |  | ||||||
|     if ( |  | ||||||
|       opt.type === 'string' |  | ||||||
|       || opt.type === 'file' |  | ||||||
|       || opt.type === 'folder' |  | ||||||
|       || opt.type === 'path' |  | ||||||
|       || opt.type === 'number' |  | ||||||
|     ) { |  | ||||||
|       const value = await new Input ({ |  | ||||||
|         message: typeof opt.message === 'undefined' |  | ||||||
|           ? `input ${opt.name}` |  | ||||||
|           : opt.message, |  | ||||||
|         default: opt.default |  | ||||||
|       }) |  | ||||||
|         .run (); |  | ||||||
|       await this.assign_arg (opt, value); |  | ||||||
|       return; |  | ||||||
|     } |  | ||||||
|     if ( |  | ||||||
|       opt.type === 'boolean' |  | ||||||
|     ) { |  | ||||||
|       const value = await new Confirm ({ |  | ||||||
|         message: opt.message, |  | ||||||
|         default: opt.default |  | ||||||
|       }) |  | ||||||
|         .run (); |  | ||||||
|       await this.assign_arg (opt, value); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   private async get_interactive_options (): Promise<void> { |  | ||||||
|     if (this.quiet) { |  | ||||||
|       const missing = this.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); |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|     for (const opt of this.options) { |  | ||||||
|       while (!opt.filled) { |  | ||||||
|         // eslint-disable-next-line no-await-in-loop |  | ||||||
|         await this.prompt (opt); |  | ||||||
|         if (!opt.filled) |  | ||||||
|           console.log ('input was invalid'); |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										55
									
								
								lib/Sources/ArgSource.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								lib/Sources/ArgSource.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | |||||||
|  | /* eslint-disable no-console */ | ||||||
|  | /* eslint-disable no-process-exit */ | ||||||
|  | import yargs, { Options } from 'yargs'; | ||||||
|  | import { OptionProcess } from '../Types'; | ||||||
|  | import { OptionSource } from './OptionSource'; | ||||||
|  |  | ||||||
|  | export class ArgSource extends OptionSource { | ||||||
|  |   public async parse (options: OptionProcess[]): Promise<void> { | ||||||
|  |     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) { | ||||||
|  |       yargs_config[opt.name] = { | ||||||
|  |         alias:    opt.alias, | ||||||
|  |         default:  opt.default, | ||||||
|  |         type:     opt.type_validation.string_type, | ||||||
|  |         describe: opt.description | ||||||
|  |       }; | ||||||
|  |     } | ||||||
|  |     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 this.assign_arg (opt, argv[opt.name]); | ||||||
|  |       return Promise.resolve (); | ||||||
|  |     })); | ||||||
|  |  | ||||||
|  |     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); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										16
									
								
								lib/Sources/EnvSource.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								lib/Sources/EnvSource.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | |||||||
|  | /* eslint-disable no-process-env */ | ||||||
|  | import { OptionProcess } from '../Types'; | ||||||
|  | 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' | ||||||
|  |         && typeof process.env[opt.env] !== 'undefined' | ||||||
|  |       ) | ||||||
|  |         return this.assign_arg (opt, process.env[opt.env]); | ||||||
|  |       return Promise.resolve (); | ||||||
|  |     })); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										50
									
								
								lib/Sources/InteractiveSource.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								lib/Sources/InteractiveSource.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,50 @@ | |||||||
|  | /* eslint-disable no-console */ | ||||||
|  | /* eslint-disable no-process-exit */ | ||||||
|  | import { Confirm, Input } from 'enquirer'; | ||||||
|  | import { OptionProcess } from '../Types'; | ||||||
|  | import { OptionSource } from './OptionSource'; | ||||||
|  |  | ||||||
|  | export class InteractiveSource extends OptionSource { | ||||||
|  |   private async prompt (opt: OptionProcess): Promise<void> { | ||||||
|  |     if (opt.filled) | ||||||
|  |       return; | ||||||
|  |     if ( | ||||||
|  |       opt.type === 'string' | ||||||
|  |       || opt.type === 'file' | ||||||
|  |       || opt.type === 'folder' | ||||||
|  |       || opt.type === 'path' | ||||||
|  |       || opt.type === 'number' | ||||||
|  |     ) { | ||||||
|  |       const value = await new Input ({ | ||||||
|  |         message: typeof opt.message === 'undefined' | ||||||
|  |           ? `input ${opt.name}` | ||||||
|  |           : opt.message, | ||||||
|  |         default: opt.default | ||||||
|  |       }) | ||||||
|  |         .run (); | ||||||
|  |       await this.assign_arg (opt, value); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     if ( | ||||||
|  |       opt.type === 'boolean' | ||||||
|  |     ) { | ||||||
|  |       const value = await new Confirm ({ | ||||||
|  |         message: opt.message, | ||||||
|  |         default: opt.default | ||||||
|  |       }) | ||||||
|  |         .run (); | ||||||
|  |       await this.assign_arg (opt, 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); | ||||||
|  |         if (!opt.filled) | ||||||
|  |           console.log ('input was invalid'); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										18
									
								
								lib/Sources/OptionSource.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								lib/Sources/OptionSource.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | |||||||
|  | import { OptionProcess } from '../Types'; | ||||||
|  |  | ||||||
|  | export abstract class OptionSource { | ||||||
|  |   public abstract async parse(opt: OptionProcess[]): Promise<void>; | ||||||
|  |  | ||||||
|  |   protected async assign_arg ( | ||||||
|  |     opt: OptionProcess, | ||||||
|  |     value: unknown | ||||||
|  |   ): Promise<void> { | ||||||
|  |     try { | ||||||
|  |       opt.value = await opt.type_validation.to_type (value); | ||||||
|  |       opt.filled = true; | ||||||
|  |     } | ||||||
|  |     catch (e) { | ||||||
|  |       // could not assing | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										28
									
								
								lib/Types.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								lib/Types.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | |||||||
|  | import { TypeValidation } from './Types/TypeValidation'; | ||||||
|  |  | ||||||
|  | type OptionType = | ||||||
|  |   'string' | ||||||
|  |   | 'number' | ||||||
|  |   | 'boolean' | ||||||
|  |   | 'file' | ||||||
|  |   | 'folder' | ||||||
|  |   | 'path'; | ||||||
|  |  | ||||||
|  | interface Option { | ||||||
|  |   name: string; | ||||||
|  |   type: OptionType; | ||||||
|  |   required?: boolean; | ||||||
|  |   default?: unknown; | ||||||
|  |   alias?: string; | ||||||
|  |   env?: string; | ||||||
|  |   description?: string; | ||||||
|  |   message?: string; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | interface OptionProcess extends Option { | ||||||
|  |   filled: boolean; | ||||||
|  |   value?: unknown; | ||||||
|  |   type_validation: TypeValidation; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export { OptionType, Option, OptionProcess }; | ||||||
							
								
								
									
										23
									
								
								lib/Types/PathType.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								lib/Types/PathType.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | |||||||
|  | import fs from 'fs-extra'; | ||||||
|  | import { TypeValidation } from './TypeValidation'; | ||||||
|  |  | ||||||
|  | export class PathType extends TypeValidation { | ||||||
|  |   public get string_type (): 'string'|'number'|'boolean'|'array' { | ||||||
|  |     return 'string'; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public async to_type (value: unknown): Promise<unknown> { | ||||||
|  |     if (typeof value !== 'string') | ||||||
|  |       throw new Error (`invalid type for ${this.general_type}`); | ||||||
|  |     if (!await fs.pathExists (value)) | ||||||
|  |       throw new Error ('path does not exist'); | ||||||
|  |     if (this.general_type === 'path') | ||||||
|  |       return value; | ||||||
|  |  | ||||||
|  |     const stat = await fs.stat (value); | ||||||
|  |     if (stat.isDirectory () === (this.general_type === 'folder')) | ||||||
|  |       return value; | ||||||
|  |  | ||||||
|  |     throw new Error ('cannot assign folder to file'); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										44
									
								
								lib/Types/TypeValidation.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								lib/Types/TypeValidation.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | |||||||
|  | export class TypeValidation { | ||||||
|  |   private readonly _general_type: string; | ||||||
|  |  | ||||||
|  |   public get general_type (): string { | ||||||
|  |     return this._general_type; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public get string_type (): 'string'|'number'|'boolean'|'array' { | ||||||
|  |     return this._general_type as 'string'|'number'|'boolean'|'array'; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public constructor (type: string) { | ||||||
|  |     this._general_type = type; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public validate_type (value: unknown): boolean { | ||||||
|  |     return typeof value === this.general_type; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public to_type (value: unknown): Promise<unknown> { | ||||||
|  |     if (this.general_type === 'string') | ||||||
|  |       return Promise.resolve (String (value)); | ||||||
|  |  | ||||||
|  |     if (this.general_type === 'number') { | ||||||
|  |       const as_num = parseInt (String (value)); | ||||||
|  |       if (isNaN (as_num)) | ||||||
|  |         throw new Error ('value is not a number'); | ||||||
|  |       return Promise.resolve (as_num); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (this.general_type === 'boolean') { | ||||||
|  |       const as_num = parseInt (String (value)); | ||||||
|  |       if ( | ||||||
|  |         as_num !== 1 && as_num !== 0 | ||||||
|  |         && !(/^(?:true|false)$/iu).test (String (value)) | ||||||
|  |       ) | ||||||
|  |         throw new Error ('value is not a boolean'); | ||||||
|  |       return Promise.resolve ( | ||||||
|  |         as_num === 1 || (/true/iu).test (String (value)) | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |     throw new Error ('unknown type'); | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -1,7 +1,7 @@ | |||||||
| { | { | ||||||
|   "name": "@sapphirecode/console-app", |   "name": "@sapphirecode/console-app", | ||||||
|   "version": "1.0.0", |   "version": "1.0.0", | ||||||
|   "main": "dist/index.js", |   "main": "dist/lib/index.js", | ||||||
|   "author": "Timo Hocker <timo@sapphirecode.ovh>", |   "author": "Timo Hocker <timo@sapphirecode.ovh>", | ||||||
|   "license": "MIT", |   "license": "MIT", | ||||||
|   "devDependencies": { |   "devDependencies": { | ||||||
| @@ -9,20 +9,20 @@ | |||||||
|     "@sapphirecode/eslint-config-ts": "^1.0.31", |     "@sapphirecode/eslint-config-ts": "^1.0.31", | ||||||
|     "@types/fs-extra": "^8.1.0", |     "@types/fs-extra": "^8.1.0", | ||||||
|     "@types/yargs": "^15.0.4", |     "@types/yargs": "^15.0.4", | ||||||
|     "ava": "^3.8.1", |     "ava": "^3.8.2", | ||||||
|     "eslint": "^6.8.0", |     "eslint": "^6.8.0", | ||||||
|     "nyc": "^15.0.1", |     "nyc": "^15.0.1", | ||||||
|     "typescript": "^3.8.3" |     "typescript": "^3.8.3" | ||||||
|   }, |   }, | ||||||
|   "scripts": { |   "scripts": { | ||||||
|     "lint": "eslint . --ext .js,.jsx,.ts,.tsx,.vue,.mjs", |     "lint": "eslint . --ext .js,.jsx,.ts,.tsx,.vue,.mjs", | ||||||
|     "test": "echo \"no test\"", |     "test": "tsc && nyc ava", | ||||||
|     "compile": "tsc", |     "compile": "tsc", | ||||||
|     "ci": "yarn && node jenkins.js" |     "ci": "yarn && node jenkins.js" | ||||||
|   }, |   }, | ||||||
|   "files": [ |   "files": [ | ||||||
|     "LICENSE", |     "LICENSE", | ||||||
|     "/dist/" |     "/dist/lib/" | ||||||
|   ], |   ], | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|     "@sapphirecode/modelling": "^1.0.26", |     "@sapphirecode/modelling": "^1.0.26", | ||||||
|   | |||||||
							
								
								
									
										24
									
								
								test/.eslintrc.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								test/.eslintrc.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | |||||||
|  | /* | ||||||
|  |  * 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@sapphirecode.ovh>, May 2020 | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | module.exports = { | ||||||
|  |   env: { | ||||||
|  |     commonjs: true, | ||||||
|  |     es6: true, | ||||||
|  |     node: true | ||||||
|  |   }, | ||||||
|  |   extends: [ | ||||||
|  |     '@sapphirecode/eslint-config-ts' | ||||||
|  |   ], | ||||||
|  |   globals: { | ||||||
|  |     Atomics: 'readonly', | ||||||
|  |     SharedArrayBuffer: 'readonly' | ||||||
|  |   }, | ||||||
|  |   parserOptions: { | ||||||
|  |     ecmaVersion: 2018 | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										39
									
								
								test/PathType.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								test/PathType.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | |||||||
|  | import test from 'ava'; | ||||||
|  | import { PathType } from '../lib/Types/PathType'; | ||||||
|  |  | ||||||
|  | test ('no file', async (t) => { | ||||||
|  |   const validator = new PathType ('file'); | ||||||
|  |   await t.throwsAsync ( | ||||||
|  |     () => validator.to_type ('test'), | ||||||
|  |     { message: 'cannot assign folder to file' } | ||||||
|  |   ); | ||||||
|  | }); | ||||||
|  | test ('file', async (t) => { | ||||||
|  |   const validator = new PathType ('file'); | ||||||
|  |   const res = await validator.to_type ('package.json'); | ||||||
|  |   t.is (res, 'package.json'); | ||||||
|  | }); | ||||||
|  | test ('no folder', async (t) => { | ||||||
|  |   const validator = new PathType ('folder'); | ||||||
|  |   await t.throwsAsync ( | ||||||
|  |     () => validator.to_type ('package.json'), | ||||||
|  |     { message: 'cannot assign folder to file' } | ||||||
|  |   ); | ||||||
|  | }); | ||||||
|  | test ('folder', async (t) => { | ||||||
|  |   const validator = new PathType ('folder'); | ||||||
|  |   const res = await validator.to_type ('test'); | ||||||
|  |   t.is (res, 'test'); | ||||||
|  | }); | ||||||
|  | test ('no path', async (t) => { | ||||||
|  |   const validator = new PathType ('path'); | ||||||
|  |   await t.throwsAsync ( | ||||||
|  |     () => validator.to_type ('doesnotexist.file'), | ||||||
|  |     { message: 'path does not exist' } | ||||||
|  |   ); | ||||||
|  | }); | ||||||
|  | test ('path', async (t) => { | ||||||
|  |   const validator = new PathType ('path'); | ||||||
|  |   const res = await validator.to_type ('test'); | ||||||
|  |   t.is (res, 'test'); | ||||||
|  | }); | ||||||
							
								
								
									
										41
									
								
								test/TypeValidation.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								test/TypeValidation.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | |||||||
|  | import test from 'ava'; | ||||||
|  | import { TypeValidation } from '../lib/Types/TypeValidation'; | ||||||
|  |  | ||||||
|  | test ('string', async (t) => { | ||||||
|  |   const validator = new TypeValidation ('string'); | ||||||
|  |   const res = await validator.to_type ('foo'); | ||||||
|  |   t.is (res, 'foo'); | ||||||
|  | }); | ||||||
|  | test ('no number', (t) => { | ||||||
|  |   const validator = new TypeValidation ('number'); | ||||||
|  |   t.throws ( | ||||||
|  |     () => validator.to_type ('foo'), | ||||||
|  |     { message: 'value is not a number' } | ||||||
|  |   ); | ||||||
|  | }); | ||||||
|  | test ('number', async (t) => { | ||||||
|  |   const validator = new TypeValidation ('number'); | ||||||
|  |   const res = await validator.to_type ('123'); | ||||||
|  |   t.is (res, 123); | ||||||
|  | }); | ||||||
|  | test ('no boolean', (t) => { | ||||||
|  |   const validator = new TypeValidation ('boolean'); | ||||||
|  |   t.throws ( | ||||||
|  |     () => validator.to_type ('foo'), | ||||||
|  |     { message: 'value is not a boolean' } | ||||||
|  |   ); | ||||||
|  | }); | ||||||
|  | test ('boolean', async (t) => { | ||||||
|  |   const validator = new TypeValidation ('boolean'); | ||||||
|  |   const r1 = await validator.to_type ('false'); | ||||||
|  |   const r2 = await validator.to_type ('true'); | ||||||
|  |   t.is (r1, false); | ||||||
|  |   t.is (r2, true); | ||||||
|  | }); | ||||||
|  | test ('boolean number', async (t) => { | ||||||
|  |   const validator = new TypeValidation ('boolean'); | ||||||
|  |   const r1 = await validator.to_type (0); | ||||||
|  |   const r2 = await validator.to_type (1); | ||||||
|  |   t.is (r1, false); | ||||||
|  |   t.is (r2, true); | ||||||
|  | }); | ||||||
| @@ -3,7 +3,7 @@ | |||||||
|     "target": "es5", |     "target": "es5", | ||||||
|     "module": "commonjs", |     "module": "commonjs", | ||||||
|     "outDir": "./dist",  |     "outDir": "./dist",  | ||||||
|     "rootDir": "./lib", |     "rootDir": "./", | ||||||
|     "strict": true,  |     "strict": true,  | ||||||
|     "esModuleInterop": true, |     "esModuleInterop": true, | ||||||
|     "forceConsistentCasingInFileNames": true, |     "forceConsistentCasingInFileNames": true, | ||||||
|   | |||||||
							
								
								
									
										57
									
								
								yarn.lock
									
									
									
									
									
								
							
							
						
						
									
										57
									
								
								yarn.lock
									
									
									
									
									
								
							| @@ -269,9 +269,16 @@ | |||||||
|     eslint-plugin-sort-requires-by-path "^1.0.2" |     eslint-plugin-sort-requires-by-path "^1.0.2" | ||||||
|  |  | ||||||
| "@sapphirecode/modelling@^1.0.26": | "@sapphirecode/modelling@^1.0.26": | ||||||
|   version "1.0.31" |   version "1.0.32" | ||||||
|   resolved "https://registry.yarnpkg.com/@sapphirecode/modelling/-/modelling-1.0.31.tgz#7d5e0d823dc7eedc9fd1e11bd1d4be562227c2b4" |   resolved "https://registry.yarnpkg.com/@sapphirecode/modelling/-/modelling-1.0.32.tgz#d7871a464e619aa64892b27f44230327f714c2f1" | ||||||
|   integrity sha512-YqG46seI6ewsyW2n16pUowue/BDV4aRgLvR3TBu4UMPaWUEl8G5Ezdx223QK2sWbilQbDxk70HdmQkgawzcc/g== |   integrity sha512-iZrPI5/oguIGKvLz8rbAaz8InEYntGbuFQmb27ozp2RhjLCZMMSXRU4XZEOs4QyUe6TpmNWRXF44K/P9F4AqYg== | ||||||
|  |   dependencies: | ||||||
|  |     "@sapphirecode/utilities" "^1.0.39" | ||||||
|  |  | ||||||
|  | "@sapphirecode/utilities@^1.0.39": | ||||||
|  |   version "1.0.39" | ||||||
|  |   resolved "https://registry.yarnpkg.com/@sapphirecode/utilities/-/utilities-1.0.39.tgz#12fdc564065102ea156188541941f92fbb126238" | ||||||
|  |   integrity sha512-8IgL9Cw43ipUOIqyqXvgmvyR/s+4Mf1h75jIBwOniNM3x7EU2jJiE01ePDrdsNraaGNn6o4GnFMx6ymp07U2SQ== | ||||||
|  |  | ||||||
| "@sindresorhus/is@^0.14.0": | "@sindresorhus/is@^0.14.0": | ||||||
|   version "0.14.0" |   version "0.14.0" | ||||||
| @@ -402,9 +409,9 @@ acorn-walk@^7.1.1: | |||||||
|   integrity sha512-wdlPY2tm/9XBr7QkKlq0WQVgiuGTX6YWPyRyBviSoScBuLfTVQhvwg6wJ369GJ/1nPfTLMfnrFIfjqVg6d+jQQ== |   integrity sha512-wdlPY2tm/9XBr7QkKlq0WQVgiuGTX6YWPyRyBviSoScBuLfTVQhvwg6wJ369GJ/1nPfTLMfnrFIfjqVg6d+jQQ== | ||||||
|  |  | ||||||
| acorn@^7.1.1: | acorn@^7.1.1: | ||||||
|   version "7.1.1" |   version "7.2.0" | ||||||
|   resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.1.1.tgz#e35668de0b402f359de515c5482a1ab9f89a69bf" |   resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.2.0.tgz#17ea7e40d7c8640ff54a694c889c26f31704effe" | ||||||
|   integrity sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg== |   integrity sha512-apwXVmYVpQ34m/i71vrApRrRKCWQnZZF1+npOD0WV5xZFfwWOmKGQ2RWlfdy9vWITsenisM8M0Qeq8agcFHNiQ== | ||||||
|  |  | ||||||
| aggregate-error@^3.0.0: | aggregate-error@^3.0.0: | ||||||
|   version "3.0.1" |   version "3.0.1" | ||||||
| @@ -567,10 +574,10 @@ at-least-node@^1.0.0: | |||||||
|   resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" |   resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" | ||||||
|   integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== |   integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== | ||||||
|  |  | ||||||
| ava@^3.8.1: | ava@^3.8.2: | ||||||
|   version "3.8.1" |   version "3.8.2" | ||||||
|   resolved "https://registry.yarnpkg.com/ava/-/ava-3.8.1.tgz#ec50814f8e6c769b8ed0dcc64bca990cd06bb2d1" |   resolved "https://registry.yarnpkg.com/ava/-/ava-3.8.2.tgz#877c9eb861763a185bbabd54f359e1fbe57d1754" | ||||||
|   integrity sha512-OPWrTxcf1EbtAaGGFQPLbx4AaVqPrFMumKOKn2SzIRo+RTKb33lF2aoVnWqBeZaJ68uSc9R6jqIE7qkG6O33uQ== |   integrity sha512-sph3oUsVTGsq4qbgeWys03QKCmXjkZUO3oPnFWXEW6g1SReCY9vuONGghMgw1G6VOzkg1k+niqJsOzwfO8h9Ng== | ||||||
|   dependencies: |   dependencies: | ||||||
|     "@concordance/react" "^2.0.0" |     "@concordance/react" "^2.0.0" | ||||||
|     acorn "^7.1.1" |     acorn "^7.1.1" | ||||||
| @@ -603,7 +610,7 @@ ava@^3.8.1: | |||||||
|     indent-string "^4.0.0" |     indent-string "^4.0.0" | ||||||
|     is-error "^2.2.2" |     is-error "^2.2.2" | ||||||
|     is-plain-object "^3.0.0" |     is-plain-object "^3.0.0" | ||||||
|     is-promise "^3.0.0" |     is-promise "^4.0.0" | ||||||
|     lodash "^4.17.15" |     lodash "^4.17.15" | ||||||
|     matcher "^3.0.0" |     matcher "^3.0.0" | ||||||
|     md5-hex "^3.0.1" |     md5-hex "^3.0.1" | ||||||
| @@ -614,12 +621,12 @@ ava@^3.8.1: | |||||||
|     picomatch "^2.2.2" |     picomatch "^2.2.2" | ||||||
|     pkg-conf "^3.1.0" |     pkg-conf "^3.1.0" | ||||||
|     plur "^4.0.0" |     plur "^4.0.0" | ||||||
|     pretty-ms "^6.0.1" |     pretty-ms "^7.0.0" | ||||||
|     read-pkg "^5.2.0" |     read-pkg "^5.2.0" | ||||||
|     resolve-cwd "^3.0.0" |     resolve-cwd "^3.0.0" | ||||||
|     slash "^3.0.0" |     slash "^3.0.0" | ||||||
|     source-map-support "^0.5.19" |     source-map-support "^0.5.19" | ||||||
|     stack-utils "^2.0.1" |     stack-utils "^2.0.2" | ||||||
|     strip-ansi "^6.0.0" |     strip-ansi "^6.0.0" | ||||||
|     supertap "^1.0.0" |     supertap "^1.0.0" | ||||||
|     temp-dir "^2.0.0" |     temp-dir "^2.0.0" | ||||||
| @@ -639,9 +646,9 @@ binary-extensions@^2.0.0: | |||||||
|   integrity sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow== |   integrity sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow== | ||||||
|  |  | ||||||
| blueimp-md5@^2.10.0: | blueimp-md5@^2.10.0: | ||||||
|   version "2.14.0" |   version "2.15.0" | ||||||
|   resolved "https://registry.yarnpkg.com/blueimp-md5/-/blueimp-md5-2.14.0.tgz#ac45786ede87d2a985478040d2ac38a9af7a16ac" |   resolved "https://registry.yarnpkg.com/blueimp-md5/-/blueimp-md5-2.15.0.tgz#ae945f87ca6c2c11e2562983e11450b0545f9bb3" | ||||||
|   integrity sha512-fhX8JsIgugJ39g9MUJ4Y0S+WYd/1HATNVzW4nEVknP5uJU1mA7LZCV3OuVH9OvxpuYQXu6ttst0IYIlAyVfBQg== |   integrity sha512-Zc6sowqlCWu3+V0bocZwdaPPXlRv14EHtYcQDCOghj9EdyKLMkAOODBh3HHAx5r7QRylDYCOaXa/b/edgBLDpA== | ||||||
|  |  | ||||||
| boxen@^4.2.0: | boxen@^4.2.0: | ||||||
|   version "4.2.0" |   version "4.2.0" | ||||||
| @@ -1867,10 +1874,10 @@ is-plain-object@^3.0.0: | |||||||
|   dependencies: |   dependencies: | ||||||
|     isobject "^4.0.0" |     isobject "^4.0.0" | ||||||
|  |  | ||||||
| is-promise@^3.0.0: | is-promise@^4.0.0: | ||||||
|   version "3.0.0" |   version "4.0.0" | ||||||
|   resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-3.0.0.tgz#1f88031af842d9203dc1777cba40411e848f9beb" |   resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-4.0.0.tgz#42ff9f84206c1991d26debf520dd5c01042dd2f3" | ||||||
|   integrity sha512-aTHJ4BvETyySzLhguH+7sL4b8765eecqq7ZrHVuhZr3FjCL/IV+LsvisEeH+9d0AkChYny3ad1KEL+mKy4ot7A== |   integrity sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ== | ||||||
|  |  | ||||||
| is-regex@^1.0.5: | is-regex@^1.0.5: | ||||||
|   version "1.0.5" |   version "1.0.5" | ||||||
| @@ -2643,10 +2650,10 @@ prepend-http@^2.0.0: | |||||||
|   resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" |   resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" | ||||||
|   integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= |   integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= | ||||||
|  |  | ||||||
| pretty-ms@^6.0.1: | pretty-ms@^7.0.0: | ||||||
|   version "6.0.1" |   version "7.0.0" | ||||||
|   resolved "https://registry.yarnpkg.com/pretty-ms/-/pretty-ms-6.0.1.tgz#03ec6cfee20329f142645e63efad96bb775d3da4" |   resolved "https://registry.yarnpkg.com/pretty-ms/-/pretty-ms-7.0.0.tgz#45781273110caf35f55cab21a8a9bd403a233dc0" | ||||||
|   integrity sha512-ke4njoVmlotekHlHyCZ3wI/c5AMT8peuHs8rKJqekj/oR5G8lND2dVpicFlUz5cbZgE290vvkMuDwfj/OcW1kw== |   integrity sha512-J3aPWiC5e9ZeZFuSeBraGxSkGMOvulSWsxDByOcbD1Pr75YL3LSNIKIb52WXbCLE1sS5s4inBBbryjF4Y05Ceg== | ||||||
|   dependencies: |   dependencies: | ||||||
|     parse-ms "^2.1.0" |     parse-ms "^2.1.0" | ||||||
|  |  | ||||||
| @@ -2999,7 +3006,7 @@ sprintf-js@~1.0.2: | |||||||
|   resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" |   resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" | ||||||
|   integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= |   integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= | ||||||
|  |  | ||||||
| stack-utils@^2.0.1: | stack-utils@^2.0.2: | ||||||
|   version "2.0.2" |   version "2.0.2" | ||||||
|   resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.2.tgz#5cf48b4557becb4638d0bc4f21d23f5d19586593" |   resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.2.tgz#5cf48b4557becb4638d0bc4f21d23f5d19586593" | ||||||
|   integrity sha512-0H7QK2ECz3fyZMzQ8rH0j2ykpfbnd20BFtfg/SqVC2+sCTtcw0aDTGB7dk+de4U4uUeuz6nOtJcrkFFLG1B0Rg== |   integrity sha512-0H7QK2ECz3fyZMzQ8rH0j2ykpfbnd20BFtfg/SqVC2+sCTtcw0aDTGB7dk+de4U4uUeuz6nOtJcrkFFLG1B0Rg== | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user