mirror of
https://github.com/flynx/argv.js.git
synced 2025-10-29 10:50:06 +00:00
cleanup, testing and experimenting....
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
parent
679437a822
commit
0bb9c7b644
184
argv.js
184
argv.js
@ -16,6 +16,14 @@ module.OPTION_PATTERN = /^--?/
|
|||||||
module.COMMAND_PATTERN = /^[a-zA-Z]/
|
module.COMMAND_PATTERN = /^[a-zA-Z]/
|
||||||
|
|
||||||
|
|
||||||
|
module.STOP =
|
||||||
|
{doc: 'stop option processing'}
|
||||||
|
|
||||||
|
module.ERROR =
|
||||||
|
{doc: 'option processing error'}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//---------------------------------------------------------------------
|
//---------------------------------------------------------------------
|
||||||
|
|
||||||
Object.defineProperty(String.prototype, 'raw', {
|
Object.defineProperty(String.prototype, 'raw', {
|
||||||
@ -23,6 +31,19 @@ Object.defineProperty(String.prototype, 'raw', {
|
|||||||
return this.replace(/\x1b\[..?m/g, '') }, })
|
return this.replace(/\x1b\[..?m/g, '') }, })
|
||||||
|
|
||||||
|
|
||||||
|
var afterCallback = function(name){
|
||||||
|
var attr = '__after_'+ name
|
||||||
|
return function(func){
|
||||||
|
(this[attr] = this[attr] || []).push(func)
|
||||||
|
return this } }
|
||||||
|
|
||||||
|
|
||||||
|
var afterCallbackCall = function(name, context, ...args){
|
||||||
|
return (context['__after_'+ name] || [])
|
||||||
|
.forEach(function(func){
|
||||||
|
func.call(context, ...args) }) }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//---------------------------------------------------------------------
|
//---------------------------------------------------------------------
|
||||||
// basic argv parser...
|
// basic argv parser...
|
||||||
@ -227,16 +248,16 @@ function(spec){
|
|||||||
|
|
||||||
rest: argv,
|
rest: argv,
|
||||||
}
|
}
|
||||||
var other = []
|
var unhandled = []
|
||||||
while(argv.length > 0){
|
while(argv.length > 0){
|
||||||
var arg = argv.shift()
|
var arg = argv.shift()
|
||||||
var type = opt_pattern.test(arg) ?
|
var type = opt_pattern.test(arg) ?
|
||||||
'opt'
|
'opt'
|
||||||
: spec.__iscommand__(arg) ?
|
: spec.__iscommand__(arg) ?
|
||||||
'cmd'
|
'cmd'
|
||||||
: 'other'
|
: 'unhandled'
|
||||||
// options / commands...
|
// options / commands...
|
||||||
if(type != 'other'){
|
if(type != 'unhandled'){
|
||||||
// get handler...
|
// get handler...
|
||||||
var handler = spec.__gethandler__(arg).pop()
|
var handler = spec.__gethandler__(arg).pop()
|
||||||
|| spec.__unknown__
|
|| spec.__unknown__
|
||||||
@ -254,9 +275,9 @@ function(spec){
|
|||||||
arg,
|
arg,
|
||||||
argv)
|
argv)
|
||||||
continue }
|
continue }
|
||||||
// other...
|
// unhandled...
|
||||||
other.push(arg) }
|
unhandled.push(arg) }
|
||||||
return other } }
|
return unhandled } }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -287,12 +308,14 @@ object.Constructor('Parser', {
|
|||||||
|
|
||||||
|
|
||||||
// Handler API...
|
// Handler API...
|
||||||
// XXX should these be .getOptions(..) / .getCommands(..) ???
|
//
|
||||||
// Format:
|
// Format:
|
||||||
// [
|
// [
|
||||||
// [<keys>, <arg>, <doc>, <handler>],
|
// [<keys>, <arg>, <doc>, <handler>],
|
||||||
// ...
|
// ...
|
||||||
// ]
|
// ]
|
||||||
|
//
|
||||||
|
// XXX should these be .getOptions(..) / .getCommands(..) ???
|
||||||
options: function(...prefix){
|
options: function(...prefix){
|
||||||
var that = this
|
var that = this
|
||||||
prefix = prefix.length == 0 ?
|
prefix = prefix.length == 0 ?
|
||||||
@ -306,9 +329,10 @@ object.Constructor('Parser', {
|
|||||||
if(!opt.startsWith(prefix)){
|
if(!opt.startsWith(prefix)){
|
||||||
return }
|
return }
|
||||||
var [k, h] = that.getHandler(opt)
|
var [k, h] = that.getHandler(opt)
|
||||||
handlers[k] ?
|
h !== undefined
|
||||||
|
&& (handlers[k] ?
|
||||||
handlers[k][0].push(opt)
|
handlers[k][0].push(opt)
|
||||||
: (handlers[k] = [[opt], h.arg, h.doc || k.slice(1), h]) })
|
: (handlers[k] = [ [opt], h.arg, h.doc || k.slice(1), h ])) })
|
||||||
return Object.values(handlers) })
|
return Object.values(handlers) })
|
||||||
.flat(1)
|
.flat(1)
|
||||||
.map(function(e, i){ return [e, i] })
|
.map(function(e, i){ return [e, i] })
|
||||||
@ -346,17 +370,14 @@ object.Constructor('Parser', {
|
|||||||
|
|
||||||
|
|
||||||
// doc stuff...
|
// doc stuff...
|
||||||
// XXX revise naming...
|
|
||||||
helpColumnOffset: 3,
|
helpColumnOffset: 3,
|
||||||
helpColumnPrefix: '- ',
|
helpColumnPrefix: '- ',
|
||||||
|
|
||||||
// XXX these can be functions...
|
|
||||||
doc: undefined,
|
|
||||||
usage: '$SCRIPTNAME [OPTIONS]',
|
usage: '$SCRIPTNAME [OPTIONS]',
|
||||||
|
doc: undefined,
|
||||||
examples: undefined,
|
examples: undefined,
|
||||||
footer: undefined,
|
footer: undefined,
|
||||||
|
|
||||||
// XXX better name...
|
|
||||||
alignColumns: function(a, b, ...rest){
|
alignColumns: function(a, b, ...rest){
|
||||||
var opts_width = this.helpColumnOffset || 4
|
var opts_width = this.helpColumnOffset || 4
|
||||||
var prefix = this.helpColumnPrefix || ''
|
var prefix = this.helpColumnPrefix || ''
|
||||||
@ -368,86 +389,80 @@ object.Constructor('Parser', {
|
|||||||
: [a] },
|
: [a] },
|
||||||
|
|
||||||
// Builtin options/commands...
|
// Builtin options/commands...
|
||||||
// XXX do we need to encapsulate this???
|
|
||||||
// ...on one hand encapsulation is cleaner but on the other it:
|
|
||||||
// 1) splits the option spec and parser config
|
|
||||||
// 2) forces the use of two mechanisms for option spec and
|
|
||||||
// parser config...
|
|
||||||
// XXX need these to be sortable/groupable -- keep help at top...
|
|
||||||
'-h': '-help',
|
'-h': '-help',
|
||||||
'-help': {
|
'-help': {
|
||||||
doc: 'print this message and exit.',
|
doc: 'print this message and exit.',
|
||||||
priority: 99,
|
priority: 99,
|
||||||
// XXX argv is first for uniformity with .__call__(..) -- need
|
|
||||||
// the two to be interchangeable...
|
|
||||||
// ...an alternative would keep it last, but this feels more fragile...
|
|
||||||
handler: function(argv, key, value){
|
handler: function(argv, key, value){
|
||||||
var that = this
|
var that = this
|
||||||
var x
|
|
||||||
console.log([
|
var expandVars = function(str){
|
||||||
`Usage: ${
|
return str
|
||||||
typeof(this.usage) == 'function' ?
|
.replace(/\$SCRIPTNAME/g, that.scriptName) }
|
||||||
this.usage(this)
|
var getValue = function(name){
|
||||||
: this.usage }`,
|
return that[name] ?
|
||||||
// doc...
|
['', typeof(that[name]) == 'function' ?
|
||||||
...(this.doc ?
|
that[name]()
|
||||||
['', typeof(this.doc) == 'function' ?
|
: that[name]]
|
||||||
this.__doc__()
|
: [] }
|
||||||
: this.doc]
|
var section = function(title, items){
|
||||||
: []),
|
items = items instanceof Array ? items : [items]
|
||||||
|
return items.length > 0 ?
|
||||||
|
['', title +':', ...items]
|
||||||
|
: [] }
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
expandVars([
|
||||||
|
`Usage: ${ getValue('usage').join('') }`,
|
||||||
|
// doc (optional)...
|
||||||
|
...getValue('doc'),
|
||||||
// options...
|
// options...
|
||||||
'',
|
...section('Options',
|
||||||
'Options:',
|
this.options()
|
||||||
...(this.options()
|
|
||||||
.map(function([opts, arg, doc]){
|
.map(function([opts, arg, doc]){
|
||||||
return [opts.join(' | -') +' '+ (arg || ''), doc] })),
|
return [ opts.join(' | -') +' '+ (arg || ''), doc] })),
|
||||||
// commands...
|
// commands (optional)...
|
||||||
...(((x = this.commands()) && x.length > 0) ?
|
...section('Commands',
|
||||||
['', 'Commands:',
|
this.commands()
|
||||||
...x.map(function([cmd, _, doc]){
|
.map(function([cmd, _, doc]){
|
||||||
return [
|
return [
|
||||||
cmd
|
cmd
|
||||||
.map(function(cmd){
|
.map(function(cmd){ return cmd.slice(1)})
|
||||||
return cmd.slice(1)})
|
|
||||||
.join(' | '),
|
.join(' | '),
|
||||||
doc] })]
|
doc] })),
|
||||||
: []),
|
// examples (optional)...
|
||||||
// examples...
|
...section('Examples',
|
||||||
...(this.examples ?
|
|
||||||
['', 'Examples:', ...(
|
|
||||||
this.examples instanceof Array ?
|
this.examples instanceof Array ?
|
||||||
this.examples
|
this.examples
|
||||||
.map(function(e){
|
.map(function(e){
|
||||||
return e instanceof Array ? e : [e] })
|
return e instanceof Array ? e : [e] })
|
||||||
: this.examples == 'function' ?
|
: getValue('examples') ),
|
||||||
this.examples(this)
|
// footer (optional)...
|
||||||
: this.examples )]
|
...getValue('footer') ]
|
||||||
: []),
|
// expand/align columns...
|
||||||
// footer...
|
|
||||||
...(this.footer?
|
|
||||||
['', typeof(this.footer) == 'function' ?
|
|
||||||
this.footer(this)
|
|
||||||
: this.footer]
|
|
||||||
: []) ]
|
|
||||||
.map(function(e){
|
.map(function(e){
|
||||||
return e instanceof Array ?
|
return e instanceof Array ?
|
||||||
that.alignColumns(...e
|
// NOTE: we need to expandVars(..) here so as to
|
||||||
.map(function(s){
|
// be able to calculate actual widths...
|
||||||
return s.replace(/\$SCRIPTNAME/g, that.scriptName) }))
|
that.alignColumns(...e.map(expandVars))
|
||||||
// indent lists...
|
.map(function(s){ return '\t'+ s })
|
||||||
.map(function(s){
|
|
||||||
return '\t'+ s })
|
|
||||||
: e })
|
: e })
|
||||||
.flat()
|
.flat()
|
||||||
.join('\n')
|
.join('\n')))
|
||||||
.replace(/\$SCRIPTNAME/g, this.scriptName))
|
|
||||||
|
|
||||||
// XXX should we explicitly exit here or in the runner???
|
// XXX should we explicitly exit here or in the runner???
|
||||||
process.exit() }},
|
return module.STOP }},
|
||||||
|
|
||||||
unknownOption: function(key){
|
|
||||||
|
unknownOption: function(_, key){
|
||||||
console.error('Unknown option:', key)
|
console.error('Unknown option:', key)
|
||||||
process.exit(1) },
|
return module.ERROR },
|
||||||
|
|
||||||
|
|
||||||
|
// post parsing callbacks...
|
||||||
|
then: afterCallback('parsing'),
|
||||||
|
stop: afterCallback('stop'),
|
||||||
|
error: afterCallback('error'),
|
||||||
|
|
||||||
|
|
||||||
// XXX need to unify this with handler as much as possible to make
|
// XXX need to unify this with handler as much as possible to make
|
||||||
@ -458,6 +473,9 @@ object.Constructor('Parser', {
|
|||||||
// - script
|
// - script
|
||||||
// ...these should be either avoided or "inherited"
|
// ...these should be either avoided or "inherited"
|
||||||
__call__: function(context, argv){
|
__call__: function(context, argv){
|
||||||
|
var that = this
|
||||||
|
var nested = false
|
||||||
|
|
||||||
// nested command handler...
|
// nested command handler...
|
||||||
// XXX the condition is a bit too strong...
|
// XXX the condition is a bit too strong...
|
||||||
if(context instanceof Parser){
|
if(context instanceof Parser){
|
||||||
@ -465,6 +483,7 @@ object.Constructor('Parser', {
|
|||||||
this.script = this.scriptName =
|
this.script = this.scriptName =
|
||||||
context.scriptName +' '+ arguments[2]
|
context.scriptName +' '+ arguments[2]
|
||||||
this.argv = [context.scriptName, this.scriptName, ...argv]
|
this.argv = [context.scriptName, this.scriptName, ...argv]
|
||||||
|
nested = true
|
||||||
|
|
||||||
// root parser...
|
// root parser...
|
||||||
} else {
|
} else {
|
||||||
@ -481,16 +500,16 @@ object.Constructor('Parser', {
|
|||||||
|
|
||||||
var opt_pattern = this.optionPattern
|
var opt_pattern = this.optionPattern
|
||||||
|
|
||||||
var other = []
|
var unhandled = []
|
||||||
while(argv.length > 0){
|
while(argv.length > 0){
|
||||||
var arg = argv.shift()
|
var arg = argv.shift()
|
||||||
var type = opt_pattern.test(arg) ?
|
var type = opt_pattern.test(arg) ?
|
||||||
'opt'
|
'opt'
|
||||||
: this.isCommand(arg) ?
|
: this.isCommand(arg) ?
|
||||||
'cmd'
|
'cmd'
|
||||||
: 'other'
|
: 'unhandled'
|
||||||
// options / commands...
|
// options / commands...
|
||||||
if(type != 'other'){
|
if(type != 'unhandled'){
|
||||||
// get handler...
|
// get handler...
|
||||||
var handler = this.getHandler(arg).pop()
|
var handler = this.getHandler(arg).pop()
|
||||||
|| this.unknownOption
|
|| this.unknownOption
|
||||||
@ -499,17 +518,28 @@ object.Constructor('Parser', {
|
|||||||
argv.shift()
|
argv.shift()
|
||||||
: undefined
|
: undefined
|
||||||
// run handler...
|
// run handler...
|
||||||
;(typeof(handler) == 'function' ?
|
var res = (typeof(handler) == 'function' ?
|
||||||
handler
|
handler
|
||||||
: handler.handler)
|
: handler.handler)
|
||||||
.call(this,
|
.call(this,
|
||||||
argv,
|
argv,
|
||||||
arg,
|
arg,
|
||||||
...(handler.arg ? [value] : []))
|
...(handler.arg ? [value] : []))
|
||||||
|
// handle .STOP / .ERROR
|
||||||
|
if(res === module.STOP || res === module.ERROR){
|
||||||
|
afterCallbackCall(
|
||||||
|
res === module.STOP ? 'stop' : 'error',
|
||||||
|
this, arg)
|
||||||
|
return nested ?
|
||||||
|
res
|
||||||
|
: this }
|
||||||
continue }
|
continue }
|
||||||
// other...
|
// unhandled...
|
||||||
other.push(arg) }
|
unhandled.push(arg) }
|
||||||
return other },
|
|
||||||
|
// post handlers...
|
||||||
|
afterCallbackCall('parsing', this, unhandled)
|
||||||
|
return this },
|
||||||
|
|
||||||
__init__: function(spec){
|
__init__: function(spec){
|
||||||
Object.assign(this, spec)
|
Object.assign(this, spec)
|
||||||
|
|||||||
19
test.js
19
test.js
@ -38,15 +38,26 @@ argv.Parser({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
// XXX this for some reason breaks...
|
// XXX dead-end alias...
|
||||||
//'@test': argv.Parser({
|
'-d': '-dead-end',
|
||||||
//}),
|
|
||||||
|
'@test': argv.Parser({
|
||||||
|
}),
|
||||||
|
|
||||||
'@nested': argv.Parser({
|
'@nested': argv.Parser({
|
||||||
doc: 'nested parser.',
|
doc: 'nested parser.',
|
||||||
|
|
||||||
|
'@nested': argv.Parser({
|
||||||
|
doc: 'nested nested parser.',
|
||||||
}),
|
}),
|
||||||
})
|
}),
|
||||||
|
})
|
||||||
|
.then(function(){
|
||||||
|
console.log('DONE') })
|
||||||
|
.stop(function(){
|
||||||
|
console.log('STOP') })
|
||||||
|
.error(function(){
|
||||||
|
console.log('ERROR') })
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user