complete remodelling

This commit is contained in:
Timo Hocker 2020-06-09 21:03:10 +02:00
parent 742d77d29f
commit 021f55833d
17 changed files with 110 additions and 122 deletions

15
lib/OptionType.ts Normal file
View 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';

View File

@ -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');
} }

View File

@ -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;
} }
} }

View File

@ -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');
} }

View File

@ -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 {

View File

@ -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');
} }
} }

View File

@ -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');
} }

View File

@ -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');
} }
} }

View File

@ -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');
} }

View File

@ -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
};
} }
return yargs_config; } as Record<string, Options>;
} }
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 Promise.resolve (); return;
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);
}
}
} }
} }

View File

@ -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, data[opt.name]); await this.assign_arg (opt, val, data[opt.name]);
}
} }
} }

View File

@ -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 ();
}));
} }
} }

View File

@ -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) await this.prompt (opt, val)
.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 (!opt.filled) if (!val.filled)
console.log (opt.error || 'input was invalid'); console.log (opt.error || 'input was invalid');
} }
} }
}
} }

View File

@ -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
View File

@ -0,0 +1,7 @@
import { Option } from './Option';
interface StringOptionConfig extends Option {
preset?: string[];
}
export { StringOptionConfig };

View File

@ -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')
};

View File

@ -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';