migrated code from ig-object/test.js...

Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
Alex A. Naanou 2020-06-14 03:10:19 +03:00
parent a23f2c1bc8
commit 167cbf1258
3 changed files with 309 additions and 0 deletions

257
argv.js Normal file
View File

@ -0,0 +1,257 @@
/**********************************************************************
*
*
*
**********************************************************************/
((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define)
(function(require){ var module={} // make module AMD/node compatible...
/*********************************************************************/
var object = require('ig-object')
//---------------------------------------------------------------------
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){
// ...
// }},
//
// command: function(){
// ...
// },
// ...
// }
//
// 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 =
module.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 } }
/**********************************************************************
* vim:set ts=4 sw=4 : */ return module })

28
package.json Normal file
View File

@ -0,0 +1,28 @@
{
"name": "ig-argv",
"version": "1.0.0",
"description": "simple argv parser",
"main": "argv.js",
"scripts": {
"test": "node ./test.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/flynx/argv.js.git"
},
"keywords": [
"javascript",
"argv",
"parser",
"commandline"
],
"author": "Alex A. Naanou <alex.nanou@gmail.com>",
"license": "BSD-3-Clause",
"bugs": {
"url": "https://github.com/flynx/argv.js/issues"
},
"homepage": "https://github.com/flynx/argv.js#readme",
"dependencies": {
"ig-object": "^5.0.8"
}
}

24
test.js Normal file
View File

@ -0,0 +1,24 @@
#!/usr/bin/env node
/**********************************************************************
*
*
*
**********************************************************************/
((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define)
(function(require){ var module={} // make module AMD/node compatible...
/*********************************************************************/
var colors = require('colors')
var object = require('ig-object')
var argv = require('./argv')
//---------------------------------------------------------------------
/**********************************************************************
* vim:set ts=4 sw=4 : */ return module })