From 17259cedbfb281dbdb8bc6fa0e9f37a8afc17c25 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Mon, 29 Jan 2018 02:04:00 -0500 Subject: [PATCH] Detect bad option files supplied via --options. --- dist/cli/error.js | 24 +++++++++++++++++++++++ dist/cli/main.js | 22 +++++++++++++++++++++ dist/cli/msg.yml | 6 ++++++ dist/core/fresh-theme.js | 2 +- dist/core/status-codes.js | 4 +++- dist/utils/safe-json-loader.js | 2 +- dist/verbs/peek.js | 2 +- dist/verbs/validate.js | 2 +- src/cli/error.coffee | 22 +++++++++++++++++++++ src/cli/main.coffee | 18 +++++++++++++++-- src/cli/msg.yml | 6 ++++++ src/core/fresh-theme.coffee | 2 +- src/core/status-codes.coffee | 2 ++ src/utils/safe-json-loader.coffee | 2 +- src/utils/syntax-error-ex.coffee | 3 ++- src/verbs/peek.coffee | 2 +- src/verbs/validate.coffee | 2 +- test/scripts/hmr-options-broken.json | 10 ++++++++++ test/scripts/test-output.js | 29 +++++++++++++++++++++++++++- 19 files changed, 149 insertions(+), 13 deletions(-) create mode 100644 test/scripts/hmr-options-broken.json diff --git a/dist/cli/error.js b/dist/cli/error.js index c7f6722..73964e3 100644 --- a/dist/cli/error.js +++ b/dist/cli/error.js @@ -234,6 +234,30 @@ Error-handling routines for HackMyResume. case HMSTATUS.validateError: msg = printf(M2C(this.msgs.validateError.msg), ex.inner.toString()); etype = 'error'; + break; + case HMSTATUS.invalidOptionsFile: + msg = M2C(this.msgs.invalidOptionsFile.msg[0]); + if (SyntaxErrorEx.is(ex.inner)) { + console.error(printf(M2C(this.msgs.readError.msg, 'red'), ex.file)); + se = new SyntaxErrorEx(ex, ex.raw); + if ((se.line != null) && (se.col != null)) { + msg += printf(M2C(this.msgs.parseError.msg[0], 'red'), se.line, se.col); + } else if (se.line != null) { + msg += printf(M2C(this.msgs.parseError.msg[1], 'red'), se.line); + } else { + msg += M2C(this.msgs.parseError.msg[2], 'red'); + } + } else if (ex.inner && (ex.inner.line != null) && (ex.inner.col != null)) { + msg += printf(M2C(this.msgs.parseError.msg[0], 'red'), ex.inner.line, ex.inner.col); + } else { + msg += ex; + } + msg += this.msgs.invalidOptionsFile.msg[1]; + etype = 'error'; + break; + case HMSTATUS.optionsFileNotFound: + msg = M2C(this.msgs.optionsFileNotFound.msg); + etype = 'error'; } return { msg: msg, diff --git a/dist/cli/main.js b/dist/cli/main.js index 27e4ba7..9236369 100644 --- a/dist/cli/main.js +++ b/dist/cli/main.js @@ -62,6 +62,9 @@ Definition of the `main` function. main = module.exports = function(rawArgs, exitCallback) { var args, initInfo, program; initInfo = initialize(rawArgs, exitCallback); + if (initInfo === null) { + return; + } args = initInfo.args; program = new Command('hackmyresume').version(PKG.version).description(chalk.yellow.bold('*** HackMyResume ***')).option('-s --silent', 'Run in silent mode').option('--no-color', 'Disable colors').option('--color', 'Enable colors').option('-d --debug', 'Enable diagnostics', false).option('-a --assert', 'Treat warnings as errors', false).option('-v --version', 'Show the version').allowUnknownOption(); program.jsonArgs = initInfo.options; @@ -104,6 +107,23 @@ Definition of the `main` function. var o; _exitCallback = exitCallback || process.exit; o = initOptions(ar); + if (o.ex) { + _err.init(false, true, false); + if (o.ex.op === 'parse') { + _err.err({ + fluenterror: o.ex.op === 'parse' ? HMSTATUS.invalidOptionsFile : HMSTATUS.optionsFileNotFound, + inner: o.ex.inner, + quit: true + }); + } else { + _err.err({ + fluenterror: HMSTATUS.optionsFileNotFound, + inner: o.ex.inner, + quit: true + }); + } + return null; + } o.silent || logMsg(_title); if (o.debug) { _out.log(chalk.cyan('The -d or --debug switch was specified. DEBUG mode engaged.')); @@ -173,6 +193,8 @@ Definition of the `main` function. inf = safeLoadJSON(optStr); if (!inf.ex) { oJSON = inf.json; + } else { + return inf; } } } diff --git a/dist/cli/msg.yml b/dist/cli/msg.yml index b60bdb2..015ba90 100644 --- a/dist/cli/msg.yml +++ b/dist/cli/msg.yml @@ -109,3 +109,9 @@ errors: msg: Exiting with status code **%s**. validateError: msg: "An error occurred during validation:\n%s" + invalidOptionsFile: + msg: + - "The specified options file is invalid:\n" + - "\nMake sure the options file contains valid JSON." + optionsFileNotFound: + msg: "The specified options file is missing or inaccessible." diff --git a/dist/core/fresh-theme.js b/dist/core/fresh-theme.js index 9b8642d..a68d12c 100644 --- a/dist/core/fresh-theme.js +++ b/dist/core/fresh-theme.js @@ -50,7 +50,7 @@ Definition of the FRESHTheme class. themeInfo = loadSafeJson(themeFile); if (themeInfo.ex) { throw { - fluenterror: themeInfo.ex.operation === 'parse' ? HMSTATUS.parseError : HMSTATUS.readError, + fluenterror: themeInfo.ex.op === 'parse' ? HMSTATUS.parseError : HMSTATUS.readError, inner: themeInfo.ex.inner }; } diff --git a/dist/core/status-codes.js b/dist/core/status-codes.js index 3a8fc4c..ec2ff1e 100644 --- a/dist/core/status-codes.js +++ b/dist/core/status-codes.js @@ -33,7 +33,9 @@ Status codes for HackMyResume. invalidParamCount: 23, missingParam: 24, createError: 25, - validateError: 26 + validateError: 26, + invalidOptionsFile: 27, + optionsFileNotFound: 28 }; }).call(this); diff --git a/dist/utils/safe-json-loader.js b/dist/utils/safe-json-loader.js index 51f2d2e..6208f77 100644 --- a/dist/utils/safe-json-loader.js +++ b/dist/utils/safe-json-loader.js @@ -21,7 +21,7 @@ Definition of the SafeJsonLoader class. } catch (_error) { retRaw = ret.raw && ret.raw.trim(); ret.ex = { - operation: retRaw ? 'parse' : 'read', + op: retRaw ? 'parse' : 'read', inner: SyntaxErrorEx.is(_error) ? new SyntaxErrorEx(_error, retRaw) : _error, file: file }; diff --git a/dist/verbs/peek.js b/dist/verbs/peek.js index 8445d37..5dda93c 100644 --- a/dist/verbs/peek.js +++ b/dist/verbs/peek.js @@ -80,7 +80,7 @@ Implementation of the 'peek' verb for HackMyResume. } pkgError = null; if (obj.ex) { - errCode = obj.ex.operation === 'parse' ? HMSTATUS.parseError : HMSTATUS.readError; + errCode = obj.ex.op === 'parse' ? HMSTATUS.parseError : HMSTATUS.readError; if (errCode === HMSTATUS.readError) { obj.ex.quiet = true; } diff --git a/dist/verbs/validate.js b/dist/verbs/validate.js index 02de2c8..abf663e 100644 --- a/dist/verbs/validate.js +++ b/dist/verbs/validate.js @@ -111,7 +111,7 @@ Implementation of the 'validate' verb for HackMyResume. ret.violations = validate.errors; } } else { - if (obj.ex.operation === 'parse') { + if (obj.ex.op === 'parse') { errCode = HMSTATUS.parseError; ret.status = 'broken'; } else { diff --git a/src/cli/error.coffee b/src/cli/error.coffee index d482799..5398e68 100644 --- a/src/cli/error.coffee +++ b/src/cli/error.coffee @@ -226,6 +226,28 @@ assembleError = ( ex ) -> msg = printf M2C( @msgs.validateError.msg ), ex.inner.toString() etype = 'error' + when HMSTATUS.invalidOptionsFile + msg = M2C @msgs.invalidOptionsFile.msg[0] + if SyntaxErrorEx.is ex.inner + console.error printf( M2C(this.msgs.readError.msg, 'red'), ex.file ) + se = new SyntaxErrorEx ex, ex.raw + if se.line? and se.col? + msg += printf M2C( this.msgs.parseError.msg[0], 'red' ), se.line, se.col + else if se.line? + msg += printf M2C( this.msgs.parseError.msg[1], 'red' ), se.line + else + msg += M2C @msgs.parseError.msg[2], 'red' + else if ex.inner && ex.inner.line? && ex.inner.col? + msg += printf( M2C( this.msgs.parseError.msg[0], 'red' ), ex.inner.line, ex.inner.col) + else + msg += ex + msg += @msgs.invalidOptionsFile.msg[1] + etype = 'error' + + when HMSTATUS.optionsFileNotFound + msg = M2C( @msgs.optionsFileNotFound.msg ) + etype = 'error' + msg: msg # The error message to display withStack: withStack # Whether to include the stack quit: quit diff --git a/src/cli/main.coffee b/src/cli/main.coffee index e2a7ba1..7fe35c1 100644 --- a/src/cli/main.coffee +++ b/src/cli/main.coffee @@ -40,6 +40,9 @@ process.argv (in production) or custom parameters (in test). main = module.exports = ( rawArgs, exitCallback ) -> initInfo = initialize( rawArgs, exitCallback ) + if initInfo is null + return + args = initInfo.args # Create the top-level (application) command... @@ -139,6 +142,16 @@ initialize = ( ar, exitCallback ) -> _exitCallback = exitCallback || process.exit o = initOptions ar + if o.ex + _err.init false, true, false + if( o.ex.op == 'parse' ) + _err.err + fluenterror: if o.ex.op == 'parse' then HMSTATUS.invalidOptionsFile else HMSTATUS.optionsFileNotFound, + inner: o.ex.inner, + quit: true + else + _err.err fluenterror: HMSTATUS.optionsFileNotFound, inner: o.ex.inner, quit: true + return null o.silent || logMsg( _title ) # Emit debug prelude if --debug was specified @@ -169,7 +182,6 @@ initialize = ( ar, exitCallback ) -> , true return - # Override the .helpInformation behavior Command.prototype.helpInformation = -> manPage = FS.readFileSync( @@ -210,6 +222,7 @@ initOptions = ( ar ) -> if optStr && (optStr = optStr.trim()) #var myJSON = JSON.parse(optStr); if( optStr[0] == '{') + # TODO: remove use of evil(). - hacksalot ### jshint ignore:start ### oJSON = eval('(' + optStr + ')') # jshint ignore:line <-- no worky ### jshint ignore:end ### @@ -217,7 +230,8 @@ initOptions = ( ar ) -> inf = safeLoadJSON( optStr ) if( !inf.ex ) oJSON = inf.json - # TODO: Error handling + else + return inf # Grab the --debug flag, --silent, --assert and --no-color flags isDebug = _.some args, (v) -> v == '-d' || v == '--debug' diff --git a/src/cli/msg.yml b/src/cli/msg.yml index b60bdb2..015ba90 100644 --- a/src/cli/msg.yml +++ b/src/cli/msg.yml @@ -109,3 +109,9 @@ errors: msg: Exiting with status code **%s**. validateError: msg: "An error occurred during validation:\n%s" + invalidOptionsFile: + msg: + - "The specified options file is invalid:\n" + - "\nMake sure the options file contains valid JSON." + optionsFileNotFound: + msg: "The specified options file is missing or inaccessible." diff --git a/src/core/fresh-theme.coffee b/src/core/fresh-theme.coffee index 4dd0485..1a97ddc 100644 --- a/src/core/fresh-theme.coffee +++ b/src/core/fresh-theme.coffee @@ -43,7 +43,7 @@ class FRESHTheme if themeInfo.ex throw fluenterror: - if themeInfo.ex.operation == 'parse' + if themeInfo.ex.op == 'parse' then HMSTATUS.parseError else HMSTATUS.readError inner: themeInfo.ex.inner diff --git a/src/core/status-codes.coffee b/src/core/status-codes.coffee index bf7cce3..d19c344 100644 --- a/src/core/status-codes.coffee +++ b/src/core/status-codes.coffee @@ -33,3 +33,5 @@ module.exports = missingParam: 24 createError: 25 validateError: 26 + invalidOptionsFile: 27 + optionsFileNotFound: 28 diff --git a/src/utils/safe-json-loader.coffee b/src/utils/safe-json-loader.coffee index f61b576..fb391ff 100644 --- a/src/utils/safe-json-loader.coffee +++ b/src/utils/safe-json-loader.coffee @@ -17,7 +17,7 @@ module.exports = ( file ) -> # We'll return HMSTATUS.readError or HMSTATUS.parseError. retRaw = ret.raw && ret.raw.trim() ret.ex = - operation: if retRaw then 'parse' else 'read' + op: if retRaw then 'parse' else 'read' inner: if SyntaxErrorEx.is( _error ) then (new SyntaxErrorEx( _error, retRaw )) diff --git a/src/utils/syntax-error-ex.coffee b/src/utils/syntax-error-ex.coffee index 88d6e8c..432ca77 100644 --- a/src/utils/syntax-error-ex.coffee +++ b/src/utils/syntax-error-ex.coffee @@ -30,6 +30,7 @@ class SyntaxErrorEx @line = (/on line (\d+)/.exec _error)[1] - +# Return true if the supplied parameter is a JavaScript SyntaxError SyntaxErrorEx.is = ( ex ) -> ex instanceof SyntaxError + module.exports = SyntaxErrorEx; diff --git a/src/verbs/peek.coffee b/src/verbs/peek.coffee index 8072dd2..51d95ad 100644 --- a/src/verbs/peek.coffee +++ b/src/verbs/peek.coffee @@ -66,7 +66,7 @@ _peekOne = ( t, objPath ) -> ## safeLoadJSON can only return a READ error or a PARSE error pkgError = null if obj.ex - errCode = if obj.ex.operation == 'parse' then HMSTATUS.parseError else HMSTATUS.readError + errCode = if obj.ex.op == 'parse' then HMSTATUS.parseError else HMSTATUS.readError if errCode == HMSTATUS.readError obj.ex.quiet = true pkgError = fluenterror: errCode, inner: obj.ex diff --git a/src/verbs/validate.coffee b/src/verbs/validate.coffee index 14e9133..bc07520 100644 --- a/src/verbs/validate.coffee +++ b/src/verbs/validate.coffee @@ -83,7 +83,7 @@ _validateOne = (t, validator, schemas, opts) -> # If failure, package JSON read/parse errors else - if obj.ex.operation == 'parse' + if obj.ex.op == 'parse' errCode = HMSTATUS.parseError ret.status = 'broken' else diff --git a/test/scripts/hmr-options-broken.json b/test/scripts/hmr-options-broken.json new file mode 100644 index 0000000..1fe034e --- /dev/null +++ b/test/scripts/hmr-options-broken.json @@ -0,0 +1,10 @@ +{ + // Set the default theme to "compact" + //"theme": "node_modules/jsonresume-theme-elegant", + //"theme": "jsonresume-theme-elegant", + "theme": "elegant", + // Change the "employment" section title text to "Work" + "sectionTitles": { + "employment": "Work" + } +} diff --git a/test/scripts/test-output.js b/test/scripts/test-output.js index cd584d6..d3d906b 100644 --- a/test/scripts/test-output.js +++ b/test/scripts/test-output.js @@ -143,9 +143,36 @@ describe('Testing Ouput interface', function () { 'to', 'test/sandbox/temp/janeq-3.all', '--options', - "test/hmr-options.json", + "test/scripts/hmr-options.json", "-t", "modern" ], [ 'Applying MODERN theme'] ); + + run('HMR should detect a missing or inaccessible options file', + [ + 'build', + 'doesntmatter.json', + 'to', + 'dontcare.all', + '--options', + "test/scripts/hmr-options-nonexistent.json", + "-t", + "modern" + ], + [ 'The specified options file is missing or inaccessible'] ); + + run('HMR should detect an invalid or malformed options file', + [ + 'build', + 'doesntmatter.json', + 'to', + 'dontcare.all', + '--options', + "test/scripts/hmr-options-broken.json", + "-t", + "modern" + ], + [ 'The specified options file is invalid'] ); + });