UNPKG

3.63 kBJavaScriptView Raw
1'use strict'
2const arrayify = require('array-back')
3const option = require('./option')
4const Definition = require('./definition')
5const t = require('typical')
6
7/**
8 * @module definitions
9 * @private
10 */
11
12/**
13 * @alias module:definitions
14 */
15class Definitions extends Array {
16 load (definitions) {
17 this.clear()
18 arrayify(definitions).forEach(def => this.push(new Definition(def)))
19 this.validate()
20 }
21
22 clear () {
23 this.length = 0
24 }
25
26 /**
27 * validate option definitions
28 * @returns {string}
29 */
30 validate (argv) {
31 const someHaveNoName = this.some(def => !def.name)
32 if (someHaveNoName) {
33 halt(
34 'NAME_MISSING',
35 'Invalid option definitions: the `name` property is required on each definition'
36 )
37 }
38
39 const someDontHaveFunctionType = this.some(def => def.type && typeof def.type !== 'function')
40 if (someDontHaveFunctionType) {
41 halt(
42 'INVALID_TYPE',
43 'Invalid option definitions: the `type` property must be a setter fuction (default: `Boolean`)'
44 )
45 }
46
47 let invalidOption
48
49 const numericAlias = this.some(def => {
50 invalidOption = def
51 return t.isDefined(def.alias) && t.isNumber(def.alias)
52 })
53 if (numericAlias) {
54 halt(
55 'INVALID_ALIAS',
56 'Invalid option definition: to avoid ambiguity an alias cannot be numeric [--' + invalidOption.name + ' alias is -' + invalidOption.alias + ']'
57 )
58 }
59
60 const multiCharacterAlias = this.some(def => {
61 invalidOption = def
62 return t.isDefined(def.alias) && def.alias.length !== 1
63 })
64 if (multiCharacterAlias) {
65 halt(
66 'INVALID_ALIAS',
67 'Invalid option definition: an alias must be a single character'
68 )
69 }
70
71 const hypenAlias = this.some(def => {
72 invalidOption = def
73 return def.alias === '-'
74 })
75 if (hypenAlias) {
76 halt(
77 'INVALID_ALIAS',
78 'Invalid option definition: an alias cannot be "-"'
79 )
80 }
81
82 const duplicateName = hasDuplicates(this.map(def => def.name))
83 if (duplicateName) {
84 halt(
85 'DUPLICATE_NAME',
86 'Two or more option definitions have the same name'
87 )
88 }
89
90 const duplicateAlias = hasDuplicates(this.map(def => def.alias))
91 if (duplicateAlias) {
92 halt(
93 'DUPLICATE_ALIAS',
94 'Two or more option definitions have the same alias'
95 )
96 }
97
98 const duplicateDefaultOption = hasDuplicates(this.map(def => def.defaultOption))
99 if (duplicateDefaultOption) {
100 halt(
101 'DUPLICATE_DEFAULT_OPTION',
102 'Only one option definition can be the defaultOption'
103 )
104 }
105 }
106
107 /**
108 * @param {string}
109 * @returns {Definition}
110 */
111 get (arg) {
112 return option.short.test(arg)
113 ? this.find(def => def.alias === option.short.name(arg))
114 : this.find(def => def.name === option.long.name(arg))
115 }
116
117 getDefault () {
118 return this.find(def => def.defaultOption === true)
119 }
120
121 isGrouped () {
122 return this.some(def => def.group)
123 }
124
125 whereGrouped () {
126 return this.filter(containsValidGroup)
127 }
128 whereNotGrouped () {
129 return this.filter(def => !containsValidGroup(def))
130 }
131}
132
133function halt (name, message) {
134 const err = new Error(message)
135 err.name = name
136 throw err
137}
138
139function containsValidGroup (def) {
140 return arrayify(def.group).some(group => group)
141}
142
143function hasDuplicates (array) {
144 const items = {}
145 for (let i = 0; i < array.length; i++) {
146 const value = array[i]
147 if (items[value]) {
148 return true
149 } else {
150 if (t.isDefined(value)) items[value] = true
151 }
152 }
153}
154
155module.exports = Definitions