mirror of
https://github.com/flynx/argv.js.git
synced 2025-10-29 10:50:06 +00:00
refactoring + more defaults... (starting to get a feeling of completeness)
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
parent
87cd4abe96
commit
ece1a86d1c
136
argv.js
136
argv.js
@ -127,6 +127,24 @@ var afterCallback = function(name){
|
|||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
//
|
//
|
||||||
|
//
|
||||||
|
// General runtime architecture:
|
||||||
|
//
|
||||||
|
// Parser(..) -> parser(..) -> result
|
||||||
|
//
|
||||||
|
// Parse(..)
|
||||||
|
// - constructs a parser object (instance)
|
||||||
|
// parse(..)
|
||||||
|
// - parse is instance of Parse
|
||||||
|
// - contains the parsing configuration / grammar
|
||||||
|
// - parses the argv
|
||||||
|
// - creates/returns a result object
|
||||||
|
// result
|
||||||
|
// - parse is prototype of result
|
||||||
|
// - contains all the data resulting from the parse
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
// It is recommended not to do any processing with side-effects in
|
// It is recommended not to do any processing with side-effects in
|
||||||
// option/command handlers directly, prepare for the execution and to
|
// option/command handlers directly, prepare for the execution and to
|
||||||
// the actual work in the .then(..) callback. The reason being that the
|
// the actual work in the .then(..) callback. The reason being that the
|
||||||
@ -134,6 +152,8 @@ var afterCallback = function(name){
|
|||||||
// yet know of any error or stop conditions triggered later in the argv.
|
// yet know of any error or stop conditions triggered later in the argv.
|
||||||
//
|
//
|
||||||
//
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
// 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???
|
||||||
//
|
//
|
||||||
@ -271,6 +291,10 @@ object.Constructor('Parser', {
|
|||||||
// XXX need to test option definitions... (???)
|
// XXX need to test option definitions... (???)
|
||||||
// i.e. report loops and dead ends...
|
// i.e. report loops and dead ends...
|
||||||
|
|
||||||
|
// Builtin options/commands and their configuration...
|
||||||
|
|
||||||
|
// Help...
|
||||||
|
//
|
||||||
// doc config...
|
// doc config...
|
||||||
helpColumnOffset: 3,
|
helpColumnOffset: 3,
|
||||||
helpColumnPrefix: '- ',
|
helpColumnPrefix: '- ',
|
||||||
@ -280,7 +304,6 @@ object.Constructor('Parser', {
|
|||||||
helpValueSeparator: ' ',
|
helpValueSeparator: ' ',
|
||||||
|
|
||||||
// doc sections...
|
// doc sections...
|
||||||
version: undefined,
|
|
||||||
license: undefined,
|
license: undefined,
|
||||||
usage: '$SCRIPTNAME [OPTIONS]',
|
usage: '$SCRIPTNAME [OPTIONS]',
|
||||||
doc: undefined,
|
doc: undefined,
|
||||||
@ -306,7 +329,6 @@ object.Constructor('Parser', {
|
|||||||
.replace(/\$VERSION/g, this.version || '0.0.0')
|
.replace(/\$VERSION/g, this.version || '0.0.0')
|
||||||
.replace(/\$SCRIPTNAME/g, this.scriptName) },
|
.replace(/\$SCRIPTNAME/g, this.scriptName) },
|
||||||
|
|
||||||
// Builtin options/commands...
|
|
||||||
'-h': '-help',
|
'-h': '-help',
|
||||||
'-help': {
|
'-help': {
|
||||||
doc: 'print this message and exit',
|
doc: 'print this message and exit',
|
||||||
@ -412,6 +434,11 @@ object.Constructor('Parser', {
|
|||||||
.join('\n')))
|
.join('\n')))
|
||||||
return module.STOP }},
|
return module.STOP }},
|
||||||
|
|
||||||
|
|
||||||
|
// Version...
|
||||||
|
//
|
||||||
|
version: undefined,
|
||||||
|
|
||||||
'-v': '-version',
|
'-v': '-version',
|
||||||
'-version': {
|
'-version': {
|
||||||
doc: 'show $SCRIPTNAME verion and exit',
|
doc: 'show $SCRIPTNAME verion and exit',
|
||||||
@ -420,6 +447,7 @@ object.Constructor('Parser', {
|
|||||||
this.print(this.version || '0.0.0')
|
this.print(this.version || '0.0.0')
|
||||||
return module.STOP }, },
|
return module.STOP }, },
|
||||||
|
|
||||||
|
|
||||||
// Stop processing arguments and continue into .then(..) handlers...
|
// Stop processing arguments and continue into .then(..) handlers...
|
||||||
//
|
//
|
||||||
// If .then(..) does not handle rest in the nested context then this
|
// If .then(..) does not handle rest in the nested context then this
|
||||||
@ -433,6 +461,7 @@ object.Constructor('Parser', {
|
|||||||
handler: function(){
|
handler: function(){
|
||||||
return module.THEN }, },
|
return module.THEN }, },
|
||||||
|
|
||||||
|
|
||||||
// common short-hands...
|
// common short-hands...
|
||||||
//
|
//
|
||||||
// NOTE: defining this as a loop will enable the user to define any
|
// NOTE: defining this as a loop will enable the user to define any
|
||||||
@ -442,6 +471,7 @@ object.Constructor('Parser', {
|
|||||||
//'-verbose': '-v',
|
//'-verbose': '-v',
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Handle arguments with no explicit handlers found...
|
// Handle arguments with no explicit handlers found...
|
||||||
//
|
//
|
||||||
// Handle dynamic/unknown argument...
|
// Handle dynamic/unknown argument...
|
||||||
@ -473,6 +503,19 @@ object.Constructor('Parser', {
|
|||||||
this.printError('Unknown '+ (key.startsWith('-') ? 'option:' : 'command:'), key)
|
this.printError('Unknown '+ (key.startsWith('-') ? 'option:' : 'command:'), key)
|
||||||
return module.ERROR },
|
return module.ERROR },
|
||||||
|
|
||||||
|
// Default handler action...
|
||||||
|
//
|
||||||
|
// This is called when .handler is not set...
|
||||||
|
handlerDefault: function(handler, rest, key, value){
|
||||||
|
key = handler.key
|
||||||
|
|| handler.arg
|
||||||
|
// get the final key...
|
||||||
|
|| this.handler(key)[0].slice(1)
|
||||||
|
this[key] = value === undefined ?
|
||||||
|
true
|
||||||
|
: value
|
||||||
|
return this },
|
||||||
|
|
||||||
// Handle argument value conversion...
|
// Handle argument value conversion...
|
||||||
//
|
//
|
||||||
// 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...
|
||||||
@ -527,51 +570,54 @@ object.Constructor('Parser', {
|
|||||||
|
|
||||||
//
|
//
|
||||||
// parser(argv)
|
// parser(argv)
|
||||||
// -> parser
|
// -> result
|
||||||
//
|
//
|
||||||
// parser(argv, main)
|
// parser(argv, main)
|
||||||
// -> parser
|
// -> result
|
||||||
//
|
//
|
||||||
|
// NOTE: the result is an object inherited from parser and containing
|
||||||
|
// all the parse data...
|
||||||
// NOTE: this (i.e. parser) can be used as a nested command/option
|
// NOTE: this (i.e. parser) can be used as a nested command/option
|
||||||
// handler...
|
// handler...
|
||||||
//
|
//
|
||||||
__call__: function(context, argv, main, root_value){
|
__call__: function(context, argv, main, root_value){
|
||||||
var that = this
|
var parsed = Object.create(this)
|
||||||
var nested = false
|
var nested = parsed.nested = false
|
||||||
var rest = this.rest =
|
var rest = parsed.rest =
|
||||||
argv == null ?
|
argv == null ?
|
||||||
(typeof(process) != 'unhandled' ?
|
(typeof(process) != 'unhandled' ?
|
||||||
process.argv
|
process.argv
|
||||||
: [])
|
: [])
|
||||||
: argv
|
: argv
|
||||||
argv = rest.slice()
|
parsed.argv = rest.slice()
|
||||||
main = main
|
main = main
|
||||||
|| require.main.filename
|
|| require.main.filename
|
||||||
|
|
||||||
// nested command handler...
|
// nested command handler...
|
||||||
if(context instanceof Parser){
|
if(context instanceof Parser){
|
||||||
nested = true
|
nested = parsed.nested = true
|
||||||
main = context.scriptName +' '+ main
|
main = context.scriptName +' '+ main
|
||||||
rest.unshift(main) }
|
rest.unshift(main) }
|
||||||
|
|
||||||
// normalize the argv...
|
// normalize the argv...
|
||||||
if(main != null){
|
if(main != null){
|
||||||
this.pre_argv = rest.splice(0, rest.indexOf(main))
|
parsed.pre_argv = rest.splice(0, rest.indexOf(main))
|
||||||
rest.includes(main)
|
rest.includes(main)
|
||||||
|| rest.unshift(main) }
|
|| rest.unshift(main) }
|
||||||
|
// script stuff...
|
||||||
|
var script = parsed.script = rest.shift()
|
||||||
|
parsed.scriptName = script.split(/[\\\/]/).pop()
|
||||||
|
parsed.scriptPath = script.slice(0,
|
||||||
|
script.length - parsed.scriptName.length)
|
||||||
|
|
||||||
this.script = rest[0]
|
var opt_pattern = parsed.optionInputPattern
|
||||||
this.scriptName = rest.shift().split(/[\\\/]/).pop()
|
|
||||||
this.scriptPath = this.script.slice(0,
|
|
||||||
this.script.length - this.scriptName.length)
|
|
||||||
|
|
||||||
var opt_pattern = this.optionInputPattern
|
|
||||||
|
|
||||||
// helpers...
|
// helpers...
|
||||||
var handleError = function(reason, arg, rest){
|
var handleError = function(reason, arg, rest){
|
||||||
that.error(reason, arg, rest)
|
parsed.error(reason, arg, rest)
|
||||||
that.handleErrorExit
|
parsed.handleErrorExit
|
||||||
&& that.handleErrorExit(arg, reason) }
|
&& parsed.handleErrorExit(arg, reason) }
|
||||||
|
var defaultHandler = function(handler){
|
||||||
|
return function(rest, arg, value) {
|
||||||
|
return parsed.handlerDefault(handler, rest, arg, value) } }
|
||||||
var runHandler = function(handler, arg, rest){
|
var runHandler = function(handler, arg, rest){
|
||||||
var [arg, value] = arg.split(/=/)
|
var [arg, value] = arg.split(/=/)
|
||||||
// get option value...
|
// get option value...
|
||||||
@ -585,14 +631,15 @@ object.Constructor('Parser', {
|
|||||||
: value
|
: value
|
||||||
// value conversion...
|
// value conversion...
|
||||||
value = (value != null
|
value = (value != null
|
||||||
&& that.handleArgumentValue) ?
|
&& parsed.handleArgumentValue) ?
|
||||||
that.handleArgumentValue(handler, value)
|
parsed.handleArgumentValue(handler, value)
|
||||||
: value
|
: value
|
||||||
// run handler...
|
// run handler...
|
||||||
var res = (typeof(handler) == 'function' ?
|
var res = (typeof(handler) == 'function' ?
|
||||||
handler
|
handler
|
||||||
: handler.handler)
|
: (handler.handler
|
||||||
.call(that,
|
|| defaultHandler(handler)))
|
||||||
|
.call(parsed,
|
||||||
rest,
|
rest,
|
||||||
arg,
|
arg,
|
||||||
...(value != null ?
|
...(value != null ?
|
||||||
@ -600,7 +647,7 @@ object.Constructor('Parser', {
|
|||||||
: []))
|
: []))
|
||||||
// handle .STOP / .ERROR
|
// handle .STOP / .ERROR
|
||||||
res === module.STOP
|
res === module.STOP
|
||||||
&& that.stop(arg, rest)
|
&& parsed.stop(arg, rest)
|
||||||
// XXX might be a good idea to use exceptions for this...
|
// XXX might be a good idea to use exceptions for this...
|
||||||
res === module.ERROR
|
res === module.ERROR
|
||||||
// XXX is this the correct reason???
|
// XXX is this the correct reason???
|
||||||
@ -612,7 +659,7 @@ object.Constructor('Parser', {
|
|||||||
var [arg, value] = arg.split(/=/)
|
var [arg, value] = arg.split(/=/)
|
||||||
// skip single letter unknown options or '--' options...
|
// skip single letter unknown options or '--' options...
|
||||||
if(arg.length <= 2
|
if(arg.length <= 2
|
||||||
|| arg.startsWith(that.optionPrefix.repeat(2))){
|
|| arg.startsWith(parsed.optionPrefix.repeat(2))){
|
||||||
return undefined }
|
return undefined }
|
||||||
// split and normalize...
|
// split and normalize...
|
||||||
var [a, ...r] =
|
var [a, ...r] =
|
||||||
@ -623,7 +670,7 @@ object.Constructor('Parser', {
|
|||||||
&& r.push(r.pop() +'='+ value)
|
&& r.push(r.pop() +'='+ value)
|
||||||
// push new options back to option "stack"...
|
// push new options back to option "stack"...
|
||||||
rest.splice(0, 0, ...r)
|
rest.splice(0, 0, ...r)
|
||||||
var handler = that.handler(a)[1]
|
var handler = parsed.handler(a)[1]
|
||||||
return handler
|
return handler
|
||||||
&& [a, handler] }
|
&& [a, handler] }
|
||||||
|
|
||||||
@ -634,21 +681,20 @@ object.Constructor('Parser', {
|
|||||||
var arg = rest.shift()
|
var arg = rest.shift()
|
||||||
var type = opt_pattern.test(arg) ?
|
var type = opt_pattern.test(arg) ?
|
||||||
'opt'
|
'opt'
|
||||||
: this.isCommand(arg) ?
|
: parsed.isCommand(arg) ?
|
||||||
'cmd'
|
'cmd'
|
||||||
: 'unhandled'
|
: 'unhandled'
|
||||||
// options / commands...
|
// options / commands...
|
||||||
if(type != 'unhandled'){
|
if(type != 'unhandled'){
|
||||||
// get handler...
|
// get handler...
|
||||||
// XXX revise -- arg replacement feels clunky...
|
var handler = parsed.handler(arg)[1]
|
||||||
var handler = this.handler(arg)[1]
|
|
||||||
// handle merged options
|
// handle merged options
|
||||||
// NOTE: if successful returns array...
|
// NOTE: if successful returns array...
|
||||||
|| (type == 'opt'
|
|| (type == 'opt'
|
||||||
&& this.splitOptions
|
&& parsed.splitOptions
|
||||||
&& splitArgs(arg, rest))
|
&& splitArgs(arg, rest))
|
||||||
// dynamic or error...
|
// dynamic or error...
|
||||||
|| this.handleArgument
|
|| parsed.handleArgument
|
||||||
// normalize output of splitArgs(..)
|
// normalize output of splitArgs(..)
|
||||||
;[arg, handler] = handler instanceof Array ?
|
;[arg, handler] = handler instanceof Array ?
|
||||||
handler
|
handler
|
||||||
@ -665,8 +711,8 @@ object.Constructor('Parser', {
|
|||||||
if(res === module.STOP || res === module.ERROR){
|
if(res === module.STOP || res === module.ERROR){
|
||||||
return nested ?
|
return nested ?
|
||||||
res
|
res
|
||||||
: this }
|
: parsed }
|
||||||
// break processing -> .then(...)
|
// finish arg processing now...
|
||||||
if(res === module.THEN){
|
if(res === module.THEN){
|
||||||
arg = null
|
arg = null
|
||||||
break }
|
break }
|
||||||
@ -676,17 +722,17 @@ object.Constructor('Parser', {
|
|||||||
&& unhandled.push(arg) }
|
&& unhandled.push(arg) }
|
||||||
// call value handlers with .env or .default values that were
|
// call value handlers with .env or .default values that were
|
||||||
// not explicitly called yet...
|
// not explicitly called yet...
|
||||||
this.optionsWithValue()
|
parsed.optionsWithValue()
|
||||||
.forEach(function([k, a, d, handler]){
|
.forEach(function([k, a, d, handler]){
|
||||||
values.has(handler)
|
values.has(handler)
|
||||||
|| (((typeof(process) != 'undefined'
|
|| (((typeof(process) != 'undefined'
|
||||||
&& handler.env in process.env)
|
&& handler.env in process.env)
|
||||||
|| handler.default)
|
|| handler.default)
|
||||||
&& seen.add(handler)
|
&& seen.add(handler)
|
||||||
&& runHandler(handler, a || k[0], null, rest)) })
|
&& runHandler(handler, a || k[0], rest)) })
|
||||||
|
|
||||||
// check required options...
|
// check required options...
|
||||||
var missing = this
|
var missing = parsed
|
||||||
.requiredOptions()
|
.requiredOptions()
|
||||||
.filter(function([k, a, d, h]){
|
.filter(function([k, a, d, h]){
|
||||||
return !seen.has(h) })
|
return !seen.has(h) })
|
||||||
@ -694,14 +740,18 @@ 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)
|
||||||
this.printError('Required but missing:', missing.join(', '))
|
parsed.printError('Required but missing:', missing.join(', '))
|
||||||
return this }
|
return parsed }
|
||||||
|
|
||||||
// post handlers...
|
// post handlers...
|
||||||
root_value = root_value && this.handleArgumentValue ?
|
root_value = root_value && parsed.handleArgumentValue ?
|
||||||
this.handleArgumentValue(this, root_value)
|
parsed.handleArgumentValue(parsed, root_value)
|
||||||
: root_value
|
: root_value
|
||||||
return this.then(unhandled, root_value, rest) },
|
parsed.then(unhandled, root_value, rest)
|
||||||
|
// XXX should we detach parsed from this???
|
||||||
|
// i.e. set:
|
||||||
|
// parsed.__proto__ = {}.__proto__
|
||||||
|
return parsed },
|
||||||
|
|
||||||
// NOTE: see general doc...
|
// NOTE: see general doc...
|
||||||
__init__: function(spec){
|
__init__: function(spec){
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ig-argv",
|
"name": "ig-argv",
|
||||||
"version": "2.0.7",
|
"version": "2.1.0",
|
||||||
"description": "simple argv parser",
|
"description": "simple argv parser",
|
||||||
"main": "argv.js",
|
"main": "argv.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user