mirror of
https://github.com/flynx/argv.js.git
synced 2025-10-28 18:30:07 +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
|
||||
// option/command handlers directly, prepare for the execution and to
|
||||
// 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.
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
// NOTE: essentially this parser is a very basic 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... (???)
|
||||
// i.e. report loops and dead ends...
|
||||
|
||||
// Builtin options/commands and their configuration...
|
||||
|
||||
// Help...
|
||||
//
|
||||
// doc config...
|
||||
helpColumnOffset: 3,
|
||||
helpColumnPrefix: '- ',
|
||||
@ -280,7 +304,6 @@ object.Constructor('Parser', {
|
||||
helpValueSeparator: ' ',
|
||||
|
||||
// doc sections...
|
||||
version: undefined,
|
||||
license: undefined,
|
||||
usage: '$SCRIPTNAME [OPTIONS]',
|
||||
doc: undefined,
|
||||
@ -306,7 +329,6 @@ object.Constructor('Parser', {
|
||||
.replace(/\$VERSION/g, this.version || '0.0.0')
|
||||
.replace(/\$SCRIPTNAME/g, this.scriptName) },
|
||||
|
||||
// Builtin options/commands...
|
||||
'-h': '-help',
|
||||
'-help': {
|
||||
doc: 'print this message and exit',
|
||||
@ -412,6 +434,11 @@ object.Constructor('Parser', {
|
||||
.join('\n')))
|
||||
return module.STOP }},
|
||||
|
||||
|
||||
// Version...
|
||||
//
|
||||
version: undefined,
|
||||
|
||||
'-v': '-version',
|
||||
'-version': {
|
||||
doc: 'show $SCRIPTNAME verion and exit',
|
||||
@ -420,6 +447,7 @@ object.Constructor('Parser', {
|
||||
this.print(this.version || '0.0.0')
|
||||
return module.STOP }, },
|
||||
|
||||
|
||||
// Stop processing arguments and continue into .then(..) handlers...
|
||||
//
|
||||
// If .then(..) does not handle rest in the nested context then this
|
||||
@ -433,6 +461,7 @@ object.Constructor('Parser', {
|
||||
handler: function(){
|
||||
return module.THEN }, },
|
||||
|
||||
|
||||
// common short-hands...
|
||||
//
|
||||
// NOTE: defining this as a loop will enable the user to define any
|
||||
@ -442,6 +471,7 @@ object.Constructor('Parser', {
|
||||
//'-verbose': '-v',
|
||||
|
||||
|
||||
|
||||
// Handle arguments with no explicit handlers found...
|
||||
//
|
||||
// Handle dynamic/unknown argument...
|
||||
@ -473,6 +503,19 @@ object.Constructor('Parser', {
|
||||
this.printError('Unknown '+ (key.startsWith('-') ? 'option:' : 'command:'), key)
|
||||
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...
|
||||
//
|
||||
// If this is false/undefined value is passed to the handler as-is...
|
||||
@ -527,51 +570,54 @@ object.Constructor('Parser', {
|
||||
|
||||
//
|
||||
// parser(argv)
|
||||
// -> parser
|
||||
// -> result
|
||||
//
|
||||
// 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
|
||||
// handler...
|
||||
//
|
||||
__call__: function(context, argv, main, root_value){
|
||||
var that = this
|
||||
var nested = false
|
||||
var rest = this.rest =
|
||||
var parsed = Object.create(this)
|
||||
var nested = parsed.nested = false
|
||||
var rest = parsed.rest =
|
||||
argv == null ?
|
||||
(typeof(process) != 'unhandled' ?
|
||||
process.argv
|
||||
: [])
|
||||
: argv
|
||||
argv = rest.slice()
|
||||
parsed.argv = rest.slice()
|
||||
main = main
|
||||
|| require.main.filename
|
||||
|
||||
// nested command handler...
|
||||
if(context instanceof Parser){
|
||||
nested = true
|
||||
nested = parsed.nested = true
|
||||
main = context.scriptName +' '+ main
|
||||
rest.unshift(main) }
|
||||
|
||||
// normalize the argv...
|
||||
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.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]
|
||||
this.scriptName = rest.shift().split(/[\\\/]/).pop()
|
||||
this.scriptPath = this.script.slice(0,
|
||||
this.script.length - this.scriptName.length)
|
||||
|
||||
var opt_pattern = this.optionInputPattern
|
||||
var opt_pattern = parsed.optionInputPattern
|
||||
|
||||
// helpers...
|
||||
var handleError = function(reason, arg, rest){
|
||||
that.error(reason, arg, rest)
|
||||
that.handleErrorExit
|
||||
&& that.handleErrorExit(arg, reason) }
|
||||
parsed.error(reason, arg, rest)
|
||||
parsed.handleErrorExit
|
||||
&& 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 [arg, value] = arg.split(/=/)
|
||||
// get option value...
|
||||
@ -585,14 +631,15 @@ object.Constructor('Parser', {
|
||||
: value
|
||||
// value conversion...
|
||||
value = (value != null
|
||||
&& that.handleArgumentValue) ?
|
||||
that.handleArgumentValue(handler, value)
|
||||
&& parsed.handleArgumentValue) ?
|
||||
parsed.handleArgumentValue(handler, value)
|
||||
: value
|
||||
// run handler...
|
||||
var res = (typeof(handler) == 'function' ?
|
||||
handler
|
||||
: handler.handler)
|
||||
.call(that,
|
||||
: (handler.handler
|
||||
|| defaultHandler(handler)))
|
||||
.call(parsed,
|
||||
rest,
|
||||
arg,
|
||||
...(value != null ?
|
||||
@ -600,7 +647,7 @@ object.Constructor('Parser', {
|
||||
: []))
|
||||
// handle .STOP / .ERROR
|
||||
res === module.STOP
|
||||
&& that.stop(arg, rest)
|
||||
&& parsed.stop(arg, rest)
|
||||
// XXX might be a good idea to use exceptions for this...
|
||||
res === module.ERROR
|
||||
// XXX is this the correct reason???
|
||||
@ -612,7 +659,7 @@ object.Constructor('Parser', {
|
||||
var [arg, value] = arg.split(/=/)
|
||||
// skip single letter unknown options or '--' options...
|
||||
if(arg.length <= 2
|
||||
|| arg.startsWith(that.optionPrefix.repeat(2))){
|
||||
|| arg.startsWith(parsed.optionPrefix.repeat(2))){
|
||||
return undefined }
|
||||
// split and normalize...
|
||||
var [a, ...r] =
|
||||
@ -623,7 +670,7 @@ object.Constructor('Parser', {
|
||||
&& r.push(r.pop() +'='+ value)
|
||||
// push new options back to option "stack"...
|
||||
rest.splice(0, 0, ...r)
|
||||
var handler = that.handler(a)[1]
|
||||
var handler = parsed.handler(a)[1]
|
||||
return handler
|
||||
&& [a, handler] }
|
||||
|
||||
@ -634,21 +681,20 @@ object.Constructor('Parser', {
|
||||
var arg = rest.shift()
|
||||
var type = opt_pattern.test(arg) ?
|
||||
'opt'
|
||||
: this.isCommand(arg) ?
|
||||
: parsed.isCommand(arg) ?
|
||||
'cmd'
|
||||
: 'unhandled'
|
||||
// options / commands...
|
||||
if(type != 'unhandled'){
|
||||
// get handler...
|
||||
// XXX revise -- arg replacement feels clunky...
|
||||
var handler = this.handler(arg)[1]
|
||||
var handler = parsed.handler(arg)[1]
|
||||
// handle merged options
|
||||
// NOTE: if successful returns array...
|
||||
|| (type == 'opt'
|
||||
&& this.splitOptions
|
||||
&& parsed.splitOptions
|
||||
&& splitArgs(arg, rest))
|
||||
// dynamic or error...
|
||||
|| this.handleArgument
|
||||
|| parsed.handleArgument
|
||||
// normalize output of splitArgs(..)
|
||||
;[arg, handler] = handler instanceof Array ?
|
||||
handler
|
||||
@ -665,8 +711,8 @@ object.Constructor('Parser', {
|
||||
if(res === module.STOP || res === module.ERROR){
|
||||
return nested ?
|
||||
res
|
||||
: this }
|
||||
// break processing -> .then(...)
|
||||
: parsed }
|
||||
// finish arg processing now...
|
||||
if(res === module.THEN){
|
||||
arg = null
|
||||
break }
|
||||
@ -676,17 +722,17 @@ object.Constructor('Parser', {
|
||||
&& unhandled.push(arg) }
|
||||
// call value handlers with .env or .default values that were
|
||||
// not explicitly called yet...
|
||||
this.optionsWithValue()
|
||||
parsed.optionsWithValue()
|
||||
.forEach(function([k, a, d, handler]){
|
||||
values.has(handler)
|
||||
|| (((typeof(process) != 'undefined'
|
||||
&& handler.env in process.env)
|
||||
|| handler.default)
|
||||
&& seen.add(handler)
|
||||
&& runHandler(handler, a || k[0], null, rest)) })
|
||||
&& runHandler(handler, a || k[0], rest)) })
|
||||
|
||||
// check required options...
|
||||
var missing = this
|
||||
var missing = parsed
|
||||
.requiredOptions()
|
||||
.filter(function([k, a, d, h]){
|
||||
return !seen.has(h) })
|
||||
@ -694,14 +740,18 @@ object.Constructor('Parser', {
|
||||
return k.pop() })
|
||||
if(missing.length > 0){
|
||||
handleError('required', missing, rest)
|
||||
this.printError('Required but missing:', missing.join(', '))
|
||||
return this }
|
||||
parsed.printError('Required but missing:', missing.join(', '))
|
||||
return parsed }
|
||||
|
||||
// post handlers...
|
||||
root_value = root_value && this.handleArgumentValue ?
|
||||
this.handleArgumentValue(this, root_value)
|
||||
root_value = root_value && parsed.handleArgumentValue ?
|
||||
parsed.handleArgumentValue(parsed, 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...
|
||||
__init__: function(spec){
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ig-argv",
|
||||
"version": "2.0.7",
|
||||
"version": "2.1.0",
|
||||
"description": "simple argv parser",
|
||||
"main": "argv.js",
|
||||
"scripts": {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user