From f7eb790f413bd227b2c2370f1ea3bcbdb8721ed2 Mon Sep 17 00:00:00 2001 From: "Alex A. Naanou" Date: Sun, 14 Jun 2020 04:29:27 +0300 Subject: [PATCH] split out argv parsing into ig-argv + some tweaks... Signed-off-by: Alex A. Naanou --- README.md | 31 +++++-- object.js | 21 +++++ package.json | 5 +- test.js | 253 +-------------------------------------------------- 4 files changed, 52 insertions(+), 258 deletions(-) diff --git a/README.md b/README.md index efb0cbc..e7dc612 100755 --- a/README.md +++ b/README.md @@ -835,13 +835,28 @@ normalizeTextIndent(..) This ignores `object.LEADING_TABS` and `leading_tabs` is 0 by default. +### `deepKeys(..)` + +``` +deepKeys() + -> +``` + +``` +deepKeys(, ) + -> +``` + +This is like `Object.keys(..)` but will get the keys from the whole +prototype chain or until `` if given. + ### `match(..)` Test if the two objects match in attributes and attribute values ``` -match(base, obj) - -> bool +match(, ) + -> ``` This relies on first level object structure to match the input object, for @@ -857,8 +872,8 @@ or: Non-strict match ``` -match(base, obj, true) - -> bool +match(, true) + -> ``` Like the default case but uses _equality_ instead of _identity_ to match @@ -868,12 +883,12 @@ values. ### `matchPartial(..)` ``` -match(base, obj) - -> bool +matchPartial(, ) + -> // non-strict version... -match(base, obj, true) - -> bool +matchPartial(, , true) + -> ``` Like `.match(..)` but will check for a partial match, i.e. when `obj` is diff --git a/object.js b/object.js index aa329c1..1af65c5 100755 --- a/object.js +++ b/object.js @@ -103,6 +103,27 @@ function(text, tab_size, leading_tabs){ return module.normalizeIndent(text, tab_size, leading_tabs || 0) } +// Get keys from prototype chain... +// +// deepKeys(obj) +// deepKeys(obj, stop) +// -> keys +// +// +// NOTE: this is like Object.keys(..) but will get keys for all levels +// till stop if given... +// +// XXX should we add this to Object??? +var deepKeys = +module.deepKeys = +function(obj, stop){ + var res = [] + while(obj !== stop && obj != null){ + res.push(Object.keys(obj)) + obj = obj.__proto__ } + return [...(new Set(res.flat()))] } + + // Match two objects... // // match(a, b) diff --git a/package.json b/package.json index 7077823..2be819c 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ig-object", - "version": "5.0.8", + "version": "5.0.9", "description": "", "main": "object.js", "scripts": { @@ -27,6 +27,7 @@ "homepage": "https://github.com/flynx/object.js#readme", "devDependencies": { "colors": "^1.4.0", - "c8": "*" + "c8": "*", + "ig-argv": "*" } } diff --git a/test.js b/test.js index 8619136..50ce329 100755 --- a/test.js +++ b/test.js @@ -43,6 +43,7 @@ /*********************************************************************/ var colors = require('colors') +var argv = require('ig-argv') var object = require('./object') @@ -71,15 +72,6 @@ Object.defineProperty(String.prototype, 'raw', { return this.replace(/\x1b\[..?m/g, '') }, }) -// get all keys accessible from object... -var deepKeys = function(obj, stop){ - var res = [] - while(obj !== stop && obj != null){ - res.push(Object.keys(obj)) - obj = obj.__proto__ } - return [...(new Set(res.flat()))] } - - // compare two arrays by items... var arrayCmp = function(a, b){ var ka = Object.keys(a) @@ -115,241 +107,6 @@ var instances = function(obj){ -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -module.OPTION_PATTERN = /^--?/ -module.COMMAND_PATTERN = /^[a-zA-Z]/ - -// basic argv parser... -// -// Format: -// { -// // alias... -// v: 'verbose', -// // handler... -// verbose: function(opt, rest){ -// ... -// }, -// -// t: 'test', -// test: { -// doc: 'test option.', -// arg: 'VALUE', -// handler: function(value, opt, rest){ -// ... -// }}, -// -// ... -// } -// -// XXX add features: -// - option groups -- nested specs... -// - arg value type conversion??? -// - make this a constructor??? -// - extend this to support command calling... -// XXX do we handle = for options with values??? -// XXX move this to it's own lib... -// argv-handler -// ig-argv -// ... -// XXX need better test processing: -// - line breaks -// - ... -var ArgvParser = function(spec){ - // spec defaults... - // NOTE: this is intentionally not dynamic... - spec = Object.assign({ - // builtin options... - '-h': '-help', - // XXX revise... - '-help': { - doc: 'print this message and exit.', - handler: function(){ - var spec = this.spec - var that = this - var x - console.log([ - `Usage: ${ - typeof(spec.__usage__) == 'function' ? - spec.__usage__.call(this) - : spec.__usage__ }`, - // doc... - ...(spec.__doc__ ? - ['', typeof(spec.__doc__) == 'function' ? - spec.__doc__() - : spec.__doc__] - : []), - // options... - '', - 'Options:', - ...(spec.__getoptions__() - .map(function([opts, arg, doc]){ - return [opts.join(' | -') +' '+ (arg || ''), doc] })), - // commands... - ...(((x = spec.__getcommands__()) && x.length > 0) ? - ['', 'Commands:', - ...x.map(function([cmd, _, doc]){ - return [cmd.join(' | '), doc] })] - : []), - // examples... - ...(this.spec.__examples__ ? - ['', 'Examples:', ...( - this.spec.__examples__ instanceof Array ? - spec.__examples__ - .map(function(e){ - return e instanceof Array ? e : [e] }) - : spec.__examples__(this) )] - : []), - // footer... - ...(this.spec.__footer__? - ['', typeof(this.spec.__footer__) == 'function' ? - spec.__footer__(this) - : spec.__footer__] - : []) ] - .map(function(e){ - return e instanceof Array ? - spec.__align__(...e - .map(function(s){ - return s.replace(/\$scriptname/g, that.scriptname) })) - // indent lists... - .map(function(s){ - return '\t'+ s }) - : e }) - .flat() - .join('\n') - .replace(/\$scriptname/g, this.scriptname)) - - process.exit() }}, - - // special values and methods... - __pre_check__: true, - __opt_pattern__: module.OPTION_PATTERN, - __cmd_pattern__: module.COMMAND_PATTERN, - __opts_width__: 3, - __doc_prefix__: '- ', - - // these is run in the same context as the handlers... (XXX ???) - __align__: function(a, b, ...rest){ - var opts_width = this.__opts_width__ || 4 - var prefix = this.__doc_prefix__ || '' - b = [b, ...rest].join('\n'+ ('\t'.repeat(opts_width+1) + ' '.repeat(prefix.length))) - return b ? - (a.raw.length < opts_width*8 ? - [a +'\t'.repeat(opts_width - Math.floor(a.raw.length/8))+ prefix + b] - : [a, '\t'.repeat(opts_width)+ prefix + b]) - : [a] }, - - __usage__: function(){ - return `${ this.scriptname } [OPTIONS]` }, - __doc__: undefined, - __examples__: undefined, - __footer__: undefined, - - __unknown__: function(key){ - console.error('Unknown option:', key) - process.exit(1) }, - - // these are run in the context of spec... - __getoptions__: function(...pattern){ - var that = this - pattern = pattern.length == 0 ? - [this.__opt_pattern__ - || module.OPTION_PATTERN] - : pattern - return pattern - .map(function(pattern){ - var handlers = {} - Object.keys(that) - .forEach(function(opt){ - // skip special methods... - if(/^__.*__$/.test(opt) - || !pattern.test(opt)){ - return } - var [k, h] = that.__gethandler__(opt) - handlers[k] ? - handlers[k][0].push(opt) - : (handlers[k] = [[opt], h.arg, h.doc || k, h]) }) - return Object.values(handlers) }) - .flat(1) }, - __iscommand__: function(str){ - return (this.__cmd_pattern__ - || module.COMMAND_PATTERN) - .test(str) - && str in this }, - __getcommands__: function(){ - return this.__getoptions__( - this.__cmd_pattern__ - || module.COMMAND_PATTERN) }, - __gethandler__: function(key){ - key = key.replace( - this.__opt_pattern__ - || module.OPTION_PATTERN, - '-') - var seen = new Set([key]) - while(key in this - && typeof(this[key]) == typeof('str')){ - key = this[key] - // check for loops... - if(seen.has(key)){ - throw Error('Option loop detected: '+ ([...seen, key].join(' -> '))) } - seen.add(key) } - return [key, this[key]] }, - }, spec) - - // sanity check -- this will detect argument loops for builtin opts - // and commands... - spec.__pre_check__ - && spec.__getoptions__( - spec.__opt_pattern__ || module.OPTION_PATTERN, - spec.__cmd_pattern__ || module.COMMAND_PATTERN) - - return function(argv){ - var opt_pattern = spec.__opt_pattern__ - || module.OPTION_PATTERN - argv = argv.slice() - var context = { - spec: spec, - argv: argv.slice(), - - interpreter: argv.shift(), - script: argv[0], - scriptname: argv.shift().split(/[\\\/]/).pop(), - - rest: argv, - } - var other = [] - while(argv.length > 0){ - var arg = argv.shift() - var type = opt_pattern.test(arg) ? - 'opt' - : spec.__iscommand__(arg) ? - 'cmd' - : 'other' - // options / commands... - if(type != 'other'){ - // get handler... - var handler = spec.__gethandler__(arg).pop() - || spec.__unknown__ - // get option value... - var value = (handler.arg && !opt_pattern.test(argv[0])) ? - argv.shift() - : undefined - // run handler... - ;(typeof(handler) == 'function' ? - handler - : handler.handler) - .call(context, - // pass value... - ...(handler.arg ? [value] : []), - arg, - argv) - continue } - // other... - other.push(arg) } - return other } } - - - //--------------------------------------------------------------------- // Tests... @@ -752,7 +509,7 @@ module.tests = { methods: function(assert, setup){ instances(setup) .forEach(function([k, o]){ - deepKeys(o) + object.deepKeys(o) .forEach(function(m){ typeof(o[m]) == 'function' // skip special methods... @@ -762,7 +519,7 @@ module.tests = { constructor_methods: function(assert, setup){ constructors(setup) .forEach(function([k, O]){ - deepKeys(O) + object.deepKeys(O) .forEach(function(m){ typeof(O[m]) == 'function' // skip special methods... @@ -1052,7 +809,7 @@ if(typeof(__filename) != 'undefined' // parse args... var chains = - ArgvParser({ + argv.ArgvParser({ // doc... __usage__: `$scriptname [OPTIONS] [CHAIN] ...`, __doc__: object.normalizeTextIndent( @@ -1136,7 +893,7 @@ if(typeof(__filename) != 'undefined' doc: 'test command...' }), // XXX need to make this nestable... - 'nested': ArgvParser({ }), + 'nested': argv.ArgvParser({ }), })(process.argv) // run the tests...