@softvisio/core
Version:
Softisio core
270 lines (218 loc) • 7.31 kB
JavaScript
import Ajv from "#lib/ajv";
import ansi from "#lib/ansi";
export default class Option {
#name;
#config;
#minItems;
#maxItems;
#value;
#validator;
#arrayValidator;
#helpDescription;
constructor ( name, config ) {
this.#name = name;
this.#config = config;
if ( this.maxItems && this.maxItems < this.minItems ) this._throwSpecError( "maxItems must be >= minItems" );
if ( this.#config.required != null && this.isArray ) this._throwSpecError( "required can't be used with arrays" );
// compile json schema
try {
this.#validator = new Ajv( {
"coerceTypes": true,
} ).compile( {
"type": "object",
"properties": {
"value": this.itemSchema,
},
} );
if ( this.isArray ) {
this.#arrayValidator = new Ajv( {
"coerceTypes": true,
} ).compile( this.#config.schema );
}
}
catch ( e ) {
throw `${ this.prefix } "${ this.name }" error compiling schema:\n${ JSON.stringify( config.schema, null, 4 ) }\n${ e }`;
}
// set default value
if ( this.default != null ) {
if ( this.isArray ) this._throwSpecError( "default value can't be used with arrays" );
const errors = this.addValue( this.default );
if ( errors.length ) this._throwSpecError( errors[ 0 ] );
}
}
// properties
get prefix () {
return this.isArgument
? "Argument"
: "Option";
}
get name () {
return this.#name;
}
get description () {
return this.#config.description;
}
get default () {
return this.#config.default;
}
get itemSchema () {
return this.isArray
? this.#config.schema.items
: this.#config.schema;
}
get type () {
return this.itemSchema.type;
}
get isRequired () {
return this.default == null && this.minItems > 0;
}
get minItems () {
if ( this.#minItems == null ) {
if ( this.isArray ) {
this.#minItems = this.#config.schema.minItems || 0;
}
else {
if ( this.#config.required ) this.#minItems = 1;
else this.#minItems = 0;
}
}
return this.#minItems;
}
get maxItems () {
if ( this.#maxItems == null ) {
if ( this.isArray ) {
this.#maxItems = this.#config.schema.maxItems || 0;
}
else {
this.#maxItems = 1;
}
}
return this.#maxItems;
}
get isArray () {
return this.#config.schema.type === "array";
}
get value () {
return this.#value;
}
// public
addValue ( value ) {
var errors = [],
data = { value },
isValid = this.#validator( data );
if ( !isValid ) {
errors.push( `${ this.prefix } "${ this.name }" ${ this.#validator.errors.messages[ 0 ] }` );
}
else {
if ( this.isArray ) {
if ( this.maxItems && this.#value && this.#value.length === this.maxItems ) {
errors.push( `${ this.prefix } "${ this.name }" can be repeated not more than ${ this.maxItems } time(s)` );
}
this.#value ||= [];
this.#value.push( data.value );
}
else {
this.#value = data.value;
}
}
return errors;
}
validate () {
var errors = [];
// check required
if ( this.isRequired && this.value == null ) {
errors.push( `${ this.prefix } "${ this.name }" is required.` );
}
// check array
else if ( this.isArray ) {
// check minItems
if ( this.minItems && this.value.length < this.minItems ) {
errors.push( `${ this.prefix } "${ this.name }" must be specified at least ${ this.minItems } time(s).` );
}
// check maxItems
if ( this.maxItems && this.value.length > this.maxItems ) {
errors.push( `${ this.prefix } "${ this.name }" must be specified not more than ${ this.maxItems } time(s).` );
}
// validate array
if ( this.value && !this.#arrayValidator( this.value ) ) {
errors.push( `${ this.prefix } "${ this.name }" ${ this.#arrayValidator.errors.messages[ 0 ] }` );
}
}
return errors;
}
getHelpDescription () {
if ( this.#helpDescription == null ) {
this.#helpDescription = this.description;
var tags = new Map();
// required
if ( this.isRequired ) {
tags.set( "REQUIRED" );
}
// repeatable
if ( this.isArray ) {
tags.set( "REPEATABLE" );
}
// min
if ( this.itemSchema.minimum != null ) {
tags.set( "MIN", this.itemSchema.minimum );
}
// max
if ( this.itemSchema.maximum != null ) {
tags.set( "MAX", this.itemSchema.maximum );
}
// enum
if ( this.itemSchema.enum ) {
tags.set( "ALLOWED VALUES", this.itemSchema.enum.map( value => this._quoteValue( value ) ).join( ", " ) );
}
// defaulr, do not print default value for boolean options
if ( this.default != null && ( !this.isBoolean || this.isNegatable ) ) {
tags.set( "DEFAULT", this._getHelpDefaultValue() );
}
// add tags
if ( tags.size ) {
if ( !this.#helpDescription.endsWith( "\n" ) && !this.#helpDescription.endsWith( "\r" ) ) {
this.#helpDescription += " ";
}
this.#helpDescription += [ ...tags.entries() ]
.map( ( [ name, value ] ) => {
name = ansi.bold.underline( name );
if ( value == null ) {
return name + ".";
}
else {
return name + ": " + value + ".";
}
} )
.join( " " );
}
}
return this.#helpDescription;
}
// protected
_throwSpecError ( error ) {
console.log( `Argument "${ this.name }" ${ error }.` );
process.exit( 2 );
}
_quoteValue ( value ) {
if ( typeof value === "string" ) {
return `"${ value }"`;
}
else {
return value;
}
}
_getHelpDefaultValue () {
return this._quoteValue( this.default );
}
_getHelpType () {
if ( this.itemSchema.format ) {
return this.itemSchema.format;
}
else if ( this.type ) {
return this.type;
}
else {
return "value";
}
}
}