UNPKG

flipflag

Version:

cli flags with aliases, from argv, env, globals, --env.flags, callbacks, preserves casing

360 lines (297 loc) 9.25 kB
// @TODO: handle parsing cli such as `node eh --canada -- foo bar baz -- --cool io` // https://www.npmjs.com/package/minimist // https://npmcompare.com/compare/commander,minimist,nomnom,optimist,yargs const timer = require('fliptime') const minimist = require('minimist') const nodeFlags = require('./node-flags') const IS = require('izz') const argv = minimist(process.argv.slice(2)) const cache = Object.assign({}, {}, argv) // const yargs = require('yargs') const aliased = {} function aliasFor(flag) { if (flag.includes(',')) flag = flag.split(',').pop() const alias = false for (const key in aliased) { const aliasValues = aliased[key] if (aliasValues.includes(flag)) return key } return alias } function addAliases(aliases) { Object.assign(aliased, aliases) } function parseAliases(aliases) { if (Array.isArray(aliases)) { return aliases.forEach(parseAliases) } else if (aliases && typeof aliases === 'object') { return } // split for each one if (aliases.includes(',')) { aliases = aliases.replace(/\s\S/gmi, '').split(',') // last one is the name const alias = aliases[aliases.length - 1] addAliases({ [alias]: aliases, }) } } function searchAll(nEeDlE, options) { if (!options) options = {} if (nEeDlE && nEeDlE.includes(',')) { const possibleAlias = nEeDlE.split(',').pop() if (aliased[possibleAlias]) nEeDlE = possibleAlias } if (aliased[nEeDlE]) { const result = aliased[nEeDlE] .map((alias) => _searchAll(alias, options)) .filter((value) => value) .pop() if (result) return result // const alias = aliasFor(nEeDlE) // if (alias) nEeDlE = alias } return _searchAll(nEeDlE, options) } function _searchAll(nEeDlE, options) { timer.start('flagger') if (!options) options = {} let value // console.log({aliased, nEeDlE, alias: aliased[nEeDlE]}) // console.log('\n\n') const NEEDLE = nEeDlE.toUpperCase() const needle = nEeDlE.toLowerCase() options.needle = needle // log('0', {level: 'cache'}) if (cache[needle]) return cache[needle] if (cache[NEEDLE]) return cache[NEEDLE] if (cache[nEeDlE]) return cache[nEeDlE] // log('1', {level: 'val'}) value = val(nEeDlE, options) || val(needle, options) || val(NEEDLE, options) if (value) return realValue(value, options) // log('2', {level: 'get'}) value = nodeFlags.get(nEeDlE) || nodeFlags.get(NEEDLE) || nodeFlags.get(needle) if (value) return realValue(value, options) // log('3', {level: 'env'}) value = findIn(nEeDlE, process.env) || findIn(needle, process.env) || findIn(NEEDLE, process.env) if (value) return realValue(value, options) // log('4', {level: 'global'}) value = findIn(nEeDlE, global) || findIn(needle, global) || findIn(NEEDLE, global) if (value) return realValue(value, options) // log('5', {level: 'argv minimalist'}) if (argv[needle]) return argv[needle] // log('6', {level: 'fallback argv'}) if (process.argv.includes(needle)) return true if (process.argv.includes(`--${needle}`)) return true if (process.argv.includes(`.env${needle}`)) return true // log('6', {level: 'fallback default'}) if (options && options.default) return options.default } // used to export a callable fn obj let flagger = searchAll // searches through the commandline arguments // check if it matches what we are searching for // string or array later function get(needle, options) { const defaults = { includeSlash: false, } options = Object.assign(defaults, options) const haystack = process.argv // console.log('SEARCH:', needle) for (let i = 0; i < haystack.length; i++) { const arg = haystack[i] if (!arg || !arg.includes) continue // because the location of the file being run is in the args if (!needle.includes('/') && !options.includeSlash && arg.includes('/')) { continue } if (arg.includes(needle) || needle.includes(arg)) { let argStripped = arg // remove prefixes if (arg.replace) argStripped = arg.replace('--', '').replace('env.', '') // check it if (argStripped.includes(needle) || needle.includes(argStripped)) { let argResult = argStripped // take value after `=` if (argStripped.includes && argStripped.includes('=')) argResult = argStripped.split('=').pop() // if it is an empty string // that is evaluated to false in a condition // if ('' === argStripped) return true // return argStripped // use result if (argResult === '') return true return argResult } } } // because webpack2 does not allow custom cli args if (!needle.includes('env')) return get(`env.${needle}`, haystack, options) return false } // @example // --html --template=./demo/index.html // // argVal('template') // -> argd // -> --template=./demo/index.html // -> template=./demo/index.html // -> ./demo/index.html // function val(search, options) { const arg = get(search, options) if (arg === true) return true if (!arg) return false return arg .replace('--', '') .replace(search, '') .replace('env.', '') .replace('=', '') } // or hasOwnProperty? // could be in other helper libs function findIn(prop, obj) { if (obj[prop]) return obj[prop] return null } function findAll(flags, cb = false) { const found = {} const allStr = IS.arrOf(flags, IS.str) const allObj = IS.arrOf(flags, IS.obj) // ['ca', 'eu'] if (allStr) { const names = flags flags = {} flags.names = names } else if (allObj) { return flags.forEach((flag) => findAll(flag, flag.cb || cb)) } // names: [{flag: ['run'], type: 'bool', default: false}] for (let i = 0; i < flags.names.length; i++) { let flag = flags.names[i] const names = flag.flag || flag parseAliases(names) // flag: 'run', type: 'bool', default: false if (typeof flag === 'string') { // use alias key if it exists const alias = aliasFor(flag) if (alias) flag = alias // console.log({alias, flag, aliased}, flagger(flag)) // console.log('\n\n\n') // add to object found[flag] = flagger(flag) continue } // 'run' if (typeof names === 'string') { found[names] = flagger(names, flag) continue } // flag: ['run'], type: 'bool', default: false if (IS.arr(names)) { names.forEach((sub) => { // use alias key if it exists const alias = aliasFor(sub) if (alias) sub = alias // add result to the object found[sub] = flagger(sub, flag) }) } } // log // .tags('flags') // .title('🚩 built for flags') // .color('italic') // .data(found) // .echo() // taylored for this specific environment // so if certain flags are added // there would be different properties if (cb) return cb(found) return found } function _realValue(value, options) { // console.log({value, options}) if (options && options.type) { const type = options.type timer.stop('flagger') if (type === 'bool' || type === 'boolean') { if (value === 'true') return true if (value === 'false') return true if (typeof value === 'string') { if (value.includes('true')) return true if (value.includes('false')) return false } if (value == 'true') return true if (value == 'false') return false return Boolean(value) } // be more specific if (type === 'arr' || type === 'array') { if (value && value.includes) { if (value.includes(',')) return value.split(',') if (typeof value === 'string') return [value] } else { return [value] } } } if (Number.isInteger(value)) return value + 0 if (value === 'true') return true if (value === 'false') return false if (typeof value === 'string') { if (value.includes('true')) return true if (value.includes('false')) return false } if (value == 'undefined') return undefined return value } // @TODO: // - [ ] flush out // - [x] bool // - [x] default undefined // - [ ] parse str // - [ ] safety to array function realValue(value, opts) { const {needle} = opts const val = _realValue(value, opts) cache[needle] = val return val } function decorate(obj = {}, flag, opts = {override: true}) { const {override} = opts const name = Object.keys(flag)[0] const val = flag[name] const valIsReal = val != undefined const valNotOnContext = (obj[name] == undefined) const valIsRealAndNotOnContext = valIsReal && valNotOnContext if (override || valIsRealAndNotOnContext) obj[name] = val return obj } decorate.obj = function(obj) { return decorate.bind(null, obj) } function findAndDecorate(flags, obj, opts = {override: true}) { return decorate(obj, flags, opts) } const flaggerObj = { aliased, addAliases, parseAliases, findAndDecorate, decorate, findAll, // @TODO: should be lowercasing props when checking searchAll, val, get, argv, minimist, // yargs, } // @TODO: // - [ ] optimize // - [ ] respect options for which vals to search through flagger = Object.assign(flagger, flaggerObj) module.exports = flagger