/**
* Parse a command line options & environment variables and create a configuration object.
*
const cmdLineOpt = require('cmd/options')
const argv = require('cmd/argv')
let paramsDescription = cmdLineOpt.describe('cmd/generateDDL',
'Check database structure for application domain. ' +
'Generate DDL (both create and alter) if need and optionally run it'
).add(
argv.establishConnectionFromCmdLineAttributes._cmdLineParams
).add({
short: 'm', long: 'models', param: 'modelsList', defaultValue: '*',
help: 'Comma separated model names for DDL generation. If -e specified this options is ignored'
}).add({
short: 'e', long: 'entities', param: 'entitiesList', defaultValue: '*',
help: 'Comma separated entity names list for DDL generation'
}).add({
short: 'out', long: 'out', param: 'outputPath', defaultValue: process.cwd(),
help: 'Folder to output generated DDLs (one file per connection)'
}).add({
short: 'autorun', long: 'autorun', defaultValue: false,
help: 'execute DDL statement after generation. BE CAREFUL! DO NOT USE ON PRODUCTION'
})
let passedOptions = paramsDescription.parseVerbose({}, true)
*
* @author pavel.mash
* @module @unitybase/base/options
*/
// [{short: 'u', long: 'user', param: 'userName', defaultValue: true, searchInEnv: true, help: 'A user name for server connection'}]
const _ = require('lodash')
/**
* @class
* @param commandName
* @param commandDescription
* @param {String} [cli='ub'] An executable used to execute a command `commandName`. For example: `ubcli`
* @constructor
*/
function Options (commandName, commandDescription, cli) {
this.commandName = commandName || ''
this.commandDescription = commandDescription || ''
this.cli = cli
this.options = []
}
/**
* @typedef {Object} Option
* @property {String} short - a short option name
* @property {String} long - a long option name. This name are used in `parse` result
* @property {String} [param] - if parameter has a value - help string for a parameter name `-short param`
* @property {*} [defaultValue] - a default value for a property. For a string properties what allow empty value set it to `*`
* @property {Boolean} [searchInEnv=false] - if property do not passed as a cmd line switch then
* perform search of `UB_`+long.toUpperCase() in environment variables
* @property {String} help - a help string for a `usage()` call
*/
/**
* Add a option(s) definition.
* @param {Option|Array.<Option>} otherOptions
* @return {Options}
*/
Options.prototype.add = function add (otherOptions) {
if (Array.isArray(otherOptions)) {
this.options = this.options.concat(otherOptions)
} else {
this.options.push(otherOptions)
}
return this
}
/**
*
* Parse a command line & env variables for a options and create a configuration object.
* Return `undefined` in case options is not valid or a object with keys - options.long & values
* @param {Object} [defaults] Override for a command line attributes. If any - this one will be used
* @param {Array} [errors] If passed will bw filled by a errors in passed parameters
*/
Options.prototype.parse = function parse (defaults, errors) {
let result = _.defaults({}, defaults)
// [{short: 'u', long: 'user', param: 'userName', defaultValue: true, searchInEnv: true, help: 'A user name for server connection'}]
let valid = true
this.options.forEach(function (option) {
let val, t
if (!result.hasOwnProperty(option.long)) { // not passed in defaults
if (option.param) { // option with parameter `-http register`
val = switchValue(option.short)
if (typeof val === 'undefined') {
val = switchValue(option.long)
}
if ((typeof val === 'undefined') && option.searchInEnv) {
val = process.env['UB_' + option.long.toUpperCase()]
}
} else { // boolean option without parameter `-createDB`
if (switchIndex(option.short) !== -1) {
val = true
} else if (switchIndex(option.long) !== -1) {
val = true
} else if (option.searchInEnv) {
t = process.env['UB_' + option.long.toUpperCase()]
if (typeof t !== 'undefined') {
val = (t === 'true') || (t === 'TRUE')
}
}
}
if ((typeof val === 'undefined')) {
val = option.defaultValue
}
if ((typeof val === 'undefined')) {
valid = false
if (errors) {
errors.push('expected parameter "' + option.long + '" not found')
}
} else {
result[option.long] = (val === '*' ? '' : val)
}
}
})
return valid ? result : undefined
}
/**
* In case `-help` or '-?' command line switch found or passed options not match a options set
* will output a usage help to console and return `undefined`, else - return a parsed options
*
* @param {Object} [defaults] - Override passed parameter values by this one
* @param {Boolean} [outputParsed=false] output a parsed parameters to a log
*/
Options.prototype.parseVerbose = function parseVerbose (defaults, outputParsed) {
let result
let errors = []
if (switchIndex('?') !== -1 || switchIndex('help') !== -1) {
console.log(this.usage())
} else {
result = this.parse(defaults, errors)
if (!result) console.log(this.usage())
if (errors.length) {
console.error('\nInvalid usage')
console.error('\t' + errors.join('\n\t'))
}
if (outputParsed) {
console.info('Run a command "%s" using %j', this.commandName, result)
}
}
return result
}
Options.prototype.howParamsAppearInCommandLine = function () {
let res = []
this.options.forEach(function (option) {
let elm = '-' + option.short + (option.param ? ' ' + option.param : '')
if (option.defaultValue) elm = '[' + elm + ']'
res.push(elm)
})
return res.join(' ')
}
/**
* Output a usage info to console
*/
Options.prototype.usage = function usage () {
const PARAM_IDENT = 15
if (this.commandDescription) {
console.info('\n' + this.commandDescription)
}
console.info(`\n${this.cli} ${this.commandName} ` + this.howParamsAppearInCommandLine())
console.info('\nwhere:')
let envs = []
let res = []
// create a parameters description
this.options.forEach(function (option) {
let elm = '-' + option.short
if (option.short !== option.long) elm += ' | ' + option.long
if (option.searchInEnv) elm += '*'
elm += ' '.repeat(elm.length > PARAM_IDENT - 1 ? 1 : PARAM_IDENT - elm.length) + option.help
res.push(elm)
if (typeof option.defaultValue !== 'undefined') {
res.push(' '.repeat(PARAM_IDENT + 5) + 'Default: ' + ((option.defaultValue === '') ? '""' : option.defaultValue))
}
if (option.searchInEnv) {
envs.push('UB_' + option.long.toUpperCase())
}
})
res = ' ' + res.join('\n ')
if (envs.length) {
res += '\n\n* will lookup a environment variable in case switch omitted: ' + JSON.stringify(envs)
}
return res
}
/**
* Create a new options definition.
* @example
const cmdLineOpt = require('cmd/options')
let paramsDescription = cmdLineOpt.describe('cmd/createStore',
'Create internal store structure (folders) for specifies FileSystem store'
).add({
short: 'cfg', long: 'cfg', param: 'serverConfig', defaultValue: 'ubConfig.json',
help: 'Server config'
}).add({
short: 'store', long: 'store', param: 'storesList', defaultValue: '*',
help: 'Comma separated blob stores list'
})
let options = paramsDescription.parseVerbose({}, true);
* @param {String} commandName Name of a command then executed from a command line
* @param {String} [commandDescription] Command description for help (-help switch)
* @param {String} [cli] Command line interpretator
* @return {Options}
*/
exports.describe = function describe (commandName = '', commandDescription = '', cli = 'ub') {
return new Options(commandName, commandDescription, cli)
}
/**
* Determines whether a switchName was passed as a command-line argument to the application
* Switch may be specified in the following ways on the command line:
* -switchName
* or
* /switchName
* @param switchName
* @returns {Number} switch index if found or -1 otherwise
*/
exports.switchIndex = function switchIndex (switchName) {
let res = process.argv.indexOf('-' + switchName)
return (res === -1) ? process.argv.indexOf('/' + switchName) : res
}
const switchIndex = exports.switchIndex
/**
* Determines whether a switchName was passed as a command-line argument to the application and have VALUE
* Switch values may be specified in the following ways on the command line:
* -switchName Value
* or
* /switchName Value
* @param switchName
* @returns {String|undefined} switch value or `undefined` in case switch not found or switch not have value
*/
exports.switchValue = function switchValue (switchName) {
let idx = switchIndex(switchName) + 1
let val
return (idx && (val = process.argv[idx]) && val.charAt !== '-' && val.charAt !== '/') ? process.argv[idx] : undefined
}
const switchValue = exports.switchValue