From c98d05270e0d6d96dde2b5eb784428fb83b69d11 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Mon, 18 Jan 2016 17:13:37 -0500 Subject: [PATCH] Improve error handling. --- src/cli/error.js | 22 ++++++- src/cli/msg.yml | 4 ++ src/cli/out.js | 7 ++- src/core/status-codes.js | 3 +- src/generators/template-generator.js | 61 +++++++----------- src/helpers/generic-helpers.js | 2 +- src/verbs/build.js | 92 ++++++++++++++++------------ 7 files changed, 105 insertions(+), 86 deletions(-) diff --git a/src/cli/error.js b/src/cli/error.js index 478440a..80c2fd9 100644 --- a/src/cli/error.js +++ b/src/cli/error.js @@ -59,8 +59,10 @@ Error-handling routines for HackMyResume. )); // Output the stack (sometimes) - if( objError.showStack ) - o( chalk.red( ex.stack || ex.inner.stack ) ); + if( objError.withStack ) { + var stack = ex.stack || (ex.inner && ex.inner.stack); + stack && o( chalk.red( stack ) ); + } // Quit if necessary if( objError.quit ) { @@ -101,6 +103,7 @@ Error-handling routines for HackMyResume. function assembleError( ex ) { var msg = '', withStack = false, isError = false, quit = true, warn = true; + if( this.debug ) withStack = true; switch( ex.fluenterror ) { @@ -156,7 +159,15 @@ Error-handling routines for HackMyResume. break; case HMSTATUS.generateError: - console.log(ex); + msg = (ex.inner && ex.inner.toString()) || ex; + quit = false; + warn = false; + break; + + case HMSTATUS.fileSaveError: + msg = printf( M2C( this.msgs.fileSaveError.msg ), (ex.inner || ex).toString() ); + warn = false; + quit = false; break; case HMSTATUS.invalidFormat: @@ -184,6 +195,11 @@ Error-handling routines for HackMyResume. warn = false; break; + case HMSTATUS.mixedMerge: + msg = M2C( this.msgs.mixedMerge.msg ); + quit = false; + break; + case HMSTATUS.parseError: if( SyntaxErrorEx.is( ex.inner )) { var se = new SyntaxErrorEx( ex, ex.raw ); diff --git a/src/cli/msg.yml b/src/cli/msg.yml index cac897c..8d35cff 100644 --- a/src/cli/msg.yml +++ b/src/cli/msg.yml @@ -82,3 +82,7 @@ errors: msg: Invalid or corrupt JSON on line %s column %s. invalidHelperUse: msg: Invalid use of the **%s** theme helper. + fileSaveError: + msg: An error occurred while writing %s to disk: %s. + mixedMerge: + msg: "**Warning:** merging mixed resume types. Errors may occur." diff --git a/src/cli/out.js b/src/cli/out.js index 36cbb87..87f7375 100644 --- a/src/cli/out.js +++ b/src/cli/out.js @@ -97,13 +97,16 @@ Output routines for HackMyResume. msg += printf( ((idx === 0) ? this.msgs.beforeMerge.msg[0] : - this.msgs.beforeMerge.msg[1] ), a.i().file + this.msgs.beforeMerge.msg[1] ), a.file ); }, this); - L( M2C(msg, 'green') ); + L( M2C(msg, evt.mixed ? 'yellow' : 'green') ); break; case HME.afterMerge: + break; + + case HME.applyTheme: var numFormats = Object.keys( this.theme.formats ).length; L( M2C(this.msgs.afterMerge.msg, 'green'), this.theme.name.toUpperCase(), diff --git a/src/core/status-codes.js b/src/core/status-codes.js index 955e06b..7e93d45 100644 --- a/src/core/status-codes.js +++ b/src/core/status-codes.js @@ -25,7 +25,8 @@ Status codes for HackMyResume. parseError: 15, fileSaveError: 16, generateError: 17, - invalidHelperUse: 18 + invalidHelperUse: 18, + mixedMerge: 19 }; }()); diff --git a/src/generators/template-generator.js b/src/generators/template-generator.js index 48ddd37..f25ae84 100644 --- a/src/generators/template-generator.js +++ b/src/generators/template-generator.js @@ -15,7 +15,6 @@ Definition of the TemplateGenerator class. TODO: Refactor , PATH = require('path') , parsePath = require('parse-filepath') , MKDIRP = require('mkdirp') - , HMSTATUS = require('../core/status-codes') , BaseGenerator = require( './base-generator' ) , EXTEND = require('../utils/extend') , FRESHTheme = require('../core/fresh-theme') @@ -102,12 +101,13 @@ Definition of the TemplateGenerator class. TODO: Refactor var that = this; // "Generate": process individual files within the theme + // The transform() method catches exceptions internally return { files: curFmt.files.map( function( tplInfo ) { return { info: tplInfo, data: tplInfo.action === 'transform' ? - transform.call( that, rez, tplInfo, theme ) : undefined + transform.call( that, rez, tplInfo, theme, opts ) : undefined }; }).filter(function(item){ return item !== null; }), themeInfo: themeInfo @@ -153,38 +153,26 @@ Definition of the TemplateGenerator class. TODO: Refactor if( file.info.action === 'transform' ) { thisFilePath = PATH.join( outFolder, file.info.orgPath ); - try { - if( that.onBeforeSave ) { - file.data = that.onBeforeSave({ - theme: theme, - outputFile: (file.info.major ? f : thisFilePath), - mk: file.data, - opts: that.opts - }); - if( !file.data ) return; // PDF etc - } - var fileName = file.info.major ? f : thisFilePath; - MKDIRP.sync( PATH.dirname( fileName ) ); - FS.writeFileSync( fileName, file.data, - { encoding: 'utf8', flags: 'w' } ); - that.onAfterSave && that.onAfterSave( - { outputFile: fileName, mk: file.data, opts: that.opts } ); - } - catch( ex ) { - that.stat( HME.error, ex.fluenterrror || - { fluenterror: HMSTATUS.fileSaveError, inner: ex } ); + if( that.onBeforeSave ) { + file.data = that.onBeforeSave({ + theme: theme, + outputFile: (file.info.major ? f : thisFilePath), + mk: file.data, + opts: that.opts + }); + if( !file.data ) return; // PDF etc } + var fileName = file.info.major ? f : thisFilePath; + MKDIRP.sync( PATH.dirname( fileName ) ); + FS.writeFileSync( fileName, file.data, + { encoding: 'utf8', flags: 'w' } ); + that.onAfterSave && that.onAfterSave( + { outputFile: fileName, mk: file.data, opts: that.opts } ); } else if( file.info.action === null/* && theme.explicit*/ ) { thisFilePath = PATH.join( outFolder, file.info.orgPath ); - try { - MKDIRP.sync( PATH.dirname(thisFilePath) ); - FS.copySync( file.info.path, thisFilePath ); - } - catch( ex ) { - that.stat( HME.error, ex.fluenterrror || - { fluenterror: HMSTATUS.fileSaveError, inner: ex } ); - } + MKDIRP.sync( PATH.dirname(thisFilePath) ); + FS.copySync( file.info.path, thisFilePath ); } }); @@ -271,7 +259,7 @@ Definition of the TemplateGenerator class. TODO: Refactor - function transform( rez, tplInfo, theme ) { + function transform( rez, tplInfo, theme, opts ) { try { var cssInfo = { file: tplInfo.css ? tplInfo.cssPath : null, @@ -281,15 +269,8 @@ Definition of the TemplateGenerator class. TODO: Refactor theme ); } catch(ex) { - if( ex.fluenterror ) - throw ex; - else { - console.log('Ballyhoo'); - console.log(ex.stack); - throw { - fluenterror: HMSTATUS.generateError, inner: ex - }; - } + if( opts.errHandler ) opts.errHandler(ex); + else throw ex; } } diff --git a/src/helpers/generic-helpers.js b/src/helpers/generic-helpers.js index 3b55688..e92e9a4 100644 --- a/src/helpers/generic-helpers.js +++ b/src/helpers/generic-helpers.js @@ -279,7 +279,7 @@ Generic template helper definitions for HackMyResume / FluentCV. // Prevent accidental use of safe.start, safe.end, safe.date // The dateRange helper is for raw dates only if( moment.isMoment( dateA ) || moment.isMoment( dateB ) ) { - _reportError( HMSTATUS.invalidHelperUse, { helper: 'dateRange' } ) + _reportError( HMSTATUS.invalidHelperUse, { helper: 'dateRange' } ); return ''; } diff --git a/src/verbs/build.js b/src/verbs/build.js index bcf1382..413a6d7 100644 --- a/src/verbs/build.js +++ b/src/verbs/build.js @@ -32,7 +32,7 @@ Implementation of the 'build' verb for HackMyResume. , pad = require('string-padding') , Verb = require('../verbs/verb'); - var _err, _log, rez; + var _err, _log, _rezObj; @@ -65,50 +65,62 @@ Implementation of the 'build' verb for HackMyResume. */ function build( src, dst, opts ) { - if( !src || !src.length ) { this.err( HMSTATUS.resumeNotFound ); } prep( src, dst, opts ); - // Load input resumes... - var sheets = ResumeFactory.load(src, { - format: null, objectify: true, throw: true, inner: { sort: _opts.sort } - }, this).map( function(sh) { - return sh.rez; - }); + // Load input resumes as JSON... + var sheetObjects = ResumeFactory.load(src, { + format: null, objectify: false, throw: true, inner: { sort: _opts.sort } + }, this); - // Load the theme...we do this first because the theme choice (FRESH or - // JSON Resume) determines what format we'll convert the resume to. + var sheets = sheetObjects.map(function(r) { return r.json; }); + + // Load the theme... this.stat( HMEVENT.beforeTheme, { theme: _opts.theme }); var tFolder = verifyTheme.call( this, _opts.theme ); var theme = loadTheme( tFolder ); this.stat( HMEVENT.afterTheme, { theme: theme }); - // Check for invalid outputs + // Check for invalid outputs... var inv = verifyOutputs.call( this, dst, theme ); if( inv && inv.length ) { this.err( HMSTATUS.invalidFormat, { data: inv, theme: theme } ); } - // Convert resume inputs as necessary - var toFormat = theme.render ? 'JRS' : 'FRESH'; - sheets.forEach( function( sh, idx ) { - if( sh.format() !== toFormat ) { - this.stat( HMEVENT.beforeInlineConvert ); - sheets[ idx ] = new (RTYPES[ toFormat ])(); - var convJSON = RConverter[ 'to' + toFormat ]( sh ); - sheets[ idx ].parseJSON( convJSON ); - this.stat( HMEVENT.afterInlineConvert, { file: sh.i().file, fmt: toFormat } ); - } - }, this); + // Merge input resumes, yielding a single source resume. + var rez; + if( sheets.length > 1 ) { - // Merge input resumes... - (sheets.length > 1) && this.stat( HMEVENT.beforeMerge, { f: _.clone(sheets) }); - rez = _.reduceRight( sheets, function( a, b, idx ) { - return extend( true, b, a ); - }); - // TODO: Fix this condition - (sheets.length) && this.stat( HMEVENT.afterMerge, { r: rez } ); + var isFRESH = !sheets[0].basics; + var mixed = _.any( sheets, function(s) { return isFRESH ? s.basics : !s.basics; }); + this.stat( HMEVENT.beforeMerge, { f: _.clone(sheetObjects), mixed: mixed }); + if( mixed ) { + this.err( HMSTATUS.mixedMerge ); + } + rez = _.reduceRight( sheets, function( a, b, idx ) { + return extend( true, b, a ); + }); + this.stat( HMEVENT.afterMerge, { r: rez } ); + } + else { + rez = sheets[0]; + } + + this.stat( HMEVENT.applyTheme, { r: rez }); + + + // Convert the merged source resume to the theme's format, if necessary + var orgFormat = rez.basics ? 'JRS' : 'FRESH'; + var toFormat = theme.render ? 'JRS' : 'FRESH'; + if( toFormat !== orgFormat ) { + this.stat( HMEVENT.beforeInlineConvert ); + rez = RConverter[ 'to' + toFormat ]( rez ); + this.stat( HMEVENT.afterInlineConvert, { file: sheetObjects[0].file, fmt: toFormat }); + } + + // Load the resume into a FRESHResume or JRSResume object + _rezObj = new (RTYPES[ toFormat ])().parseJSON( rez ); // Expand output resumes... var targets = expand( dst, theme ); @@ -148,6 +160,10 @@ Implementation of the 'build' verb for HackMyResume. } + function handleInternalError( ex ) { + console.log(ex); + } + /** Generate a single target resume such as "out/rez.html" or "out/rez.doc". @@ -157,14 +173,12 @@ Implementation of the 'build' verb for HackMyResume. */ function single( targInfo, theme, finished ) { - var ret, ex; + var ret, ex, f = targInfo.file; try { - if( !targInfo.fmt ) { - return; - } - var f = targInfo.file - , fType = targInfo.fmt.outFormat + + if( !targInfo.fmt ) { return; } + var fType = targInfo.fmt.outFormat , fName = PATH.basename(f, '.' + fType) , theFormat; @@ -180,8 +194,8 @@ Implementation of the 'build' verb for HackMyResume. function(fmt) { return fmt.name === targInfo.fmt.outFormat; })[0]; MKDIRP.sync( PATH.dirname( f ) ); // Ensure dest folder exists; _opts.targets = finished; - _opts.errHandler = this; - ret = theFormat.gen.generate( rez, f, _opts ); + _opts.errHandler = handleInternalError; + ret = theFormat.gen.generate( _rezObj, f, _opts ); } //Otherwise this is an ad-hoc format (JSON, YML, or PNG) that every theme // gets "for free". @@ -191,8 +205,8 @@ Implementation of the 'build' verb for HackMyResume. })[0]; var outFolder = PATH.dirname( f ); MKDIRP.sync( outFolder ); // Ensure dest folder exists; - _opts.errHandler = this; - ret = theFormat.gen.generate( rez, f, _opts ); + _opts.errHandler = handleInternalError; + ret = theFormat.gen.generate( _rezObj, f, _opts ); } } catch( e ) {