split out argv parsing into ig-argv + some tweaks...

Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
Alex A. Naanou 2020-06-14 04:29:27 +03:00
parent c700ec3812
commit f7eb790f41
4 changed files with 52 additions and 258 deletions

View File

@ -835,13 +835,28 @@ normalizeTextIndent(..)
This ignores `object.LEADING_TABS` and `leading_tabs` is 0 by default.
### `deepKeys(..)`
```
deepKeys(<obj>)
-> <keys>
```
```
deepKeys(<obj>, <stop>)
-> <keys>
```
This is like `Object.keys(..)` but will get the keys from the whole
prototype chain or until `<stop>` if given.
### `match(..)`
Test if the two objects match in attributes and attribute values
```
match(base, obj)
-> bool
match(<base>, <obj>)
-> <bool>
```
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(<base., <obj>, true)
-> <bool>
```
Like the default case but uses _equality_ instead of _identity_ to match
@ -868,12 +883,12 @@ values.
### `matchPartial(..)`
```
match(base, obj)
-> bool
matchPartial(<base>, <obj>)
-> <bool>
// non-strict version...
match(base, obj, true)
-> bool
matchPartial(<base>, <obj>, true)
-> <bool>
```
Like `.match(..)` but will check for a partial match, i.e. when `obj` is

View File

@ -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)

View File

@ -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": "*"
}
}

253
test.js
View File

@ -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...