| 'use strict'; |
| |
| const isPlainObject = require('is-plain-obj'); |
| const arrify = require('arrify'); |
| const kindOf = require('kind-of'); |
| |
| const push = (obj, prop, value) => { |
| if (!obj[prop]) { |
| obj[prop] = []; |
| } |
| |
| obj[prop].push(value); |
| }; |
| |
| const insert = (obj, prop, key, value) => { |
| if (!obj[prop]) { |
| obj[prop] = {}; |
| } |
| |
| obj[prop][key] = value; |
| }; |
| |
| const prettyPrint = output => { |
| return Array.isArray(output) ? |
| `[${output.map(prettyPrint).join(', ')}]` : |
| kindOf(output) === 'string' ? JSON.stringify(output) : output; |
| }; |
| |
| const resolveType = value => { |
| if (Array.isArray(value) && value.length > 0) { |
| const [element] = value; |
| return `${kindOf(element)}-array`; |
| } |
| |
| return kindOf(value); |
| }; |
| |
| const normalizeExpectedType = (type, defaultValue) => { |
| const inferredType = type === 'array' ? 'string-array' : type; |
| |
| if (arrayTypes.includes(inferredType) && Array.isArray(defaultValue) && defaultValue.length === 0) { |
| return 'array'; |
| } |
| |
| return inferredType; |
| }; |
| |
| const passthroughOptions = ['stopEarly', 'unknown', '--']; |
| const primitiveTypes = ['string', 'boolean', 'number']; |
| const arrayTypes = primitiveTypes.map(t => `${t}-array`); |
| const availableTypes = [...primitiveTypes, 'array', ...arrayTypes]; |
| |
| const buildOptions = options => { |
| options = options || {}; |
| |
| const result = {}; |
| |
| passthroughOptions.forEach(key => { |
| if (options[key]) { |
| result[key] = options[key]; |
| } |
| }); |
| |
| Object.keys(options).forEach(key => { |
| let value = options[key]; |
| |
| if (key === 'arguments') { |
| key = '_'; |
| } |
| |
| // If short form is used |
| // convert it to long form |
| // e.g. { 'name': 'string' } |
| if (typeof value === 'string') { |
| value = {type: value}; |
| } |
| |
| if (isPlainObject(value)) { |
| const props = value; |
| const {type} = props; |
| |
| if (type) { |
| if (!availableTypes.includes(type)) { |
| throw new TypeError(`Expected type of "${key}" to be one of ${prettyPrint(availableTypes)}, got ${prettyPrint(type)}`); |
| } |
| |
| if (arrayTypes.includes(type)) { |
| const [elementType] = type.split('-'); |
| push(result, 'array', {key, [elementType]: true}); |
| } else { |
| push(result, type, key); |
| } |
| } |
| |
| if ({}.hasOwnProperty.call(props, 'default')) { |
| const {default: defaultValue} = props; |
| const defaultType = resolveType(defaultValue); |
| const expectedType = normalizeExpectedType(type, defaultValue); |
| |
| if (expectedType && expectedType !== defaultType) { |
| throw new TypeError(`Expected "${key}" default value to be of type "${expectedType}", got ${prettyPrint(defaultType)}`); |
| } |
| |
| insert(result, 'default', key, defaultValue); |
| } |
| |
| arrify(props.alias).forEach(alias => { |
| insert(result, 'alias', alias, key); |
| }); |
| } |
| }); |
| |
| return result; |
| }; |
| |
| module.exports = buildOptions; |
| module.exports.default = buildOptions; |