Compare commits

..

29 Commits

Author SHA1 Message Date
d9de76b188 use jasmine
All checks were successful
continuous-integration/drone/push Build is passing
2020-10-03 15:14:14 +02:00
b81b77b924 update-scanner: automatic update
Some checks failed
continuous-integration/drone/push Build is failing
eslint: 7.7.0 ==> 7.8.1 minor
2020-09-07 13:34:32 +02:00
ee6be8317d update-scanner: automatic update
Some checks failed
continuous-integration/drone/push Build is failing
@sapphirecode/modelling: 1.1.12 ==> 1.1.13 minor
ava: 3.11.1 ==> 3.12.1 minor
2020-08-30 15:57:26 +02:00
390cc10305 upgrade typescript
All checks were successful
continuous-integration/drone/push Build is passing
2020-08-21 13:16:34 +02:00
e1507edd5d update-scanner: automatic update
Some checks failed
continuous-integration/drone/push Build is failing
eslint: 7.6.0 ==> 7.7.0 minor
2020-08-19 08:26:00 +02:00
7268ed1643 fix drone config
Some checks failed
continuous-integration/drone/push Build is failing
2020-08-07 08:11:31 +02:00
04aa24a75d update-scanner: automatic update
Some checks failed
continuous-integration/drone/push Build is failing
ava: 3.10.1 ==> 3.11.1 minor
eslint: 7.5.0 ==> 7.6.0 minor
2020-08-04 12:57:31 +02:00
8eb8deb855 update-scanner: automatic update
All checks were successful
continuous-integration/drone/push Build is passing
@sapphirecode/eslint-config-ts: 1.1.21 ==> 1.1.22 minor
@sapphirecode/modelling: 1.1.11 ==> 1.1.12 minor
2020-07-25 16:51:48 +02:00
c0eb113036 fix double quote escape in path selector
All checks were successful
continuous-integration/drone/push Build is passing
2020-07-24 08:33:58 +02:00
6207de8e5b fix backslash breaking path selection
All checks were successful
continuous-integration/drone/push Build is passing
2020-07-24 08:18:36 +02:00
7521fb310c update description
Some checks failed
continuous-integration/drone/push Build is failing
2020-07-24 07:45:46 +02:00
125fb7b5e6 update-scanner: automatic update
All checks were successful
continuous-integration/drone/push Build is passing
@sapphirecode/eslint-config-ts: 1.1.20 ==> 1.1.21 minor
@sapphirecode/modelling: 1.1.10 ==> 1.1.11 minor
@sapphirecode/utilities: 1.8.4 ==> 1.8.5 minor
2020-07-19 15:08:47 +02:00
8f040c38eb update-scanner: automatic update
All checks were successful
continuous-integration/drone/push Build is passing
eslint: 7.4.0 ==> 7.5.0 minor
typescript: 3.9.6 ==> 3.9.7 minor
2020-07-19 12:12:54 +02:00
7ad999878a fix for number input, new integer input
All checks were successful
continuous-integration/drone/push Build is passing
2020-07-19 11:41:06 +02:00
7395241329 update-scanner: automatic update
All checks were successful
continuous-integration/drone/push Build is passing
@sapphirecode/eslint-config-ts: 1.1.19 ==> 1.1.20 minor
@sapphirecode/modelling: 1.1.9 ==> 1.1.10 minor
yargs: 15.4.0 ==> 15.4.1 minor
2020-07-16 10:52:47 +02:00
ed2d0ba047 update-scanner: automatic update
All checks were successful
continuous-integration/drone/push Build is passing
@sapphirecode/eslint-config-ts: 1.1.17 ==> 1.1.19 minor
@sapphirecode/modelling: 1.1.7 ==> 1.1.9 minor
@sapphirecode/utilities: 1.7.2 ==> 1.8.4 minor
ava: 3.9.0 ==> 3.10.1 minor
enquirer: 2.3.5 ==> 2.3.6 minor
eslint: 7.3.1 ==> 7.4.0 minor
typescript: 3.9.5 ==> 3.9.6 minor
yargs: 15.3.1 ==> 15.4.0 minor
2020-07-10 12:33:41 +02:00
78c09809e6 fix
All checks were successful
continuous-integration/drone/push Build is passing
2020-07-10 08:40:46 +02:00
85b02b552f fix
Some checks failed
continuous-integration/drone/push Build is failing
2020-07-10 08:38:58 +02:00
3e178b28ae switch to drone
Some checks failed
continuous-integration/drone/push Build is failing
2020-07-10 08:36:54 +02:00
f5ee4665dc update-scanner: automatic update
@sapphirecode/eslint-config-ts: 1.1.16 ==> 1.1.17 minor
@sapphirecode/modelling: 1.1.5 ==> 1.1.7 minor
@sapphirecode/utilities: 1.4.5 ==> 1.7.2 minor
2020-07-01 09:32:53 +02:00
fe4057d0b5 update-scanner: automatic update
@sapphirecode/eslint-config-ts: 1.1.14 ==> 1.1.16 minor
@sapphirecode/modelling: 1.1.4 ==> 1.1.5 minor
@sapphirecode/utilities: 1.4.4 ==> 1.4.5 minor
@types/hjson: 2.4.1 ==> 2.4.2 minor
eslint: 7.3.0 ==> 7.3.1 minor
2020-06-24 12:51:58 +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
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
42 changed files with 1883 additions and 5302 deletions

14
.drone.yml Normal file
View File

@ -0,0 +1,14 @@
kind: pipeline
name: default
steps:
- name: setup
image: registry:5000/node-build
commands:
- yarn
- curl https://git.scode.ovh/Timo/standard/raw/branch/master/ci.js > ci.js
- name: build
image: registry:5000/node-build
commands:
- node ci.js

View File

@ -8,6 +8,7 @@
// @ts-nocheck
/* eslint-disable no-console */
/* eslint-disable id-match */
/* eslint-disable node/no-missing-require */
'use strict';
@ -16,7 +17,8 @@ const {
BooleanOption,
NumberOption,
ArrayOption,
FolderOption
FolderOption,
IntegerOption
} = require ('./dist/lib/index.js');
(async () => {
@ -26,12 +28,14 @@ const {
.parse ();
const num = await new NumberOption ({ name: 'num' })
.parse ();
const int = await new IntegerOption ({ 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 };
const data = { str, bool, num, int, arr, fld };
console.log (data);
}) ();

View File

@ -1,5 +1,22 @@
# Changelog
## 2.1.0
- Fix for NumberOption: do not cut off float values
- New type IntegerOption: only allows integer values
## 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.1.x
read parameters from env, console args or interactively
read parameters from env, config files, console args or interactively
## Installation
@ -17,46 +17,37 @@ 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, // integer and float values
IntegerOption, // only integer values
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 +59,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>

14
jasmine.json Normal file
View File

@ -0,0 +1,14 @@
{
"spec_dir": "test",
"spec_files": [
"spec/*.js",
"spec/*.ts"
],
"helpers": [
"helpers/*.js",
"helpers/*.ts"
],
"stopSpecOnExpectationFailure": false,
"random": false
}

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,3 +1,10 @@
/*
* 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>, October 2020
*/
export type ErrorCallback = (
option: string,
value: unknown,

View File

@ -6,23 +6,24 @@
*/
import { TypeValidation } from './TypeValidation/TypeValidation';
import { ErrorCallback } from './ErrorCallback';
interface SourceConfig {
console?: boolean,
configs?: string[],
env?: boolean,
interactive?: boolean,
}
interface Option {
name: string;
required?: boolean;
default?: unknown;
sources?: SourceConfig;
alias?: string;
env?: string;
message?: string;
error?: string;
error_callback?: ErrorCallback;
exit_on_interrupt?: boolean;
}
class OptionValue {
@ -33,6 +34,20 @@ class OptionValue {
public constructor (type_validation: TypeValidation) {
this.type_validation = type_validation;
}
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

@ -8,6 +8,7 @@
export type OptionType =
'string'
| 'number'
| 'int'
| 'boolean'
| 'file'
| 'folder'

View File

@ -1,3 +1,10 @@
/*
* 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>, October 2020
*/
import { TypeValidation } from '../TypeValidation/TypeValidation';
import { BaseOption } from './BaseOption';

View File

@ -1,7 +1,13 @@
/*
* 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>, October 2020
*/
import { OptionSource } from '../Sources/OptionSource';
import { Option, OptionValue } from '../Option';
import { EnvSource } from '../Sources/EnvSource';
import { ErrorCallback } from '../ErrorCallback';
import { ArgSource } from '../Sources/ArgSource';
import { ConfigSource } from '../Sources/ConfigSource';
import { TypeValidation } from '../TypeValidation/TypeValidation';
@ -12,26 +18,29 @@ export abstract class BaseOption<T> {
private _config: Option;
public constructor (
config: Option,
error_callback?: ErrorCallback,
exit_on_interrupt = true
config: Option
) {
this._config = config;
const sources = config.sources || {};
if (typeof sources.configs !== 'undefined')
this.sources.push (new ConfigSource (sources.configs, error_callback));
const exit_on_interrupt = config.exit_on_interrupt !== false;
if (sources.env !== false)
this.sources.push (new EnvSource (error_callback));
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 (error_callback));
this.sources.push (new ArgSource (config.error_callback));
if (sources.interactive !== false) {
this.sources.push (new InteractiveSource (
exit_on_interrupt,
error_callback
config.error_callback
));
}
}

View File

@ -1,3 +1,10 @@
/*
* 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>, October 2020
*/
import { TypeValidation } from '../TypeValidation/TypeValidation';
import { BaseOption } from './BaseOption';

View File

@ -1,3 +1,10 @@
/*
* 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>, October 2020
*/
import { PathType } from '../TypeValidation/PathType';
import { TypeValidation } from '../TypeValidation/TypeValidation';
import { StringOption } from './StringOption';

View File

@ -1,3 +1,10 @@
/*
* 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>, October 2020
*/
import { PathType } from '../TypeValidation/PathType';
import { TypeValidation } from '../TypeValidation/TypeValidation';
import { StringOption } from './StringOption';

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>, October 2020
*/
import { TypeValidation } from '../TypeValidation/TypeValidation';
import { BaseOption } from './BaseOption';
export class IntegerOption extends BaseOption<number> {
protected get validation ():TypeValidation {
return new TypeValidation ('int');
}
}

View File

@ -1,3 +1,10 @@
/*
* 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>, October 2020
*/
import { TypeValidation } from '../TypeValidation/TypeValidation';
import { BaseOption } from './BaseOption';

View File

@ -1,3 +1,10 @@
/*
* 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>, October 2020
*/
import { PathType } from '../TypeValidation/PathType';
import { TypeValidation } from '../TypeValidation/TypeValidation';
import { StringOption } from './StringOption';

View File

@ -1,3 +1,10 @@
/*
* 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>, October 2020
*/
import { TypeValidation } from '../TypeValidation/TypeValidation';
import { StringOptionConfig } from '../SubConfigs';
import { BaseOption } from './BaseOption';

View File

@ -45,6 +45,6 @@ export class ArgSource extends OptionSource {
)
return;
await this.assign_arg (opt, val, argv[opt.name]);
await val.assign_arg (opt, argv[opt.name]);
}
}

View File

@ -64,6 +64,6 @@ export class ConfigSource extends OptionSource {
const keys = Object.keys (data);
if (keys.includes (opt.name))
await this.assign_arg (opt, val, data[opt.name]);
await val.assign_arg (opt, data[opt.name]);
}
}

View File

@ -11,10 +11,20 @@ import { OptionSource } from './OptionSource';
export class EnvSource extends OptionSource {
public async parse (opt: Option, val:OptionValue): Promise<void> {
if (
typeof opt.env !== 'undefined'
&& typeof process.env[opt.env] !== 'undefined'
)
await this.assign_arg (opt, val, process.env[opt.env]);
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,26 @@
/*
* 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>, October 2020
*/
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,26 @@
/*
* 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>, October 2020
*/
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,38 @@
/*
* 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>, October 2020
*/
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,26 @@
/*
* 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>, October 2020
*/
import { NumberPrompt } from 'enquirer';
import { InteractiveSubSource } from './InteractiveSubSource';
export class NumberSubSource extends InteractiveSubSource {
protected condition ():boolean {
return this.val.type_validation.option_type === 'number';
}
protected async run ():Promise<void> {
await this.val.assign_arg (
this.opt,
await new NumberPrompt ({
message: this.get_message (),
default: this.opt.default
})
.run ()
);
}
}

View File

@ -0,0 +1,29 @@
/*
* 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>, October 2020
*/
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,26 @@
/*
* 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>, October 2020
*/
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,20 @@
/*
* 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>, October 2020
*/
import { ArraySubSource } from './ArraySubSource';
import { BooleanSubSource } from './BooleanSubSource';
import { PresetSubSource } from './PresetSubSource';
import { StringSubSource } from './StringSubSource';
import { NumberSubSource } from './NumberSubSource';
export const sources = [
ArraySubSource,
BooleanSubSource,
PresetSubSource,
NumberSubSource,
StringSubSource
];

View File

@ -7,11 +7,10 @@
/* eslint-disable no-console */
/* eslint-disable no-process-exit */
import { Confirm, Input, List, AutoComplete } from 'enquirer';
import { ErrorCallback } from '../ErrorCallback';
import { Option, OptionValue } from '../Option';
import { StringOptionConfig } from '../SubConfigs';
import { OptionSource } from './OptionSource';
import { sources } from './Interactive';
export class InteractiveSource extends OptionSource {
private _exit_on_interrupt: boolean;
@ -24,62 +23,16 @@ 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: Option, val:OptionValue): Promise<void> {
if (val.filled)
return;
let value = null;
const { option_type } = val.type_validation;
const { preset } = opt as StringOptionConfig;
if (
option_type === 'string'
|| option_type === 'file'
|| option_type === 'folder'
|| option_type === 'path'
|| option_type === 'number'
) {
if (typeof 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: preset,
limit: 10
})
.run ();
}
}
if (
option_type === 'boolean'
) {
value = await new Confirm ({
message: this.get_message (opt),
default: opt.default
})
.run ();
}
if (option_type === 'array') {
value = await new List ({
message: this.get_message (opt),
default: opt.default
})
.run ();
}
if (value === null)
return;
await this.assign_arg (opt, val, value);
for (const src of sources) {
// eslint-disable-next-line no-await-in-loop
if (await new src (val, opt)
.parse ())
break;
}
}
public async parse (opt: Option, val:OptionValue): Promise<void> {

View File

@ -16,19 +16,4 @@ export abstract class OptionSource {
public constructor (error_callback?: ErrorCallback) {
this.error_callback = error_callback;
}
protected async assign_arg (
opt: Option,
val: OptionValue,
value: unknown
): Promise<void> {
try {
val.value = await val.type_validation.to_type (value);
val.filled = true;
}
catch (e) {
if (typeof this.error_callback !== 'undefined')
this.error_callback (opt.name, value, e);
}
}
}

View File

@ -1,3 +1,10 @@
/*
* 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>, October 2020
*/
import { Option } from './Option';
interface StringOptionConfig extends Option {

View File

@ -20,14 +20,17 @@ export class PathType extends TypeValidation {
public async to_type (value: unknown): Promise<unknown> {
if (typeof value !== 'string')
throw new Error (`invalid type for ${this.option_type}`);
if (!await fs.pathExists (value))
const escaped = value.replace (/\\$/u, '')
.replace (/"$/u, '');
if (!await fs.pathExists (escaped))
throw new Error ('path does not exist');
if (this.option_type === 'path')
return value;
return escaped;
const stat = await fs.stat (value);
const stat = await fs.stat (escaped);
if (stat.isDirectory () === (this.option_type === 'folder'))
return value;
return escaped;
throw new Error ('cannot assign folder to file');
}

View File

@ -40,6 +40,13 @@ export class TypeValidation {
return Promise.resolve (String (value));
if (this.option_type === 'number') {
const as_num = parseFloat (String (value));
if (isNaN (as_num))
throw new Error ('value is not a number');
return Promise.resolve (as_num);
}
if (this.option_type === 'int') {
const as_num = parseInt (String (value));
if (isNaN (as_num))
throw new Error ('value is not a number');

View File

@ -12,3 +12,4 @@ export { FolderOption } from './Options/FolderOption';
export { NumberOption } from './Options/NumberOption';
export { PathOption } from './Options/PathOption';
export { StringOption } from './Options/StringOption';
export { IntegerOption } from './Options/IntegerOption';

View File

@ -1,30 +1,35 @@
{
"name": "@sapphirecode/console-app",
"version": "1.0.0",
"version": "2.1.7",
"main": "dist/lib/index.js",
"author": "Timo Hocker <timo@scode.ovh>",
"author": {
"name": "Timo Hocker",
"email": "timo@scode.ovh"
},
"license": "MIT",
"repository": {
"type": "git",
"url": "git@git.scode.ovh:timo/console-app"
"url": "https://git.scode.ovh:timo/console-app.git"
},
"description": "read parameters from env, console args or interactively",
"bugs": "https://redmine.scode.ovh/projects/console-app",
"description": "read parameters from env, config files, console args or interactively",
"devDependencies": {
"@ava/typescript": "^1.1.1",
"@sapphirecode/eslint-config-ts": "^1.1.4",
"@types/fs-extra": "^9.0.0",
"@types/hjson": "^2.4.1",
"@types/jasmine": "^3.5.14",
"@types/yargs": "^15.0.5",
"ava": "^3.8.2",
"eslint": "^7.0.0",
"jasmine": "^3.6.1",
"jasmine-ts": "^0.3.0",
"nyc": "^15.0.1",
"typescript": "^3.9.2"
"ts-node": "^9.0.0",
"typescript": "^4.0.2"
},
"scripts": {
"lint": "eslint . --ext .js,.jsx,.ts,.tsx,.vue,.mjs",
"test": "tsc && nyc ava",
"compile": "tsc",
"ci": "yarn && node jenkins.js"
"test": "nyc jasmine-ts --config=\"jasmine.json\"",
"compile": "tsc"
},
"files": [
"LICENSE",
@ -38,5 +43,13 @@
"fs-extra": "^9.0.0",
"hjson": "^3.2.1",
"yargs": "^15.3.1"
}
}
},
"keywords": [
"interactive",
"console input",
"config",
"command line args",
"environment variables",
"parsing"
]
}

View File

@ -1,46 +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 test from 'ava';
import { PathType } from '../lib/TypeValidation/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');
});

View File

@ -1,85 +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 test from 'ava';
import { TypeValidation } from '../lib/TypeValidation/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);
});
test ('no array', (t) => {
const validator = new TypeValidation ('array');
t.throws (
() => validator.to_type (1),
{ message: 'value is not an array' }
);
});
test ('array', async (t) => {
const validator = new TypeValidation ('array');
const res = await validator.to_type ([
'foo',
'bar',
'baz'
]);
t.deepEqual (res, [
'foo',
'bar',
'baz'
]);
});
test ('string array', async (t) => {
const validator = new TypeValidation ('array');
const res = await validator.to_type ('f o o,bar , baz');
t.deepEqual (res, [
'f o o',
'bar',
'baz'
]);
});

46
test/spec/paths.ts Normal file
View File

@ -0,0 +1,46 @@
/*
* 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>, October 2020
*/
import { PathType } from '../../lib/TypeValidation/PathType';
describe ('paths', () => {
it ('no file', async () => {
const validator = new PathType ('file');
await expectAsync (
validator.to_type ('test')
)
.toBeRejectedWithError ('cannot assign folder to file');
});
it ('file', async () => {
const validator = new PathType ('file');
const res = await validator.to_type ('package.json');
expect (res)
.toEqual ('package.json');
});
it ('no folder', async () => {
const validator = new PathType ('folder');
await expectAsync (validator.to_type ('package.json'))
.toBeRejectedWithError ('cannot assign folder to file');
});
it ('folder', async () => {
const validator = new PathType ('folder');
const res = await validator.to_type ('test');
expect (res)
.toEqual ('test');
});
it ('no path', async () => {
const validator = new PathType ('path');
await expectAsync (validator.to_type ('doesnotexist.file'))
.toBeRejectedWithError ('path does not exist');
});
it ('path', async () => {
const validator = new PathType ('path');
const res = await validator.to_type ('test');
expect (res)
.toEqual ('test');
});
});

102
test/spec/types.ts Normal file
View File

@ -0,0 +1,102 @@
/*
* 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>, October 2020
*/
import { TypeValidation } from '../../lib/TypeValidation/TypeValidation';
// eslint-disable-next-line max-lines-per-function
describe ('type validation', () => {
it ('string', async () => {
const validator = new TypeValidation ('string');
const res = await validator.to_type ('foo');
expect (res)
.toEqual ('foo');
});
it ('no number', () => {
const validator = new TypeValidation ('number');
expect (
() => validator.to_type ('foo')
)
.toThrowError ('value is not a number');
});
it ('number', async () => {
const validator = new TypeValidation ('number');
const res = await validator.to_type ('123.4');
expect (res)
.toEqual (123.4);
});
it ('int', async () => {
const validator = new TypeValidation ('int');
const res = await validator.to_type ('123.4');
expect (res)
.toEqual (123);
});
it ('no boolean', () => {
const validator = new TypeValidation ('boolean');
expect (
() => validator.to_type ('foo')
)
.toThrowError ('value is not a boolean');
});
it ('boolean', async () => {
const validator = new TypeValidation ('boolean');
const r1 = await validator.to_type ('false');
const r2 = await validator.to_type ('true');
expect (r1)
.toEqual (false);
expect (r2)
.toEqual (true);
});
it ('boolean number', async () => {
const validator = new TypeValidation ('boolean');
const r1 = await validator.to_type (0);
const r2 = await validator.to_type (1);
expect (r1)
.toEqual (false);
expect (r2)
.toEqual (true);
});
it ('no array', () => {
const validator = new TypeValidation ('array');
expect (
() => validator.to_type (1)
)
.toThrowError ('value is not an array');
});
it ('array', async () => {
const validator = new TypeValidation ('array');
const res = await validator.to_type ([
'foo',
'bar',
'baz'
]);
expect (res)
.toEqual ([
'foo',
'bar',
'baz'
]);
});
it ('string array', async () => {
const validator = new TypeValidation ('array');
const res = await validator.to_type ('f o o,bar , baz');
expect (res)
.toEqual ([
'f o o',
'bar',
'baz'
]);
});
});

File diff suppressed because it is too large Load Diff

2782
yarn.lock

File diff suppressed because it is too large Load Diff