reworked error handling...

Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
Alex A. Naanou 2020-08-01 19:25:31 +03:00
parent 742e1c51bc
commit dd74fb1444
5 changed files with 148 additions and 109 deletions

81
argv.js
View File

@ -49,7 +49,14 @@ module.ParserError =
// NOTE: I do not get why JavaScript's Error implements this // NOTE: I do not get why JavaScript's Error implements this
// statically... // statically...
get name(){ get name(){
return this.constructor.name }, }) return this.constructor.name },
// NOTE: msg is handled by Error(..)
__init__: function(msg, arg, rest){
this.arg = arg
this.rest = rest
},
})
module.ParserTypeError = module.ParserTypeError =
object.Constructor('ParserTypeError', module.ParserError, {}) object.Constructor('ParserTypeError', module.ParserError, {})
@ -712,7 +719,7 @@ object.Constructor('Parser', {
//section_doc: ..., //section_doc: ...,
handler: function(_, key){ handler: function(_, key){
throw module.ParserError( throw module.ParserError(
`Unknown ${key.startsWith('-') ? 'option:' : 'command:'} ${ key }`) } }, `Unknown ${key.startsWith('-') ? 'option:' : 'command:'} $ARG`) } },
'@*': '-*', '@*': '-*',
@ -731,11 +738,20 @@ object.Constructor('Parser', {
// .printError(error, ...) // .printError(error, ...)
// -> error // -> error
// //
// NOTE: this handles $ARG in error.message.
printError: afterCallback('print_error', null, function(...args){ printError: afterCallback('print_error', null, function(...args){
if(args[0] instanceof module.ParserError){ if(args[0] instanceof module.ParserError){
var err = args[0]
console.error( console.error(
this.scriptName+':', args[0].name+':', args[0].message, ...args.slice(1)) this.scriptName+':',
return args[0] } err.name+':',
err.message
// XXX this should be done in ParserError but there
// we have to fight Error's implementation of
// .message and its use...
.replace(/\$ARG/, err.arg),
...args.slice(1))
return err }
console.error(this.scriptName+': Error:', ...args) console.error(this.scriptName+': Error:', ...args)
return this }), return this }),
@ -820,7 +836,8 @@ object.Constructor('Parser', {
// error... // error...
handleErrorExit: function(arg, reason){ handleErrorExit: function(arg, reason){
typeof(process) != 'unhandled' typeof(process) != 'unhandled'
&& process.exit(1) }, && process.exit(1)
return this },
// Post parsing callbacks... // Post parsing callbacks...
@ -859,11 +876,6 @@ object.Constructor('Parser', {
// all the parse data... // 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...
// NOTE: we can't throw ParserError(..) from outside the try/catch
// block in here as it will not be handled locally...
// XXX this may need a rethink -- should the try/catch block
// include the rest of the cases where reportError(..) is
// used or be on a level above runHandler(..)
__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 opt_pattern = parsed.optionInputPattern var opt_pattern = parsed.optionInputPattern
@ -896,17 +908,14 @@ object.Constructor('Parser', {
// helpers... // helpers...
var handleError = function(reason, arg, rest){ var handleError = function(reason, arg, rest){
arg = arg || reason.arg
rest = rest || reason.rest
reason = reason instanceof Error ? reason = reason instanceof Error ?
[reason.name, reason.message].join(': ') [reason.name, reason.message].join(': ')
: reason : reason
parsed.error(reason, arg, rest) parsed.error(reason, arg, rest)
parsed.handleErrorExit parsed.handleErrorExit
&& parsed.handleErrorExit(arg, reason) } && parsed.handleErrorExit(arg, reason) }
var reportError = function(message, arg, rest){
message = message
.replace(/\$ARG/g, arg)
parsed.printError(module.ParserError(message))
return handleError(message, arg, rest) }
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
@ -927,26 +936,24 @@ object.Constructor('Parser', {
: value : value
// required value check... // required value check...
if(handler.valueRequired && value == null){ if(handler.valueRequired && value == null){
return reportError('Value missing: $ARG=?', arg, rest) } throw module.ParserValueError('Value missing: ${ arg }=?') }
try {
// run handler... // run handler...
try {
var res = parsed.handle(handler, rest, arg, value) var res = parsed.handle(handler, rest, arg, value)
// update error object with current context's arg and rest...
} catch(err){ } catch(err){
// re-throw the error... if(err instanceof module.ParserError){
// NOTE: do not like that this can mask the location of err.arg = err.arg || arg
// the original error. err.rest = err.rest || rest }
if(!(err instanceof module.ParserError)){
throw err } throw err }
// XXX should we report an error here???
parsed.printError(err)
res = err }
// NOTE: we also need to handle the errors passed to us from // NOTE: we also need to handle the errors passed to us from
// nested parsers... // nested parsers...
res === module.STOP res === module.STOP
&& parsed.stop(arg, rest) && parsed.stop(arg, rest)
// XXX revise -- do we need to re-handle errors???
res instanceof module.ParserError res instanceof module.ParserError
&& handleError(res, arg, rest) && handleError(res, arg, rest)
return res } return res }
@ -969,6 +976,7 @@ object.Constructor('Parser', {
rest.splice(0, 0, ...r) rest.splice(0, 0, ...r)
return [ a, parsed.handler(a)[1] ] } return [ a, parsed.handler(a)[1] ] }
try{
// parse/interpret the arguments and call handlers... // parse/interpret the arguments and call handlers...
var values = new Set() var values = new Set()
var seen = new Set() var seen = new Set()
@ -1002,7 +1010,7 @@ object.Constructor('Parser', {
|| parsed.handler(dfl)[1] || parsed.handler(dfl)[1]
// no handler found and '-*' or '@*' not defined... // no handler found and '-*' or '@*' not defined...
if(handler == null){ if(handler == null){
return reportError(`Unknown ${ type == 'opt' ? 'option' : 'command:' } $ARG`, arg, rest) } throw ParserError(`Unknown ${ type == 'opt' ? 'option' : 'command:' } $ARG`, arg) }
// mark handler... // mark handler...
;(handler.env || 'default' in handler) ;(handler.env || 'default' in handler)
@ -1025,10 +1033,8 @@ 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...
// XXX an error, THEN or STOP returned from runHandler(..) in here will // XXX THEN or STOP returned from runHandler(..) in here will
// not stop execution -- should it??? // not stop execution -- should it???
// XXX a ParserError thrown here will not be handled correctly
// in the root parser...
parsed.optionsWithValue() parsed.optionsWithValue()
.forEach(function([k, a, d, handler]){ .forEach(function([k, a, d, handler]){
values.has(handler) values.has(handler)
@ -1036,7 +1042,6 @@ object.Constructor('Parser', {
&& handler.env in process.env) && handler.env in process.env)
|| handler.default) || handler.default)
&& seen.add(handler) && seen.add(handler)
// XXX should we handle STOP / ParserError here???
&& runHandler(handler, && runHandler(handler,
[k[0], handler.default], [k[0], handler.default],
rest)) }) rest)) })
@ -1049,8 +1054,22 @@ object.Constructor('Parser', {
.map(function([k, a, d, h]){ .map(function([k, a, d, h]){
return k.pop() }) return k.pop() })
if(missing.length > 0){ if(missing.length > 0){
reportError('required but missing: $ARG', missing.join(', '), rest) throw module.ParserError(`required but missing: $ARG`, missing.join(', ')) }
return parsed }
// handle ParserError...
} catch(err){
// re-throw the error...
if(!(err instanceof module.ParserError)){
throw err }
// report local errors...
// NOTE: non-local errors are threaded as return values...
parsed.printError(err)
handleError(err, err.arg, rest)
return nested ?
err
: parsed }
// handle root value... // handle root value...
root_value = root_value =

View File

@ -3,7 +3,9 @@
// compatible with both node's and RequireJS' require(..) // compatible with both node's and RequireJS' require(..)
var argv = require('../argv') var argv = require('../argv')
var parser = argv.Parser({ var parser =
exports.parser =
argv.Parser({
// option definitions... // option definitions...
// ... // ...
}) })
@ -13,7 +15,7 @@ var parser = argv.Parser({
}) })
// run the parser... // run the parser...
__filename == require.main.filename __filename == (require.main || {}).filename
&& parser(process.argv) && parser(process.argv)
// vim:set ts=4 sw=4 spell : // vim:set ts=4 sw=4 spell :

View File

@ -2,7 +2,9 @@
var argv = require('../argv') var argv = require('../argv')
var parser = argv.Parser({ var parser =
exports.parser =
argv.Parser({
doc: 'Example script options', doc: 'Example script options',
// to make things consistent we'll take the version from package.json // to make things consistent we'll take the version from package.json
@ -71,6 +73,7 @@ var parser = argv.Parser({
}, },
// XXX this is misbehaving -- setting true instead of $HOME
'-home': { '-home': {
doc: 'set home path', doc: 'set home path',
arg: 'HOME | home', arg: 'HOME | home',
@ -145,7 +148,7 @@ var parser = argv.Parser({
// run the parser... // run the parser...
__filename == require.main.filename __filename == (require.main || {}).filename
&& parser() && parser()
// vim:set ts=4 sw=4 spell : // vim:set ts=4 sw=4 spell :

View File

@ -1,6 +1,6 @@
{ {
"name": "ig-argv", "name": "ig-argv",
"version": "2.8.1", "version": "2.9.0",
"description": "simple argv parser", "description": "simple argv parser",
"main": "argv.js", "main": "argv.js",
"scripts": { "scripts": {

17
test.js
View File

@ -14,6 +14,11 @@ var object = require('ig-object')
var argv = require('./argv') var argv = require('./argv')
var bare = module.bare = require('./examples/bare').parser
var options = module.options = require('./examples/options').parser
//--------------------------------------------------------------------- //---------------------------------------------------------------------
@ -90,7 +95,7 @@ argv.Parser({
'-error': { '-error': {
doc: 'throw an error', doc: 'throw an error',
handler: function(){ handler: function(){
throw argv.ParserError('error') }}, throw argv.ParserError('error: $ARG') }},
'-passive-error': { '-passive-error': {
doc: 'throw an error', doc: 'throw an error',
handler: function(){ handler: function(){
@ -148,6 +153,16 @@ argv.Parser({
'-k': '-l', '-k': '-l',
'-l': '-m', '-l': '-m',
'-m': '-k', '-m': '-k',
'@bare': bare,
'@opts': options,
// collision test...
// NOTE: values of these will shadow the API...
'@options': {},
'-handler': {},
}) })
//.print(function(...args){ //.print(function(...args){
// console.log('----\n', ...args) // console.log('----\n', ...args)