Spaces:
Running
Running
File size: 5,367 Bytes
78c921d |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 |
import arrayify from '../node_modules/array-back/index.mjs'
import * as argvTools from './argv-tools.mjs'
import t from '../node_modules/typical/index.mjs'
import Definition from './option-definition.mjs'
/**
* @module option-definitions
*/
/**
* @alias module:option-definitions
*/
class Definitions extends Array {
/**
* validate option definitions
* @param {boolean} [caseInsensitive=false] - whether arguments will be parsed in a case insensitive manner
* @returns {string}
*/
validate (caseInsensitive) {
const someHaveNoName = this.some(def => !def.name)
if (someHaveNoName) {
halt(
'INVALID_DEFINITIONS',
'Invalid option definitions: the `name` property is required on each definition'
)
}
const someDontHaveFunctionType = this.some(def => def.type && typeof def.type !== 'function')
if (someDontHaveFunctionType) {
halt(
'INVALID_DEFINITIONS',
'Invalid option definitions: the `type` property must be a setter fuction (default: `Boolean`)'
)
}
let invalidOption
const numericAlias = this.some(def => {
invalidOption = def
return t.isDefined(def.alias) && t.isNumber(def.alias)
})
if (numericAlias) {
halt(
'INVALID_DEFINITIONS',
'Invalid option definition: to avoid ambiguity an alias cannot be numeric [--' + invalidOption.name + ' alias is -' + invalidOption.alias + ']'
)
}
const multiCharacterAlias = this.some(def => {
invalidOption = def
return t.isDefined(def.alias) && def.alias.length !== 1
})
if (multiCharacterAlias) {
halt(
'INVALID_DEFINITIONS',
'Invalid option definition: an alias must be a single character'
)
}
const hypenAlias = this.some(def => {
invalidOption = def
return def.alias === '-'
})
if (hypenAlias) {
halt(
'INVALID_DEFINITIONS',
'Invalid option definition: an alias cannot be "-"'
)
}
const duplicateName = hasDuplicates(this.map(def => caseInsensitive ? def.name.toLowerCase() : def.name))
if (duplicateName) {
halt(
'INVALID_DEFINITIONS',
'Two or more option definitions have the same name'
)
}
const duplicateAlias = hasDuplicates(this.map(def => caseInsensitive && t.isDefined(def.alias) ? def.alias.toLowerCase() : def.alias))
if (duplicateAlias) {
halt(
'INVALID_DEFINITIONS',
'Two or more option definitions have the same alias'
)
}
const duplicateDefaultOption = this.filter(def => def.defaultOption === true).length > 1;
if (duplicateDefaultOption) {
halt(
'INVALID_DEFINITIONS',
'Only one option definition can be the defaultOption'
)
}
const defaultBoolean = this.some(def => {
invalidOption = def
return def.isBoolean() && def.defaultOption
})
if (defaultBoolean) {
halt(
'INVALID_DEFINITIONS',
`A boolean option ["${invalidOption.name}"] can not also be the defaultOption.`
)
}
}
/**
* Get definition by option arg (e.g. `--one` or `-o`)
* @param {string} [arg] the argument name to get the definition for
* @param {boolean} [caseInsensitive] whether to use case insensitive comparisons when finding the appropriate definition
* @returns {Definition}
*/
get (arg, caseInsensitive) {
if (argvTools.isOption(arg)) {
if (argvTools.re.short.test(arg)) {
const shortOptionName = argvTools.getOptionName(arg)
if (caseInsensitive) {
const lowercaseShortOptionName = shortOptionName.toLowerCase()
return this.find(def => t.isDefined(def.alias) && def.alias.toLowerCase() === lowercaseShortOptionName)
} else {
return this.find(def => def.alias === shortOptionName)
}
} else {
const optionName = argvTools.getOptionName(arg)
if (caseInsensitive) {
const lowercaseOptionName = optionName.toLowerCase()
return this.find(def => def.name.toLowerCase() === lowercaseOptionName)
} else {
return this.find(def => def.name === optionName)
}
}
} else {
return this.find(def => def.name === arg)
}
}
getDefault () {
return this.find(def => def.defaultOption === true)
}
isGrouped () {
return this.some(def => def.group)
}
whereGrouped () {
return this.filter(containsValidGroup)
}
whereNotGrouped () {
return this.filter(def => !containsValidGroup(def))
}
whereDefaultValueSet () {
return this.filter(def => t.isDefined(def.defaultValue))
}
static from (definitions, caseInsensitive) {
if (definitions instanceof this) return definitions
const result = super.from(arrayify(definitions), def => Definition.create(def))
result.validate(caseInsensitive)
return result
}
}
function halt (name, message) {
const err = new Error(message)
err.name = name
throw err
}
function containsValidGroup (def) {
return arrayify(def.group).some(group => group)
}
function hasDuplicates (array) {
const items = {}
for (let i = 0; i < array.length; i++) {
const value = array[i]
if (items[value]) {
return true
} else {
if (t.isDefined(value)) items[value] = true
}
}
}
export default Definitions
|