From 3e3803ed8502b410390de8ac8ff1b1079b5ccc13 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Mon, 18 Jan 2016 14:10:35 -0500 Subject: [PATCH] Improve error handling. --- src/cli/error.js | 10 +++ src/cli/msg.yml | 4 +- src/cli/out.js | 8 +-- src/core/status-codes.js | 3 +- src/generators/template-generator.js | 17 +++-- src/helpers/generic-helpers.js | 93 ++++++++++++++++++++++----- src/helpers/handlebars-helpers.js | 3 +- src/renderers/handlebars-generator.js | 4 +- src/verbs/build.js | 31 ++++++--- 9 files changed, 134 insertions(+), 39 deletions(-) diff --git a/src/cli/error.js b/src/cli/error.js index 1445fc7..478440a 100644 --- a/src/cli/error.js +++ b/src/cli/error.js @@ -155,6 +155,10 @@ Error-handling routines for HackMyResume. warn = false; break; + case HMSTATUS.generateError: + console.log(ex); + break; + case HMSTATUS.invalidFormat: ex.data.forEach(function(d){ msg += printf( M2C( this.msgs.invalidFormat.msg, 'bold' ), @@ -162,6 +166,12 @@ Error-handling routines for HackMyResume. }, this); break; + case HMSTATUS.invalidHelperUse: + msg = printf( M2C( this.msgs.invalidHelperUse.msg ), ex.helper ); + quit = false; + warn = true; + break; + case HMSTATUS.notOnPath: msg = printf( M2C(this.msgs.notOnPath.msg, 'bold'), ex.engine); quit = false; diff --git a/src/cli/msg.yml b/src/cli/msg.yml index 52578a4..cac897c 100644 --- a/src/cli/msg.yml +++ b/src/cli/msg.yml @@ -26,7 +26,7 @@ events: http-server For more information, see the README." - beforeGenerate: + afterGenerate: msg: - " (with %s)" - "Skipping %s resume: %s" @@ -80,3 +80,5 @@ errors: msg: Reading **???** resume: **%s** parseError: msg: Invalid or corrupt JSON on line %s column %s. + invalidHelperUse: + msg: Invalid use of the **%s** theme helper. diff --git a/src/cli/out.js b/src/cli/out.js index 3062817..36cbb87 100644 --- a/src/cli/out.js +++ b/src/cli/out.js @@ -127,22 +127,22 @@ Output routines for HackMyResume. } break; - case HME.beforeGenerate: + case HME.afterGenerate: var suffix = ''; if( evt.fmt === 'pdf' ) { if( this.opts.pdf ) { if( this.opts.pdf !== 'none' ) { - suffix = printf( M2C( this.msgs.beforeGenerate.msg[0], 'green' ), this.opts.pdf ); + suffix = printf( M2C( this.msgs.afterGenerate.msg[0], evt.error ? 'red' : 'green' ), this.opts.pdf ); } else { - L( M2C( this.msgs.beforeGenerate.msg[1], 'gray' ), + L( M2C( this.msgs.afterGenerate.msg[1], 'gray' ), evt.fmt.toUpperCase(), evt.file ); return; } } } - L( M2C( this.msgs.beforeGenerate.msg[2] + suffix, 'green' ), + L( M2C( this.msgs.afterGenerate.msg[2] + suffix, evt.error ? 'red' : 'green' ), pad(evt.fmt.toUpperCase(),4,null,pad.RIGHT), PATH.relative(process.cwd(), evt.file )); break; diff --git a/src/core/status-codes.js b/src/core/status-codes.js index be82e6b..955e06b 100644 --- a/src/core/status-codes.js +++ b/src/core/status-codes.js @@ -24,7 +24,8 @@ Status codes for HackMyResume. readError: 14, parseError: 15, fileSaveError: 16, - generateError: 17 + generateError: 17, + invalidHelperUse: 18 }; }()); diff --git a/src/generators/template-generator.js b/src/generators/template-generator.js index b5c962f..48ddd37 100644 --- a/src/generators/template-generator.js +++ b/src/generators/template-generator.js @@ -15,6 +15,7 @@ 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') @@ -171,7 +172,7 @@ Definition of the TemplateGenerator class. TODO: Refactor } catch( ex ) { that.stat( HME.error, ex.fluenterrror || - { fluenterror: HACKMYSTATUS.fileSaveError, inner: ex } ); + { fluenterror: HMSTATUS.fileSaveError, inner: ex } ); } } else if( file.info.action === null/* && theme.explicit*/ ) { @@ -182,7 +183,7 @@ Definition of the TemplateGenerator class. TODO: Refactor } catch( ex ) { that.stat( HME.error, ex.fluenterrror || - { fluenterror: HACKMYSTATUS.fileSaveError, inner: ex } ); + { fluenterror: HMSTATUS.fileSaveError, inner: ex } ); } } }); @@ -276,13 +277,19 @@ Definition of the TemplateGenerator class. TODO: Refactor file: tplInfo.css ? tplInfo.cssPath : null, data: tplInfo.css || null }; - return this.single( rez, tplInfo.data, this.format, cssInfo, this.opts, theme ); } catch(ex) { - that.stat( HME.error, ex.fluenterrror || - { fluenterror: HACKMYSTATUS.generateError, inner: ex } ); + if( ex.fluenterror ) + throw ex; + else { + console.log('Ballyhoo'); + console.log(ex.stack); + throw { + fluenterror: HMSTATUS.generateError, inner: ex + }; + } } } diff --git a/src/helpers/generic-helpers.js b/src/helpers/generic-helpers.js index ab9947b..3b55688 100644 --- a/src/helpers/generic-helpers.js +++ b/src/helpers/generic-helpers.js @@ -1,7 +1,7 @@ /** Generic template helper definitions for HackMyResume / FluentCV. @license MIT. See LICENSE.md for details. -@module generic-helpers.js +@module helpers/generic-helpers */ @@ -10,6 +10,8 @@ Generic template helper definitions for HackMyResume / FluentCV. var MD = require('marked') , H2W = require('../utils/html-to-wpml') , XML = require('xml-escape') + , FluentDate = require('../core/fluent-date') + , HMSTATUS = require('../core/status-codes') , moment = require('moment') , LO = require('lodash') , _ = require('underscore') @@ -23,7 +25,7 @@ Generic template helper definitions for HackMyResume / FluentCV. /** Convert the input date to a specified format through Moment.js. - If date is invalid, will return the time provided by the user, + If date is invalid, will return the time provided by the user, or default to the fallback param or 'Present' if that is set to true @method formatDate */ @@ -37,23 +39,21 @@ Generic template helper definitions for HackMyResume / FluentCV. }, /** - Format a from/to date range. + Given a resume sub-object with a start/end date, format a representation of + the date range. @method dateRange */ - dateRange: function( obj, fmt, sep, options ) { - fmt = (fmt && String.is(fmt) && fmt) || 'YYYY-MM'; - sep = (sep && String.is(sep) && sep) || ' — '; - if( obj.safe ) { - var dateA = (obj.safe.start && obj.safe.start.format(fmt)) || ''; - var dateB = (obj.safe.end && obj.safe.end.format(fmt)) || ''; - if( obj.safe.start && obj.safe.end ) { - return dateA + sep + dateB ; - } - else if( obj.safe.start || obj.safe.end ) { - return dateA || dateB; - } - } - return ''; + dateRange: function( obj, fmt, sep, fallback, options ) { + if( !obj ) return ''; + return _fromTo( obj.start, obj.end, fmt, sep, fallback, options ); + }, + + /** + Format a from/to date range for display. + @method toFrom + */ + fromTo: function() { + return _fromTo.apply( this, arguments ); }, /** @@ -262,6 +262,65 @@ Generic template helper definitions for HackMyResume / FluentCV. }; + + /** + Report an error to the outside world without throwing an exception. Currently + relies on kludging the running verb into. opts. + */ + function _reportError( code, params ) { + GenericHelpers.opts.errHandler.err( code, params ); + } + + /** + Format a from/to date range for display. + */ + function _fromTo( dateA, dateB, fmt, sep, fallback ) { + + // 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' } ) + return ''; + } + + var dateFrom, dateTo, dateTemp; + + // Check for 'current', 'present', 'now', '', null, and undefined + dateA = dateA || ''; + dateB = dateB || ''; + var dateATrim = dateA.trim().toLowerCase(); + var dateBTrim = dateB.trim().toLowerCase(); + var reserved = ['current','present','now', '']; + + fmt = (fmt && String.is(fmt) && fmt) || 'YYYY-MM'; + sep = (sep && String.is(sep) && sep) || ' — '; + + if( _.contains( reserved, dateATrim )) { + dateFrom = fallback || '???'; + } + else { + dateTemp = FluentDate.fmt( dateA ); + dateFrom = dateTemp.format( fmt ); + } + + if( _.contains( reserved, dateBTrim )) { + dateTo = fallback || 'Current'; + } + else { + dateTemp = FluentDate.fmt( dateB ); + dateTo = dateTemp.format( fmt ); + } + + if( dateFrom && dateTo ) { + return dateFrom + sep + dateTo; + } + else if( dateFrom || dateTo ) { + return dateFrom || dateTo; + } + + return ''; + } + function skillLevelToIndex( lvl ) { var idx = 0; if( String.is( lvl ) ) { diff --git a/src/helpers/handlebars-helpers.js b/src/helpers/handlebars-helpers.js index d975c4e..216e583 100644 --- a/src/helpers/handlebars-helpers.js +++ b/src/helpers/handlebars-helpers.js @@ -15,9 +15,10 @@ Template helper definitions for Handlebars. Register useful Handlebars helpers. @method registerHelpers */ - module.exports = function( theme ) { + module.exports = function( theme, opts ) { helpers.theme = theme; + helpers.opts = opts; HANDLEBARS.registerHelper( helpers ); }; diff --git a/src/renderers/handlebars-generator.js b/src/renderers/handlebars-generator.js index d295f17..e7b0fa7 100644 --- a/src/renderers/handlebars-generator.js +++ b/src/renderers/handlebars-generator.js @@ -32,7 +32,7 @@ Definition of the HandlebarsGenerator class. generate: function( json, jst, format, cssInfo, opts, theme ) { registerPartials( format, theme ); - registerHelpers( theme ); + registerHelpers( theme, opts ); // Preprocess text var encData = json; @@ -41,7 +41,7 @@ Definition of the HandlebarsGenerator class. // Compile and run the Handlebars template. var template = HANDLEBARS.compile(jst, { strict: false, assumeObjects: false }); - return template({ + return template({ // TODO: Clean r: encData, RAW: json, filt: opts.filters, diff --git a/src/verbs/build.js b/src/verbs/build.js index 592ceed..bcf1382 100644 --- a/src/verbs/build.js +++ b/src/verbs/build.js @@ -157,6 +157,8 @@ Implementation of the 'build' verb for HackMyResume. */ function single( targInfo, theme, finished ) { + var ret, ex; + try { if( !targInfo.fmt ) { return; @@ -178,29 +180,42 @@ 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; - return theFormat.gen.generate( rez, f, _opts ); + _opts.errHandler = this; + ret = theFormat.gen.generate( rez, f, _opts ); } - //Otherwise this is an ad-hoc format (JSON, YML, or PNG) that every theme // gets "for free". else { - theFormat = _fmts.filter( function(fmt) { return fmt.name === targInfo.fmt.outFormat; })[0]; - var outFolder = PATH.dirname( f ); MKDIRP.sync( outFolder ); // Ensure dest folder exists; - - return theFormat.gen.generate( rez, f, _opts ); + _opts.errHandler = this; + ret = theFormat.gen.generate( rez, f, _opts ); } } - catch( ex ) { + catch( e ) { // Catch any errors caused by generating this file and don't let them // propagate -- typically we want to continue processing other formats // even if this format failed. - this.err( HMEVENT.generate, { inner: ex } ); + ex = e; } + + this.stat( HMEVENT.afterGenerate, { + fmt: targInfo.fmt.outFormat, + file: PATH.relative( process.cwd(), f ), + error: ex + }); + + if( ex ) { + if( ex.fluenterror ) + this.err( ex.fluenterror, ex ); + else + this.err( HMSTATUS.generateError, { inner: ex } ); + } + + return ret; }