mirror of
https://github.com/JuanCanham/HackMyResume.git
synced 2025-05-02 12:27:08 +01:00
Refactor error handling.
Work towards better debug/log/stack trace options for error cases.
This commit is contained in:
@ -28,102 +28,122 @@ Error-handling routines for HackMyResume.
|
||||
|
||||
|
||||
err: function( ex, shouldExit ) {
|
||||
var msg = '', exitCode;
|
||||
|
||||
var msg = '', exitCode, log = console.log, showStack = false;
|
||||
|
||||
// If the exception has been handled elsewhere and shouldExit is true,
|
||||
// let's get out of here, otherwise silently return.
|
||||
if( ex.handled ) {
|
||||
if( shouldExit )
|
||||
process.exit( exitCode );
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Get an error message -- either a HackMyResume error message or the
|
||||
// exception's associated error message
|
||||
if( ex.fluenterror ){
|
||||
|
||||
switch( ex.fluenterror ) {
|
||||
|
||||
case HACKMYSTATUS.themeNotFound:
|
||||
msg = "The specified theme couldn't be found: " + ex.data;
|
||||
break;
|
||||
|
||||
case HACKMYSTATUS.copyCSS:
|
||||
msg = "Couldn't copy CSS file to destination folder";
|
||||
break;
|
||||
|
||||
case HACKMYSTATUS.resumeNotFound:
|
||||
msg = chalk.yellow('Please ') + chalk.yellow.bold('feed me a resume') +
|
||||
chalk.yellow(' in FRESH or JSON Resume format.');
|
||||
break;
|
||||
|
||||
case HACKMYSTATUS.missingCommand:
|
||||
msg = chalk.yellow("Please ") + chalk.yellow.bold("give me a command") +
|
||||
chalk.yellow(" (");
|
||||
|
||||
msg += Object.keys( FCMD.verbs ).map( function(v, idx, ar) {
|
||||
return (idx === ar.length - 1 ? chalk.yellow('or ') : '') +
|
||||
chalk.yellow.bold(v.toUpperCase());
|
||||
}).join( chalk.yellow(', ')) + chalk.yellow(").\n\n");
|
||||
|
||||
msg += chalk.gray(FS.readFileSync( PATH.resolve(__dirname, '../use.txt'), 'utf8' ));
|
||||
break;
|
||||
|
||||
case HACKMYSTATUS.invalidCommand:
|
||||
msg = chalk.yellow('Invalid command: "') + chalk.yellow.bold(ex.attempted) + chalk.yellow('"');
|
||||
break;
|
||||
|
||||
case HACKMYSTATUS.resumeNotFoundAlt:
|
||||
msg = chalk.yellow('Please ') + chalk.yellow.bold('feed me a resume') +
|
||||
chalk.yellow(' in either FRESH or JSON Resume format.');
|
||||
break;
|
||||
|
||||
case HACKMYSTATUS.inputOutputParity:
|
||||
msg = chalk.yellow('Please ') + chalk.yellow.bold('specify an output file name') +
|
||||
chalk.yellow(' for every input file you wish to convert.');
|
||||
break;
|
||||
|
||||
case HACKMYSTATUS.createNameMissing:
|
||||
msg = chalk.yellow('Please ') + chalk.yellow.bold('specify the filename of the resume') +
|
||||
chalk.yellow(' to create.');
|
||||
break;
|
||||
|
||||
case HACKMYSTATUS.wkhtmltopdf:
|
||||
msg = chalk.red.bold('ERROR: PDF generation failed. ') + chalk.red('Make sure wkhtmltopdf is ' +
|
||||
'installed and accessible from your path.');
|
||||
if( ex.inner ) msg += chalk.red('\n' + ex.inner);
|
||||
break;
|
||||
|
||||
case HACKMYSTATUS.invalid:
|
||||
msg = chalk.red.bold('ERROR: Validation failed and the --assert option was specified.');
|
||||
break;
|
||||
}
|
||||
var errInfo = get_error_msg( ex );
|
||||
msg = errInfo.msg;
|
||||
exitCode = ex.fluenterror;
|
||||
|
||||
showStack = errInfo.showStack;
|
||||
}
|
||||
else {
|
||||
msg = ex.toString();
|
||||
exitCode = 4;
|
||||
exitCode = -1;
|
||||
// Deal with pesky 'Error:' prefix.
|
||||
var idx = msg.indexOf('Error: ');
|
||||
msg = idx === -1 ? msg : msg.substring( idx + 7 );
|
||||
}
|
||||
|
||||
// Deal with pesky 'Error:' prefix.
|
||||
var idx = msg.indexOf('Error: ');
|
||||
var trimmed = idx === -1 ? msg : msg.substring( idx + 7 );
|
||||
// Log non-HackMyResume-handled errors in red with ERROR prefix. Log HMR
|
||||
// errors as-is.
|
||||
ex.fluenterror ?
|
||||
log( msg.toString() ) :
|
||||
log( chalk.red.bold('ERROR: ' + msg.toString()) );
|
||||
|
||||
// If this is an unhandled error, or a specific class of handled error,
|
||||
// output the error message and stack.
|
||||
if( !ex.fluenterror || ex.fluenterror < 3 ) { // TODO: magic #s
|
||||
console.log( chalk.red.bold('ERROR: ' + trimmed.toString()) );
|
||||
if( ex.code !== 'ENOENT' ) // Don't emit stack for common stuff
|
||||
console.log( chalk.gray(ex.stack) );
|
||||
}
|
||||
else {
|
||||
console.log( trimmed.toString() );
|
||||
}
|
||||
// Usually emit the stack
|
||||
( showStack && ex.code !== 'ENOENT' ) && log( chalk.gray(ex.stack) );
|
||||
|
||||
// Let the error code be the process's return code.
|
||||
if( shouldExit || ex.shouldExit )
|
||||
process.exit( exitCode );
|
||||
|
||||
( shouldExit || ex.shouldExit ) && process.exit( exitCode );
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
function get_error_msg( ex ) {
|
||||
var msg = '', withStack = false;
|
||||
switch( ex.fluenterror ) {
|
||||
|
||||
case HACKMYSTATUS.themeNotFound:
|
||||
msg = "The specified theme couldn't be found: " + ex.data;
|
||||
break;
|
||||
|
||||
case HACKMYSTATUS.copyCSS:
|
||||
msg = "Couldn't copy CSS file to destination folder";
|
||||
break;
|
||||
|
||||
case HACKMYSTATUS.resumeNotFound:
|
||||
msg = chalk.yellow('Please ') + chalk.yellow.bold('feed me a resume') +
|
||||
chalk.yellow(' in FRESH or JSON Resume format.');
|
||||
break;
|
||||
|
||||
case HACKMYSTATUS.missingCommand:
|
||||
msg = chalk.yellow("Please ") + chalk.yellow.bold("give me a command") +
|
||||
chalk.yellow(" (");
|
||||
|
||||
msg += Object.keys( FCMD.verbs ).map( function(v, idx, ar) {
|
||||
return (idx === ar.length - 1 ? chalk.yellow('or ') : '') +
|
||||
chalk.yellow.bold(v.toUpperCase());
|
||||
}).join( chalk.yellow(', ')) + chalk.yellow(").\n\n");
|
||||
|
||||
msg += chalk.gray(FS.readFileSync( PATH.resolve(__dirname, '../use.txt'), 'utf8' ));
|
||||
break;
|
||||
|
||||
case HACKMYSTATUS.invalidCommand:
|
||||
msg = chalk.yellow('Invalid command: "') + chalk.yellow.bold(ex.attempted) + chalk.yellow('"');
|
||||
break;
|
||||
|
||||
case HACKMYSTATUS.resumeNotFoundAlt:
|
||||
msg = chalk.yellow('Please ') + chalk.yellow.bold('feed me a resume') +
|
||||
chalk.yellow(' in either FRESH or JSON Resume format.');
|
||||
break;
|
||||
|
||||
case HACKMYSTATUS.inputOutputParity:
|
||||
msg = chalk.yellow('Please ') + chalk.yellow.bold('specify an output file name') +
|
||||
chalk.yellow(' for every input file you wish to convert.');
|
||||
break;
|
||||
|
||||
case HACKMYSTATUS.createNameMissing:
|
||||
msg = chalk.yellow('Please ') + chalk.yellow.bold('specify the filename of the resume') +
|
||||
chalk.yellow(' to create.');
|
||||
break;
|
||||
|
||||
case HACKMYSTATUS.wkhtmltopdf:
|
||||
msg = chalk.red.bold('ERROR: PDF generation failed. ') + chalk.red('Make sure wkhtmltopdf is ' +
|
||||
'installed and accessible from your path.');
|
||||
if( ex.inner ) msg += chalk.red('\n' + ex.inner);
|
||||
withStack = true;
|
||||
break;
|
||||
|
||||
case HACKMYSTATUS.invalid:
|
||||
msg = chalk.red.bold('ERROR: Validation failed and the --assert option was specified.');
|
||||
break;
|
||||
|
||||
case HACKMYSTATUS.invalidTarget:
|
||||
ex.data.forEach(function(d){
|
||||
msg += chalk.red.bold('The ' + ex.theme.name + " theme doesn't support the " + d.format + " format.\n");
|
||||
});
|
||||
break;
|
||||
|
||||
}
|
||||
return {
|
||||
msg: msg,
|
||||
withStack: withStack
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
}());
|
||||
|
@ -18,7 +18,8 @@ Status codes for HackMyResume.
|
||||
createNameMissing: 8,
|
||||
wkhtmltopdf: 9,
|
||||
missingPackageJSON: 10,
|
||||
invalid: 11
|
||||
invalid: 11,
|
||||
invalidTarget: 12
|
||||
};
|
||||
|
||||
}());
|
||||
|
@ -15,6 +15,7 @@ Implementation of the 'generate' verb for HackMyResume.
|
||||
, MD = require('marked')
|
||||
, MKDIRP = require('mkdirp')
|
||||
, EXTEND = require('../utils/extend')
|
||||
, HACKMYSTATUS = require('../core/status-codes')
|
||||
, parsePath = require('parse-filepath')
|
||||
, _opts = require('../core/default-options')
|
||||
, FluentTheme = require('../core/fresh-theme')
|
||||
@ -48,30 +49,19 @@ Implementation of the 'generate' verb for HackMyResume.
|
||||
*/
|
||||
function build( src, dst, opts, logger, errHandler ) {
|
||||
|
||||
// Housekeeping
|
||||
//_opts = extend( true, _opts, opts );
|
||||
_log = logger || console.log;
|
||||
_err = errHandler || error;
|
||||
_opts.theme = (opts.theme && opts.theme.toLowerCase().trim())|| 'modern';
|
||||
_opts.prettify = opts.prettify === true ? _opts.prettify : false;
|
||||
_opts.css = opts.css || 'embed';
|
||||
_opts.pdf = opts.pdf;
|
||||
_opts.wrap = opts.wrap || 60;
|
||||
_opts.stitles = opts.sectionTitles;
|
||||
_opts.tips = opts.tips;
|
||||
//_opts.noTips = opts.noTips;
|
||||
|
||||
// If two or more files are passed to the GENERATE command and the TO
|
||||
// keyword is omitted, the last file specifies the output file.
|
||||
if( src.length > 1 && ( !dst || !dst.length ) ) {
|
||||
dst.push( src.pop() );
|
||||
}
|
||||
prep( src, dst, opts, logger, errHandler );
|
||||
|
||||
// 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 tFolder = verify_theme( _opts.theme );
|
||||
var theme = load_theme( tFolder );
|
||||
|
||||
// Check for invalid outputs
|
||||
var inv = verify_outputs( dst, theme );
|
||||
if( inv && inv.length ) {
|
||||
throw { fluenterror: HACKMYSTATUS.invalidTarget, data: inv, theme: theme };
|
||||
}
|
||||
|
||||
// Load input resumes...
|
||||
if( !src || !src.length ) { throw { fluenterror: 3 }; }
|
||||
var sheets = ResumeFactory.load(src, {
|
||||
@ -134,6 +124,33 @@ Implementation of the 'generate' verb for HackMyResume.
|
||||
|
||||
|
||||
|
||||
/**
|
||||
Prepare for a BUILD run.
|
||||
*/
|
||||
function prep( src, dst, opts, logger, errHandler ) {
|
||||
|
||||
// Housekeeping
|
||||
_log = logger || console.log;
|
||||
_err = errHandler || error;
|
||||
|
||||
//_opts = extend( true, _opts, opts );
|
||||
_opts.theme = (opts.theme && opts.theme.toLowerCase().trim()) || 'modern';
|
||||
_opts.prettify = opts.prettify === true;
|
||||
_opts.css = opts.css || 'embed';
|
||||
_opts.pdf = opts.pdf;
|
||||
_opts.wrap = opts.wrap || 60;
|
||||
_opts.stitles = opts.sectionTitles;
|
||||
_opts.tips = opts.tips;
|
||||
_opts.noTips = opts.noTips;
|
||||
|
||||
// If two or more files are passed to the GENERATE command and the TO
|
||||
// keyword is omitted, the last file specifies the output file.
|
||||
( src.length > 1 && ( !dst || !dst.length ) ) && dst.push( src.pop() );
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
Generate a single target resume such as "out/rez.html" or "out/rez.doc".
|
||||
@param targInfo Information for the target resume.
|
||||
@ -147,6 +164,9 @@ Implementation of the 'generate' verb for HackMyResume.
|
||||
}
|
||||
|
||||
try {
|
||||
if( !targInfo.fmt ) {
|
||||
return;
|
||||
}
|
||||
var f = targInfo.file
|
||||
, fType = targInfo.fmt.outFormat
|
||||
, fName = PATH.basename(f, '.' + fType)
|
||||
@ -242,6 +262,27 @@ Implementation of the 'generate' verb for HackMyResume.
|
||||
|
||||
|
||||
|
||||
/**
|
||||
Ensure that user-specified outputs/targets are valid.
|
||||
*/
|
||||
function verify_outputs( targets, theme ) {
|
||||
|
||||
return _.reject(
|
||||
targets.map( function( t ) {
|
||||
var pathInfo = parsePath( t );
|
||||
return {
|
||||
format: pathInfo.extname.substr(1)
|
||||
};
|
||||
}),
|
||||
function(t) {
|
||||
return t.format === 'all' || theme.hasFormat( parsePath( t.format ).extname.substr(1));
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
Expand output files. For example, "foo.all" should be expanded to
|
||||
["foo.html", "foo.doc", "foo.pdf", "etc"].
|
||||
|
Reference in New Issue
Block a user