Compare commits

..

16 Commits

Author SHA1 Message Date
4fdfe1314e switch to drone
Some checks failed
continuous-integration/drone/push Build is failing
2020-07-10 08:31:51 +02:00
4397e5f2c4 Merge branch 'master' into dev 2020-06-22 08:29:13 +02:00
551b5c89a7 update-scanner: automatic update
@sapphirecode/eslint-config-ts: 1.1.13 ==> 1.1.14 minor
@sapphirecode/modelling: 1.1.3 ==> 1.1.4 minor
@sapphirecode/utilities: 1.4.3 ==> 1.4.4 minor
eslint: 7.2.0 ==> 7.3.0 minor
2020-06-22 08:22:29 +02:00
5dcd54e451 update-scanner: automatic update
@sapphirecode/eslint-config-ts: 1.1.12 ==> 1.1.13 minor
@sapphirecode/modelling: 1.1.2 ==> 1.1.3 minor
@sapphirecode/utilities: 1.4.2 ==> 1.4.3 minor
ava: 3.8.2 ==> 3.9.0 minor
2020-06-19 13:07:42 +02:00
62ae2990a6 prompt will have to be self made, or extending more basic levels of enquirer 2020-06-18 13:58:38 +02:00
f372e1ea17 path selector 2020-06-16 12:52:41 +02:00
d477124973 more dynamic interactive source register 2020-06-16 09:40:12 +02:00
f7c03f82f1 split interactive source 2020-06-15 11:56:33 +02:00
089519844f complete documentation 2020-06-12 14:34:53 +02:00
bc960f632e update-scanner: automatic update
@sapphirecode/eslint-config-ts: 1.1.11 ==> 1.1.12 minor
@sapphirecode/modelling: 1.1.1 ==> 1.1.2 minor
@sapphirecode/utilities: 1.4.1 ==> 1.4.2 minor
2020-06-11 20:29:33 +02:00
88a35265d0 small improvement, changelog, start documentation for 2.0 2020-06-10 22:44:54 +02:00
3fa23c1697 init version 2 2020-06-09 21:33:48 +02:00
7574c250af test 2020-06-09 21:23:26 +02:00
021f55833d complete remodelling 2020-06-09 21:07:55 +02:00
742d77d29f complete type contructs 2020-06-09 13:58:29 +02:00
3f0b9fad79 starting to split options by type 2020-06-09 13:13:27 +02:00
40 changed files with 769 additions and 779 deletions

20
.drone.yml Normal file
View File

@ -0,0 +1,20 @@
kind: pipeline
name: default
steps:
- name: setup
image: node:lts-alpine
commands:
- apk add --no-cache curl
- yarn
- curl https://git.scode.ovh/Timo/standard/raw/branch/master/ci.js > ci.js
- name: build
image: node:lts-alpine
environment:
TOKEN:
from_secret: npm_token
commands:
- echo "$TOKEN" > ~/.npmrc
- npm i -g typescript
- node ci.js

View File

@ -7,23 +7,33 @@
// @ts-nocheck
/* eslint-disable no-console */
/* eslint-disable id-match */
'use strict';
// eslint-disable-next-line node/no-missing-require, id-match
const { InteractiveOptions } = require ('./dist/lib/index.js');
const {
StringOption,
BooleanOption,
NumberOption,
ArrayOption,
FolderOption
} = require ('./dist/lib/index.js');
(async () => {
const reader = new InteractiveOptions ([
{ name: 'str', type: 'string', env: 'TEST_STR' },
{ name: 'bool', type: 'boolean', env: 'TEST_BOOL' },
{ name: 'num', type: 'number', env: 'TEST_NUM' },
{ name: 'arr', type: 'array', env: 'TEST_ARR' },
{ name: 'fld', type: 'folder', env: 'TEST_FOLDER' }
], {
exit_on_interrupt: true,
configs: [ 'test.json' ],
error_callback: console.log
});
await reader.parse ();
console.log (reader.serialize (true));
/*
* const str = await new StringOption ({ name: 'str' })
*.parse ();
*const bool = await new BooleanOption ({ name: 'bool' })
*.parse ();
*const num = await new NumberOption ({ name: 'num' })
*.parse ();
*const arr = await new ArrayOption ({ name: 'arr' })
*.parse ();
*/
const fld = await new FolderOption ({ name: 'fld' })
.parse ();
const data = { /* str, bool, num, arr,*/ fld };
console.log (data);
}) ();

View File

@ -1,5 +1,17 @@
# Changelog
## 2.0.0
Restructuring to split different Option types and keep specific parameters separate
### Breaking Changes
- new structure
- option 'required' has been removed
- automatic console parameters have been removed
- help page
- quiet switch (interactive prompts can be disabled using the sources option)
## 1.8.0
callback in case an option could not be assigned instead of silently skipping

23
Jenkinsfile vendored
View File

@ -1,23 +0,0 @@
pipeline {
agent any
environment {
VERSION = VersionNumber([
versionNumberString:
'${BUILDS_ALL_TIME}',
versionPrefix: '1.8.',
worstResultForIncrement: 'SUCCESS'
])
}
stages {
stage('Building') {
steps {
script {
currentBuild.displayName = env.VERSION
}
sh 'yarn ci ${VERSION}'
}
}
}
}

View File

@ -1,8 +1,8 @@
# @sapphirecode/console-app
version: 1.8.x
version: 2.0.x
read parameters from env, console args or interactively
read parameters from env, config files, console args or interactively
## Installation
@ -17,46 +17,36 @@ yarn:
## Usage
```js
const {InteractiveOptions} = require('@sapphirecode/console-app');
const {
ArrayOption, // arrays made out of numbers, strings and booleans
BooleanOption,
FileOption, // paths that exist and are a file
FolderOption, // paths that exist and are a folder
NumberOption,
PathOption, // paths that exist in the file system
StringOption,
} = require('@sapphirecode/console-app');
const reader = new InteractiveOptions([
{
name: 'foo', // name of the option
type: 'boolean', // data type
required: true, // require option to be specified (optional)
default: false, // default value (optional)
alias: 'f', // shorthand alias in the console (optional)
env: 'fooenv', // environment variable to read from (optional)
description: 'the switch foo', // description in the help page (optional)
message: 'should foo be true?', // message when asking interactively (optional)
preset: [], // preset choices for string and path types (optional)
error: 'wrong input' // message to display when the user gives invalid input
const input = await new BooleanOption({
name: 'foo', // option name used in configs and console arguments
// optional settings:
default: false, // default value
sources: {
configs: [], // config files to read from. none by default
interactive: true, // use interactive prompts
console: true // read from console arguments
// environment is always on if the 'env' option below is specified
},
]);
const result = await reader.parse();
console.log(result.foo);
alias: 'f', // shorthand console argument name
env: 'foo_env', // name of the environment variable to read from
message: 'input foo', // message to display in interactive prompt
error: 'failed to read foo', // message to display when input was invalid
error_callback: (opt, val, err)=>{...}, // function to call when an option value could not be read
exit_on_interrupt: true, // exit program when user cancels the interactive prompt
}).parse();
```
available data types:
- string
- number
- boolean
- path: expects a path that exists
- file: expects a path that exists and is a file
- folder: expects a path that exists and is a folder
- array: arrays made out of strings, numbers and booleans
the console reader automatically adds the options --help (-h) and --quiet (-q)
- help: shows the yargs help screen
- quiet: prevents interactive queries and throws an error when not all required
parameters are specified
the reader can also be constructed with additional options that specify which
sources should be used. It reads from all, except config files by default
config files can import other config files with #include. example:
```json
@ -68,20 +58,6 @@ config files can import other config files with #include. example:
config files are parsed using [hjson](https://github.com/hjson/hjson-js)
the option exit_on_interrupt determines whether an error should be thrown or the
process should exit when the user presses control + c in an interactive prompt.
```js
const reader = new InteractiveOptions([], {
args: true,
env: true,
interactive: true,
configs: ['json files to search for options'],
exit_on_interrupt: true, // exit when user cancels prompt
error_callback: (opt, val, err)=>{...} // function to call when an option value could not be read
});
```
## License
MIT © Timo Hocker <timo@scode.ovh>

View File

@ -1,29 +0,0 @@
/*
* 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
*/
'use strict';
const https = require ('https');
const fs = require ('fs');
const { execSync: exec_sync } = require ('child_process');
const run_file = fs.createWriteStream ('.jenkins.run.js');
const [
,, ...args
] = process.argv;
run_file.on ('close', () => {
exec_sync (`node .jenkins.run.js ${args.join (' ')}`, { stdio: 'inherit' });
});
https.get (
'https://git.scode.ovh/Timo/standard/raw/branch/master/jenkins.run.js',
(msg) => {
msg.pipe (run_file);
}
);

View File

@ -1,108 +0,0 @@
/*
* 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
*/
import { Persistent } from '@sapphirecode/modelling';
import { TypeValidation } from './Types/TypeValidation';
import { PathType } from './Types/PathType';
import { OptionType } from './OptionType';
import { OptionSource } from './Sources/OptionSource';
import { EnvSource } from './Sources/EnvSource';
import { ArgSource } from './Sources/ArgSource';
import { ConfigSource } from './Sources/ConfigSource';
import { InteractiveSource } from './Sources/InteractiveSource';
import { Option, OptionProcess } from './Option';
import { ErrorCallback } from './Types/ErrorCallback';
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')
};
interface SourceConfig {
env?: boolean;
args?: boolean;
interactive?: boolean;
configs?: string[];
exit_on_interrupt?: boolean;
error_callback?: ErrorCallback;
}
export class InteractiveOptions extends Persistent {
protected options: Array<OptionProcess>;
protected quiet = false;
protected sources: OptionSource[] = [];
public constructor (
options: Array<Option>,
source_config: SourceConfig = {}
) {
super ();
this.options = options
.map ((v) => ({
filled: false,
type_validation: types[v.type],
...v
} as OptionProcess));
for (const option of this.options) {
if (
typeof option.default !== 'undefined'
&& typeof option.default !== option.type_validation.string_type
) {
throw new Error (
`default does not match option type on ${option.name}`
);
}
this.properties[option.name] = option.type_validation.persistent_type;
}
const exit_on_interrupt
= typeof source_config.exit_on_interrupt === 'boolean'
? source_config.exit_on_interrupt
: false;
if (
typeof source_config.configs !== 'undefined'
&& Array.isArray (source_config.configs)
) {
this.sources.push (new ConfigSource (
source_config.configs,
source_config.error_callback
));
}
if (source_config.env !== false)
this.sources.push (new EnvSource (source_config.error_callback));
if (source_config.args !== false)
this.sources.push (new ArgSource (source_config.error_callback));
if (source_config.interactive !== false) {
this.sources.push (new InteractiveSource (
exit_on_interrupt,
source_config.error_callback
));
}
}
public async parse (): Promise<Record<string, unknown>> {
for (const src of this.sources) {
// eslint-disable-next-line no-await-in-loop
await src.parse (this.options);
}
for (const opt of this.options) {
if (!opt.filled) {
opt.value = opt.default;
opt.filled = true;
}
this.set (opt.name, opt.value);
}
return this.to_object ();
}
}

View File

@ -5,26 +5,49 @@
* Created by Timo Hocker <timo@scode.ovh>, May 2020
*/
import { TypeValidation } from './Types/TypeValidation';
import { OptionType } from './OptionType';
import { TypeValidation } from './TypeValidation/TypeValidation';
import { ErrorCallback } from './ErrorCallback';
interface SourceConfig {
console?: boolean,
configs?: string[],
interactive?: boolean,
}
interface Option {
name: string;
type: OptionType;
required?: boolean;
default?: unknown;
sources?: SourceConfig;
alias?: string;
env?: string;
description?: string;
message?: string;
preset?: unknown[];
error?: string;
error_callback?: ErrorCallback;
exit_on_interrupt?: boolean;
}
interface OptionProcess extends Option {
filled: boolean;
value?: unknown;
type_validation: TypeValidation;
class OptionValue {
public filled = false;
public value?: unknown;
public readonly type_validation: TypeValidation;
public constructor (type_validation: TypeValidation) {
this.type_validation = type_validation;
}
export { Option, OptionProcess };
public async assign_arg (
opt: Option,
value: unknown
): Promise<void> {
try {
this.value = await this.type_validation.to_type (value);
this.filled = true;
}
catch (e) {
if (typeof opt.error_callback !== 'undefined')
opt.error_callback (opt.name, value, e);
}
}
}
export { Option, OptionValue };

View File

@ -13,4 +13,3 @@ export type OptionType =
| 'folder'
| 'path'
| 'array';

View File

@ -0,0 +1,8 @@
import { TypeValidation } from '../TypeValidation/TypeValidation';
import { BaseOption } from './BaseOption';
export class ArrayOption extends BaseOption<(string|number|boolean)[]> {
protected get validation ():TypeValidation {
return new TypeValidation ('array');
}
}

54
lib/Options/BaseOption.ts Normal file
View File

@ -0,0 +1,54 @@
import { OptionSource } from '../Sources/OptionSource';
import { Option, OptionValue } from '../Option';
import { EnvSource } from '../Sources/EnvSource';
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[] = [];
private _config: Option;
public constructor (
config: Option
) {
this._config = config;
const sources = config.sources || {};
const exit_on_interrupt = config.exit_on_interrupt !== false;
if (typeof sources.configs !== 'undefined') {
this.sources.push (new ConfigSource (
sources.configs,
config.error_callback
));
}
this.sources.push (new EnvSource (config.error_callback));
if (sources.console !== false)
this.sources.push (new ArgSource (config.error_callback));
if (sources.interactive !== false) {
this.sources.push (new InteractiveSource (
exit_on_interrupt,
config.error_callback
));
}
}
protected abstract get validation(): TypeValidation;
public async parse (): Promise<T> {
const val = new OptionValue (this.validation);
for (const source of this.sources)
// eslint-disable-next-line no-await-in-loop
await source.parse (this._config, val);
if (!val.filled)
return this._config.default as T;
return val.value as T;
}
}

View File

@ -0,0 +1,8 @@
import { TypeValidation } from '../TypeValidation/TypeValidation';
import { BaseOption } from './BaseOption';
export class BooleanOption extends BaseOption<boolean> {
protected get validation ():TypeValidation {
return new TypeValidation ('boolean');
}
}

View File

@ -0,0 +1,9 @@
import { PathType } from '../TypeValidation/PathType';
import { TypeValidation } from '../TypeValidation/TypeValidation';
import { StringOption } from './StringOption';
export class FileOption extends StringOption {
protected get validation ():TypeValidation {
return new PathType ('file');
}
}

View File

@ -0,0 +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 {
return new PathType ('folder');
}
}

View File

@ -0,0 +1,8 @@
import { TypeValidation } from '../TypeValidation/TypeValidation';
import { BaseOption } from './BaseOption';
export class NumberOption extends BaseOption<number> {
protected get validation ():TypeValidation {
return new TypeValidation ('number');
}
}

View File

@ -0,0 +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 {
return new PathType ('path');
}
}

View File

@ -0,0 +1,14 @@
import { TypeValidation } from '../TypeValidation/TypeValidation';
import { StringOptionConfig } from '../SubConfigs';
import { BaseOption } from './BaseOption';
export class StringOption extends BaseOption<string> {
protected get validation ():TypeValidation {
return new TypeValidation ('string');
}
// eslint-disable-next-line no-useless-constructor
public constructor (config: StringOptionConfig) {
super (config);
}
}

View File

@ -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
};
default: undefined,
type: persistent_type
}
return yargs_config;
} 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 ();
return;
if (
opt.type === 'array'
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 val.assign_arg (opt, argv[opt.name]);
}
}

View File

@ -10,8 +10,8 @@ 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 { ErrorCallback } from '../Types/ErrorCallback';
import { OptionValue, Option } from '../Option';
import { ErrorCallback } from '../ErrorCallback';
import { OptionSource } from './OptionSource';
export class ConfigSource extends 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]);
}
await val.assign_arg (opt, data[opt.name]);
}
}

View File

@ -6,18 +6,25 @@
*/
/* 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'
&& typeof process.env[opt.env] !== 'undefined'
)
return this.assign_arg (opt, process.env[opt.env]);
return Promise.resolve ();
}));
public async parse (opt: Option, val:OptionValue): Promise<void> {
if (typeof opt.env === 'undefined')
return;
if (typeof process.env[opt.env] === 'undefined') {
if (typeof this.error_callback !== 'undefined') {
this.error_callback (
opt.name,
null,
new Error ('environment variable does not exist')
);
}
return;
}
await val.assign_arg (opt, process.env[opt.env]);
}
}

View File

@ -0,0 +1,19 @@
import { List } from 'enquirer';
import { InteractiveSubSource } from './InteractiveSubSource';
export class ArraySubSource extends InteractiveSubSource {
protected condition ():boolean {
return this.val.type_validation.option_type === 'array';
}
protected async run ():Promise<void> {
await this.val.assign_arg (
this.opt,
await new List ({
message: this.get_message (),
default: this.opt.default
})
.run ()
);
}
}

View File

@ -0,0 +1,19 @@
import { Confirm } from 'enquirer';
import { InteractiveSubSource } from './InteractiveSubSource';
export class BooleanSubSource extends InteractiveSubSource {
protected condition ():boolean {
return this.val.type_validation.option_type === 'boolean';
}
protected async run ():Promise<void> {
await this.val.assign_arg (
this.opt,
await new Confirm ({
message: this.get_message (),
default: this.opt.default
})
.run ()
);
}
}

View File

@ -0,0 +1,31 @@
import { OptionValue, Option } from '../../Option';
export abstract class InteractiveSubSource {
protected val: OptionValue;
protected opt: Option;
protected abstract condition():boolean;
protected abstract async run():Promise<void>;
public constructor (
val:OptionValue,
opt:Option
) {
this.val = val;
this.opt = opt;
}
public async parse ():Promise<boolean> {
if (this.condition ()) {
await this.run ();
return true;
}
return false;
}
protected get_message (): string {
return typeof this.opt.message === 'undefined'
? `input ${this.opt.name}`
: this.opt.message;
}
}

View File

@ -0,0 +1,34 @@
/* eslint-disable no-sync */
import { dirname, join, sep, basename } from 'path';
import fs from 'fs-extra';
function read_dir (dir:string, exclude_files:boolean):string[] {
const contents = fs.readdirSync (dir);
contents.unshift ('..');
if (exclude_files) {
return contents.filter ((c) => {
const full_path = join (dir, c);
try {
return fs.statSync (full_path)
.isDirectory ();
}
catch {
return false;
}
});
}
return contents;
}
interface PathPromptOptions {
starting_dir?: string;
folders_only?: boolean;
}
export class PathPrompt {
private _options: PathPromptOptions;
public constructor (options:PathPromptOptions) {
this._options = options;
}
}

View File

@ -0,0 +1,24 @@
import { InteractiveSubSource } from './InteractiveSubSource';
import { PathPrompt } from './PathCustomPrompt';
export class PathSubSource extends InteractiveSubSource {
protected condition ():boolean {
return [
'path',
'file',
'folder'
].includes (this.val.type_validation.option_type);
}
protected async run (): Promise<void> {
await this.val.assign_arg (
this.opt,
await new PathPrompt ({
message: this.get_message (),
default: this.opt.default,
folder_only: this.val.type_validation.option_type === 'folder'
})
.run ()
);
}
}

View File

@ -0,0 +1,22 @@
import { AutoComplete } from 'enquirer';
import { StringOptionConfig } from '../../SubConfigs';
import { InteractiveSubSource } from './InteractiveSubSource';
export class PresetSubSource extends InteractiveSubSource {
protected condition ():boolean {
return typeof (this.opt as StringOptionConfig).preset !== 'undefined';
}
protected async run ():Promise<void> {
await this.val.assign_arg (
this.opt,
await new AutoComplete ({
message: this.get_message (),
default: this.opt.default,
choices: (this.opt as StringOptionConfig).preset,
limit: 10
})
.run ()
);
}
}

View File

@ -0,0 +1,19 @@
import { Input } from 'enquirer';
import { InteractiveSubSource } from './InteractiveSubSource';
export class StringSubSource extends InteractiveSubSource {
protected condition ():boolean {
return true;
}
protected async run ():Promise<void> {
await this.val.assign_arg (
this.opt,
await new Input ({
message: this.get_message (),
default: this.opt.default
})
.run ()
);
}
}

View File

@ -0,0 +1,13 @@
import { ArraySubSource } from './ArraySubSource';
import { BooleanSubSource } from './BooleanSubSource';
import { PresetSubSource } from './PresetSubSource';
import { StringSubSource } from './StringSubSource';
import { PathSubSource } from './PathSubSource';
export const sources = [
ArraySubSource,
BooleanSubSource,
PresetSubSource,
PathSubSource,
StringSubSource
];

View File

@ -7,10 +7,10 @@
/* eslint-disable no-console */
/* eslint-disable no-process-exit */
import { Confirm, Input, List, AutoComplete } from 'enquirer';
import { OptionProcess, Option } from '../Option';
import { ErrorCallback } from '../Types/ErrorCallback';
import { ErrorCallback } from '../ErrorCallback';
import { Option, OptionValue } from '../Option';
import { OptionSource } from './OptionSource';
import { sources } from './Interactive';
export class InteractiveSource extends OptionSource {
private _exit_on_interrupt: boolean;
@ -23,75 +23,29 @@ export class InteractiveSource extends OptionSource {
this._exit_on_interrupt = exit_on_interrupt;
}
private get_message (opt: Option): string {
return typeof opt.message === 'undefined'
? `input ${opt.name}`
: opt.message;
}
private async prompt (opt: OptionProcess): Promise<void> {
if (opt.filled)
return;
let value = null;
if (
opt.type === 'string'
|| opt.type === 'file'
|| opt.type === 'folder'
|| opt.type === 'path'
|| opt.type === 'number'
) {
if (typeof opt.preset === 'undefined') {
value = await new Input ({
message: this.get_message (opt),
default: opt.default
})
.run ();
}
else {
value = await new AutoComplete ({
message: this.get_message (opt),
default: opt.default,
choices: opt.preset,
limit: 10
})
.run ();
}
}
if (
opt.type === 'boolean'
) {
value = await new Confirm ({
message: this.get_message (opt),
default: opt.default
})
.run ();
}
if (opt.type === 'array') {
value = await new List ({
message: this.get_message (opt),
default: opt.default
})
.run ();
}
if (value === null)
private async prompt (opt: Option, val:OptionValue): Promise<void> {
if (val.filled)
return;
await this.assign_arg (opt, value);
}
public async parse (options: OptionProcess[]): Promise<void> {
for (const opt of options) {
while (!opt.filled) {
for (const src of sources) {
// eslint-disable-next-line no-await-in-loop
await this.prompt (opt)
if (await new src (val, opt)
.parse ())
break;
}
}
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 (!opt.filled)
if (!val.filled)
console.log (opt.error || 'input was invalid');
}
}
}
}

View File

@ -5,29 +5,15 @@
* Created by Timo Hocker <timo@scode.ovh>, May 2020
*/
import { OptionProcess } from '../Option';
import { ErrorCallback } from '../Types/ErrorCallback';
import { ErrorCallback } from '../ErrorCallback';
import { OptionValue, Option } from '../Option';
export abstract class OptionSource {
public abstract async parse(opt: OptionProcess[]): Promise<void>;
public abstract async parse(opt: Option, value: OptionValue): Promise<void>;
protected error_callback?: ErrorCallback;
public constructor (error_callback?: ErrorCallback) {
this.error_callback = error_callback;
}
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) {
if (typeof this.error_callback !== 'undefined')
this.error_callback (opt.name, value, e);
}
}
}

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

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

View File

@ -1,6 +1,6 @@
{
"name": "@sapphirecode/console-app",
"version": "1.0.0",
"version": "2.0.7",
"main": "dist/lib/index.js",
"author": "Timo Hocker <timo@scode.ovh>",
"license": "MIT",
@ -23,8 +23,7 @@
"scripts": {
"lint": "eslint . --ext .js,.jsx,.ts,.tsx,.vue,.mjs",
"test": "tsc && nyc ava",
"compile": "tsc",
"ci": "yarn && node jenkins.js"
"compile": "tsc"
},
"files": [
"LICENSE",

View File

@ -6,7 +6,7 @@
*/
import test from 'ava';
import { PathType } from '../lib/Types/PathType';
import { PathType } from '../lib/TypeValidation/PathType';
test ('no file', async (t) => {
const validator = new PathType ('file');

View File

@ -6,7 +6,7 @@
*/
import test from 'ava';
import { TypeValidation } from '../lib/Types/TypeValidation';
import { TypeValidation } from '../lib/TypeValidation/TypeValidation';
test ('string', async (t) => {
const validator = new TypeValidation ('string');

View File

@ -1,6 +1,6 @@
{
"compilerOptions": {
"target": "es5",
"target": "es6",
"module": "commonjs",
"outDir": "./dist",
"rootDir": "./",

638
yarn.lock

File diff suppressed because it is too large Load Diff