mirror of
https://github.com/flynx/argv.js.git
synced 2025-10-29 10:50:06 +00:00
lots of fixes, tweaks and cleanup...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
parent
29a6c44923
commit
6c18c1b3a6
171
argv.js
171
argv.js
@ -30,7 +30,6 @@ var object = require('ig-object')
|
|||||||
module.STOP =
|
module.STOP =
|
||||||
{doc: 'stop option processing, triggers .stop(..) handlers'}
|
{doc: 'stop option processing, triggers .stop(..) handlers'}
|
||||||
|
|
||||||
// XXX rename???
|
|
||||||
module.THEN =
|
module.THEN =
|
||||||
{doc: 'break option processing, triggers .then(..) handlers'}
|
{doc: 'break option processing, triggers .then(..) handlers'}
|
||||||
|
|
||||||
@ -42,8 +41,8 @@ module.ERROR =
|
|||||||
//---------------------------------------------------------------------
|
//---------------------------------------------------------------------
|
||||||
// helpers...
|
// helpers...
|
||||||
|
|
||||||
// XXX do we need to remove handlers???
|
// XXX does this need to be an event???
|
||||||
// XXX does this need to be an event constructor???
|
// XXX doc...
|
||||||
var afterCallback = function(name){
|
var afterCallback = function(name){
|
||||||
var attr = '__after_'+ name
|
var attr = '__after_'+ name
|
||||||
return function(func){
|
return function(func){
|
||||||
@ -86,11 +85,15 @@ var afterCallback = function(name){
|
|||||||
//
|
//
|
||||||
// arg: 'VALUE|key',
|
// arg: 'VALUE|key',
|
||||||
//
|
//
|
||||||
|
// type: 'int',
|
||||||
|
//
|
||||||
// env: 'VALUE',
|
// env: 'VALUE',
|
||||||
//
|
//
|
||||||
// default: 123,
|
// default: 123,
|
||||||
//
|
//
|
||||||
// required: true,
|
// required: false,
|
||||||
|
//
|
||||||
|
// valueRequired: false,
|
||||||
//
|
//
|
||||||
// handler: function(opts, key, value){
|
// handler: function(opts, key, value){
|
||||||
// ...
|
// ...
|
||||||
@ -157,21 +160,26 @@ var afterCallback = function(name){
|
|||||||
// NOTE: essentially this parser is a very basic stack language...
|
// NOTE: essentially this parser is a very basic stack language...
|
||||||
// XXX can we implement the whole thing directly as a stack language???
|
// XXX can we implement the whole thing directly as a stack language???
|
||||||
//
|
//
|
||||||
// XXX add -about flag???
|
// XXX need to handle values (and return values) of nested parsers correctly...
|
||||||
// XXX we should be able to set .scriptName by hand...
|
// XXX can we add more prefexes, like '+' and the like???
|
||||||
// XXX might be a good idea to read metadata from package.json
|
// XXX might be a good idea to read metadata from package.json
|
||||||
// XXX handle option types???
|
// XXX should -help should work for any command?
|
||||||
// XXX --help should work for any command and not just for the nested
|
// ...now only works for nested parsers...
|
||||||
// parser commands... (???)
|
|
||||||
// ...not sure how to implement this...
|
|
||||||
// .....or should it be the responsibility of the user defining
|
|
||||||
// the command???
|
|
||||||
// XXX should we handle <scriptName>-<command> script calls???
|
// XXX should we handle <scriptName>-<command> script calls???
|
||||||
// XXX should .options(..), .commands(..) and .handler(..) be:
|
// XXX might be a good idea to use exceptions for ERROR...
|
||||||
// .getOptions(..), .getCommands(..) and .getHandler(..) respectively???
|
|
||||||
var Parser =
|
var Parser =
|
||||||
module.Parser =
|
module.Parser =
|
||||||
object.Constructor('Parser', {
|
object.Constructor('Parser', {
|
||||||
|
typeHandlers: {
|
||||||
|
int: parseInt,
|
||||||
|
float: parseFloat,
|
||||||
|
number: function(v){ return new Number(v) },
|
||||||
|
string: function(v){ return v.toString() },
|
||||||
|
date: function(v){ return new Date(v) },
|
||||||
|
// XXX ...
|
||||||
|
},
|
||||||
|
|
||||||
|
}, {
|
||||||
// config...
|
// config...
|
||||||
splitOptions: true,
|
splitOptions: true,
|
||||||
optionPrefix: '-',
|
optionPrefix: '-',
|
||||||
@ -192,13 +200,11 @@ object.Constructor('Parser', {
|
|||||||
|
|
||||||
// output...
|
// output...
|
||||||
//
|
//
|
||||||
// XXX how do we return something from these???
|
|
||||||
// ...closure?? ...globals?
|
|
||||||
print: function(...args){
|
print: function(...args){
|
||||||
console.log(...args)
|
console.log(...args)
|
||||||
return this },
|
return this },
|
||||||
printError: function(...args){
|
printError: function(...args){
|
||||||
console.error(...args)
|
console.error(this.scriptName+': Error:', ...args)
|
||||||
return this },
|
return this },
|
||||||
|
|
||||||
|
|
||||||
@ -211,9 +217,6 @@ object.Constructor('Parser', {
|
|||||||
// ]
|
// ]
|
||||||
//
|
//
|
||||||
// XXX do we need to output <doc> here???
|
// XXX do we need to output <doc> here???
|
||||||
// ...if it's used only in -help then it would be simpler to
|
|
||||||
// remove it from here and get everything in formDoc(..), same
|
|
||||||
// goes for <arg>...
|
|
||||||
options: function(...prefix){
|
options: function(...prefix){
|
||||||
var that = this
|
var that = this
|
||||||
prefix = prefix.length == 0 ?
|
prefix = prefix.length == 0 ?
|
||||||
@ -281,7 +284,9 @@ object.Constructor('Parser', {
|
|||||||
key = this.optionInputPattern.test(key) ?
|
key = this.optionInputPattern.test(key) ?
|
||||||
key.replace(this.optionInputPattern, this.optionPrefix+'$1')
|
key.replace(this.optionInputPattern, this.optionPrefix+'$1')
|
||||||
: key.replace(this.commandInputPattern, this.commandPrefix+'$1')
|
: key.replace(this.commandInputPattern, this.commandPrefix+'$1')
|
||||||
var seen = new Set([key])
|
// quote '*'...
|
||||||
|
key = key.replace(/^(.)\*$/, '$1\\*')
|
||||||
|
var seen = new Set()
|
||||||
while(key in this
|
while(key in this
|
||||||
&& typeof(this[key]) == typeof('str')){
|
&& typeof(this[key]) == typeof('str')){
|
||||||
key = this[key]
|
key = this[key]
|
||||||
@ -305,22 +310,16 @@ object.Constructor('Parser', {
|
|||||||
// doc config...
|
// doc config...
|
||||||
helpColumnOffset: 3,
|
helpColumnOffset: 3,
|
||||||
helpColumnPrefix: '- ',
|
helpColumnPrefix: '- ',
|
||||||
//helpOptionSeparator: ' | ',
|
|
||||||
helpArgumentSeparator: ', ',
|
helpArgumentSeparator: ', ',
|
||||||
//helpValueSeparator: '=',
|
helpValueSeparator: '=',
|
||||||
helpValueSeparator: ' ',
|
|
||||||
|
|
||||||
// doc sections...
|
// doc sections...
|
||||||
// XXX might be a good idea to read these from package.json by default...
|
|
||||||
// XXX
|
|
||||||
author: undefined,
|
author: undefined,
|
||||||
license: undefined,
|
license: undefined,
|
||||||
usage: '$SCRIPTNAME [OPTIONS]',
|
usage: '$SCRIPTNAME [OPTIONS]',
|
||||||
doc: undefined,
|
doc: undefined,
|
||||||
// XXX test this with string value...
|
|
||||||
examples: undefined,
|
examples: undefined,
|
||||||
// XXX add license and version info...
|
//footer: '$SCRIPTNAME ($VERSION / $LICENSE) by $AUTHOR',
|
||||||
//footer: '$SCRIPTNAME ($VERSION) by $AUTHOR',
|
|
||||||
footer: undefined,
|
footer: undefined,
|
||||||
|
|
||||||
// XXX should wrap long lines...
|
// XXX should wrap long lines...
|
||||||
@ -498,13 +497,14 @@ object.Constructor('Parser', {
|
|||||||
// Dynamic options / Dynamic commands
|
// Dynamic options / Dynamic commands
|
||||||
// .section_doc is a string or array
|
// .section_doc is a string or array
|
||||||
//
|
//
|
||||||
// XXX need a way to quote '*' to make it usable in flags/commands...
|
// NOTE: to explicitly handle '-*' option or '*' command define handlers
|
||||||
|
// for them under '-\\*' and '@\\*' respectively.
|
||||||
'-*': {
|
'-*': {
|
||||||
//key: '-*',
|
//key: '-*',
|
||||||
doc: false,
|
doc: false,
|
||||||
//section_doc: ...,
|
//section_doc: ...,
|
||||||
handler: function(_, key){
|
handler: function(_, key){
|
||||||
this.printError('Unknown '+ (key.startsWith('-') ? 'option:' : 'command:'), key)
|
this.printError('unknown '+ (key.startsWith('-') ? 'option:' : 'command:'), key)
|
||||||
return module.ERROR } },
|
return module.ERROR } },
|
||||||
'@*': '-*',
|
'@*': '-*',
|
||||||
|
|
||||||
@ -512,7 +512,6 @@ object.Constructor('Parser', {
|
|||||||
// Default handler action...
|
// Default handler action...
|
||||||
//
|
//
|
||||||
// This is called when .handler is not set...
|
// This is called when .handler is not set...
|
||||||
//
|
|
||||||
handlerDefault: function(handler, rest, key, value){
|
handlerDefault: function(handler, rest, key, value){
|
||||||
key = (handler.arg
|
key = (handler.arg
|
||||||
&& handler.arg
|
&& handler.arg
|
||||||
@ -521,11 +520,9 @@ object.Constructor('Parser', {
|
|||||||
.trim())
|
.trim())
|
||||||
// get the final key...
|
// get the final key...
|
||||||
|| this.handler(key)[0].slice(1)
|
|| this.handler(key)[0].slice(1)
|
||||||
this[key] =
|
// if value not given set true...
|
||||||
arguments.length < 4 ?
|
this[key] = arguments.length < 4 ?
|
||||||
true
|
this.handleArgumentValue(handler, true)
|
||||||
: value === undefined ?
|
|
||||||
handler.default || true
|
|
||||||
: value
|
: value
|
||||||
return this },
|
return this },
|
||||||
|
|
||||||
@ -534,30 +531,24 @@ object.Constructor('Parser', {
|
|||||||
//
|
//
|
||||||
// If this is false/undefined value is passed to the handler as-is...
|
// If this is false/undefined value is passed to the handler as-is...
|
||||||
//
|
//
|
||||||
// Example:
|
// NOTE: to disable this functionality just set:
|
||||||
// typeHandler: {
|
// handleArgumentValue: false
|
||||||
// int: parseInt,
|
handleArgumentValue: function(handler, value){
|
||||||
// float: parseFloat,
|
var convert =
|
||||||
// number: function(v){ return new Number(v) },
|
typeof(handler.type) == 'function' ?
|
||||||
// string: function(v){ return v.toString() },
|
handler.type
|
||||||
// ...
|
: (this.typeHandlers
|
||||||
// },
|
|| this.constructor.typeHandlers
|
||||||
// handleArgumentValue: function(handler, value){
|
|| {})[handler.type]
|
||||||
// var convert = typeof(handler.type) == 'function' ?
|
return convert ?
|
||||||
// handler.type
|
convert.call(this, value)
|
||||||
// : this.typeHandler[handler.type]
|
: value },
|
||||||
// return convert ?
|
|
||||||
// convert(value)
|
|
||||||
// : value },
|
|
||||||
//
|
|
||||||
// XXX should we define a handler.type handler???
|
|
||||||
handleArgumentValue: false,
|
|
||||||
|
|
||||||
// Handle error exit...
|
// Handle error exit...
|
||||||
//
|
//
|
||||||
// If this is set to false Parser will not call process.exit(..) on
|
// If this is set to false Parser will not call process.exit(..) on
|
||||||
// error...
|
// error...
|
||||||
handleErrorExit: function(arg){
|
handleErrorExit: function(arg, reason){
|
||||||
typeof(process) != 'unhandled'
|
typeof(process) != 'unhandled'
|
||||||
&& process.exit(1) },
|
&& process.exit(1) },
|
||||||
|
|
||||||
@ -598,7 +589,7 @@ object.Constructor('Parser', {
|
|||||||
// handler...
|
// handler...
|
||||||
__call__: function(context, argv, main, root_value){
|
__call__: function(context, argv, main, root_value){
|
||||||
var parsed = Object.create(this)
|
var parsed = Object.create(this)
|
||||||
var nested = parsed.nested = false
|
var opt_pattern = parsed.optionInputPattern
|
||||||
var rest = parsed.rest =
|
var rest = parsed.rest =
|
||||||
argv == null ?
|
argv == null ?
|
||||||
(typeof(process) != 'unhandled' ?
|
(typeof(process) != 'unhandled' ?
|
||||||
@ -609,6 +600,7 @@ object.Constructor('Parser', {
|
|||||||
main = main
|
main = main
|
||||||
|| require.main.filename
|
|| require.main.filename
|
||||||
// nested command handler...
|
// nested command handler...
|
||||||
|
var nested = parsed.nested = false
|
||||||
if(context instanceof Parser){
|
if(context instanceof Parser){
|
||||||
nested = parsed.nested = true
|
nested = parsed.nested = true
|
||||||
main = context.scriptName +' '+ main
|
main = context.scriptName +' '+ main
|
||||||
@ -620,55 +612,56 @@ object.Constructor('Parser', {
|
|||||||
|| rest.unshift(main) }
|
|| rest.unshift(main) }
|
||||||
// script stuff...
|
// script stuff...
|
||||||
var script = parsed.script = rest.shift()
|
var script = parsed.script = rest.shift()
|
||||||
parsed.scriptName = script.split(/[\\\/]/).pop()
|
var basename = script.split(/[\\\/]/).pop()
|
||||||
|
parsed.scriptName = parsed.scriptName || basename
|
||||||
parsed.scriptPath = script.slice(0,
|
parsed.scriptPath = script.slice(0,
|
||||||
script.length - parsed.scriptName.length)
|
script.length - parsed.scriptName.length)
|
||||||
|
|
||||||
var opt_pattern = parsed.optionInputPattern
|
|
||||||
|
|
||||||
// helpers...
|
// helpers...
|
||||||
var handleError = function(reason, arg, rest){
|
var handleError = function(reason, arg, rest){
|
||||||
parsed.error(reason, arg, rest)
|
parsed.error(reason, arg, rest)
|
||||||
parsed.handleErrorExit
|
parsed.handleErrorExit
|
||||||
&& parsed.handleErrorExit(arg, reason) }
|
&& parsed.handleErrorExit(arg, reason) }
|
||||||
var defaultHandler = function(handler){
|
var defaultHandler = function(handler){
|
||||||
return function(rest, arg, value) {
|
return function(...args){
|
||||||
return parsed.handlerDefault(handler, ...arguments) } }
|
return parsed.handlerDefault(handler, ...args) } }
|
||||||
var runHandler = function(handler, arg, rest){
|
var runHandler = function(handler, arg, rest){
|
||||||
var [arg, value] = arg instanceof Array ?
|
var [arg, value] = arg instanceof Array ?
|
||||||
arg
|
arg
|
||||||
: arg.split(/=/)
|
: arg.split(/=/)
|
||||||
// get option value...
|
// get value...
|
||||||
value = value == null ?
|
value = value == null ?
|
||||||
((handler.arg && !opt_pattern.test(rest[0])) ?
|
(((handler.arg && !opt_pattern.test(rest[0])) ?
|
||||||
rest.shift()
|
rest.shift()
|
||||||
: (typeof(process) != 'undefined' && handler.env) ?
|
: (typeof(process) != 'undefined' && handler.env) ?
|
||||||
(process.env[handler.env]
|
process.env[handler.env]
|
||||||
|| handler.default)
|
: value)
|
||||||
: handler.default)
|
|| handler.default)
|
||||||
: value
|
: value
|
||||||
// value conversion...
|
// value conversion...
|
||||||
value = (value != null
|
value = (value != null
|
||||||
&& parsed.handleArgumentValue) ?
|
&& parsed.handleArgumentValue) ?
|
||||||
parsed.handleArgumentValue(handler, value)
|
parsed.handleArgumentValue(handler, value)
|
||||||
: value
|
: value
|
||||||
|
// required value ...
|
||||||
|
if(handler.valueRequired && value == null){
|
||||||
|
handleError('value missing', arg, rest)
|
||||||
|
parsed.printError('value missing:', arg+'=?')
|
||||||
|
return module.ERROR }
|
||||||
|
|
||||||
// run handler...
|
// run handler...
|
||||||
var res = (typeof(handler) == 'function' ?
|
var res = (typeof(handler) == 'function' ?
|
||||||
handler
|
handler
|
||||||
: (handler.handler
|
: (handler.handler
|
||||||
|| defaultHandler(handler)))
|
|| defaultHandler(handler)))
|
||||||
.call(parsed,
|
.call(parsed, rest, arg,
|
||||||
rest,
|
|
||||||
arg,
|
|
||||||
...(value != null ?
|
...(value != null ?
|
||||||
[value]
|
[value]
|
||||||
: []))
|
: []))
|
||||||
// handle .STOP / .ERROR
|
|
||||||
res === module.STOP
|
res === module.STOP
|
||||||
&& parsed.stop(arg, rest)
|
&& parsed.stop(arg, rest)
|
||||||
// XXX might be a good idea to use exceptions for this...
|
|
||||||
res === module.ERROR
|
res === module.ERROR
|
||||||
// XXX is this the correct reason???
|
|
||||||
&& handleError('unknown', arg, rest)
|
&& handleError('unknown', arg, rest)
|
||||||
return res }
|
return res }
|
||||||
// NOTE: if successful this needs to modify the arg, thus it
|
// NOTE: if successful this needs to modify the arg, thus it
|
||||||
@ -698,6 +691,9 @@ object.Constructor('Parser', {
|
|||||||
var unhandled = []
|
var unhandled = []
|
||||||
while(rest.length > 0){
|
while(rest.length > 0){
|
||||||
var arg = rest.shift()
|
var arg = rest.shift()
|
||||||
|
// NOTE: opts and commands do not follow the same path here
|
||||||
|
// because options if unidentified need to be split into
|
||||||
|
// single letter options and commands to not...
|
||||||
var type = opt_pattern.test(arg) ?
|
var type = opt_pattern.test(arg) ?
|
||||||
'opt'
|
'opt'
|
||||||
: parsed.isCommand(arg) ?
|
: parsed.isCommand(arg) ?
|
||||||
@ -713,17 +709,19 @@ object.Constructor('Parser', {
|
|||||||
&& parsed.splitOptions
|
&& parsed.splitOptions
|
||||||
&& splitArgs(arg, rest))
|
&& splitArgs(arg, rest))
|
||||||
// dynamic or error...
|
// dynamic or error...
|
||||||
|| parsed.handler(
|
|| parsed[type == 'opt' ? '-*' : '@*']
|
||||||
type == 'opt' ?
|
// in case no handler found and '-*' / '@*' not defined...
|
||||||
'-*'
|
if(handler == null){
|
||||||
: '@*')[1]
|
handleError('unknown', arg, rest)
|
||||||
|
parsed.printError('unknown '+(type == 'opt' ? 'option:' : 'command:'), arg)
|
||||||
|
return module.ERROR }
|
||||||
// normalize output of splitArgs(..)
|
// normalize output of splitArgs(..)
|
||||||
;[arg, handler] = handler instanceof Array ?
|
;[arg, handler] = handler instanceof Array ?
|
||||||
handler
|
handler
|
||||||
: [arg, handler]
|
: [arg, handler]
|
||||||
// value handler called...
|
|
||||||
;(handler.env
|
// mark handler...
|
||||||
|| 'default' in handler)
|
;(handler.env || 'default' in handler)
|
||||||
&& values.add(handler)
|
&& values.add(handler)
|
||||||
seen.add(handler)
|
seen.add(handler)
|
||||||
|
|
||||||
@ -755,7 +753,7 @@ object.Constructor('Parser', {
|
|||||||
[k[0], handler.default],
|
[k[0], handler.default],
|
||||||
rest)) })
|
rest)) })
|
||||||
|
|
||||||
// check required options...
|
// check and report required options...
|
||||||
var missing = parsed
|
var missing = parsed
|
||||||
.requiredOptions()
|
.requiredOptions()
|
||||||
.filter(function([k, a, d, h]){
|
.filter(function([k, a, d, h]){
|
||||||
@ -764,13 +762,14 @@ object.Constructor('Parser', {
|
|||||||
return k.pop() })
|
return k.pop() })
|
||||||
if(missing.length > 0){
|
if(missing.length > 0){
|
||||||
handleError('required', missing, rest)
|
handleError('required', missing, rest)
|
||||||
parsed.printError('Required but missing:', missing.join(', '))
|
parsed.printError('required but missing:', missing.join(', '))
|
||||||
return parsed }
|
return parsed }
|
||||||
|
|
||||||
// post handlers...
|
// handle root value...
|
||||||
root_value = root_value && parsed.handleArgumentValue ?
|
root_value = (root_value && parsed.handleArgumentValue) ?
|
||||||
parsed.handleArgumentValue(parsed, root_value)
|
parsed.handleArgumentValue(parsed, root_value)
|
||||||
: root_value
|
: root_value
|
||||||
|
|
||||||
parsed.then(unhandled, root_value, rest)
|
parsed.then(unhandled, root_value, rest)
|
||||||
return parsed },
|
return parsed },
|
||||||
|
|
||||||
|
|||||||
11
test.js
11
test.js
@ -52,6 +52,7 @@ argv.Parser({
|
|||||||
default: 333,
|
default: 333,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// XXX need to handle value correctly...
|
||||||
'-test': argv.Parser({
|
'-test': argv.Parser({
|
||||||
env: 'TEST',
|
env: 'TEST',
|
||||||
arg: 'TEST',
|
arg: 'TEST',
|
||||||
@ -59,6 +60,12 @@ argv.Parser({
|
|||||||
}).then(function(){
|
}).then(function(){
|
||||||
console.log('TEST', ...arguments) }),
|
console.log('TEST', ...arguments) }),
|
||||||
|
|
||||||
|
'-int': {
|
||||||
|
arg: 'INT|int',
|
||||||
|
type: 'int',
|
||||||
|
valueRequired: true,
|
||||||
|
},
|
||||||
|
|
||||||
'@nested': argv.Parser({
|
'@nested': argv.Parser({
|
||||||
doc: 'nested parser.',
|
doc: 'nested parser.',
|
||||||
|
|
||||||
@ -69,6 +76,10 @@ argv.Parser({
|
|||||||
}).then(function(){
|
}).then(function(){
|
||||||
console.log('NESTED DONE', ...arguments) }),
|
console.log('NESTED DONE', ...arguments) }),
|
||||||
|
|
||||||
|
'-\\*': {
|
||||||
|
handler: function(){
|
||||||
|
console.log('-\\*:', ...arguments) } },
|
||||||
|
|
||||||
// these aliases will not get shown...
|
// these aliases will not get shown...
|
||||||
|
|
||||||
// dead-end alias...
|
// dead-end alias...
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user