156 lines
		
	
	
		
			3.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			156 lines
		
	
	
		
			3.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| 'use strict'
 | |
| const arrayify = require('array-back')
 | |
| const option = require('./option')
 | |
| const Definition = require('./definition')
 | |
| const t = require('typical')
 | |
| 
 | |
| /**
 | |
|  * @module definitions
 | |
|  * @private
 | |
|  */
 | |
| 
 | |
| /**
 | |
|  * @alias module:definitions
 | |
|  */
 | |
| class Definitions extends Array {
 | |
|   load (definitions) {
 | |
|     this.clear()
 | |
|     arrayify(definitions).forEach(def => this.push(new Definition(def)))
 | |
|     this.validate()
 | |
|   }
 | |
| 
 | |
|   clear () {
 | |
|     this.length = 0
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * validate option definitions
 | |
|    * @returns {string}
 | |
|    */
 | |
|   validate (argv) {
 | |
|     const someHaveNoName = this.some(def => !def.name)
 | |
|     if (someHaveNoName) {
 | |
|       halt(
 | |
|         'NAME_MISSING',
 | |
|         '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_TYPE',
 | |
|         '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_ALIAS',
 | |
|         '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_ALIAS',
 | |
|         'Invalid option definition: an alias must be a single character'
 | |
|       )
 | |
|     }
 | |
| 
 | |
|     const hypenAlias = this.some(def => {
 | |
|       invalidOption = def
 | |
|       return def.alias === '-'
 | |
|     })
 | |
|     if (hypenAlias) {
 | |
|       halt(
 | |
|         'INVALID_ALIAS',
 | |
|         'Invalid option definition: an alias cannot be "-"'
 | |
|       )
 | |
|     }
 | |
| 
 | |
|     const duplicateName = hasDuplicates(this.map(def => def.name))
 | |
|     if (duplicateName) {
 | |
|       halt(
 | |
|         'DUPLICATE_NAME',
 | |
|         'Two or more option definitions have the same name'
 | |
|       )
 | |
|     }
 | |
| 
 | |
|     const duplicateAlias = hasDuplicates(this.map(def => def.alias))
 | |
|     if (duplicateAlias) {
 | |
|       halt(
 | |
|         'DUPLICATE_ALIAS',
 | |
|         'Two or more option definitions have the same alias'
 | |
|       )
 | |
|     }
 | |
| 
 | |
|     const duplicateDefaultOption = hasDuplicates(this.map(def => def.defaultOption))
 | |
|     if (duplicateDefaultOption) {
 | |
|       halt(
 | |
|         'DUPLICATE_DEFAULT_OPTION',
 | |
|         'Only one option definition can be the defaultOption'
 | |
|       )
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * @param {string}
 | |
|    * @returns {Definition}
 | |
|    */
 | |
|   get (arg) {
 | |
|     return option.short.test(arg)
 | |
|       ? this.find(def => def.alias === option.short.name(arg))
 | |
|       : this.find(def => def.name === option.long.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))
 | |
|   }
 | |
| }
 | |
| 
 | |
| 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
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| module.exports = Definitions
 |