mirror of
				https://github.com/JuanCanham/HackMyResume.git
				synced 2025-10-31 13:17:26 +00:00 
			
		
		
		
	Refactor error handling.
Work towards better debug/log/stack trace options for error cases.
This commit is contained in:
		| @@ -28,17 +28,52 @@ 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 ){ | ||||
|         var errInfo = get_error_msg( ex ); | ||||
|         msg = errInfo.msg; | ||||
|         exitCode = ex.fluenterror; | ||||
|         showStack = errInfo.showStack; | ||||
|       } | ||||
|       else { | ||||
|         msg = ex.toString(); | ||||
|         exitCode = -1; | ||||
|         // Deal with pesky 'Error:' prefix. | ||||
|         var idx = msg.indexOf('Error: '); | ||||
|         msg = 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()) ); | ||||
|  | ||||
|       // Usually emit the stack | ||||
|       ( showStack && ex.code !== 'ENOENT' ) && log( chalk.gray(ex.stack) ); | ||||
|  | ||||
|       // Let the error code be the process's return code. | ||||
|       ( shouldExit || ex.shouldExit ) && process.exit( exitCode ); | ||||
|     } | ||||
|  | ||||
|   }; | ||||
|  | ||||
|  | ||||
|  | ||||
|   function get_error_msg( ex ) { | ||||
|     var msg = '', withStack = false; | ||||
|     switch( ex.fluenterror ) { | ||||
|  | ||||
|       case HACKMYSTATUS.themeNotFound: | ||||
| @@ -89,41 +124,26 @@ Error-handling routines for HackMyResume. | ||||
|         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; | ||||
|         } | ||||
|         exitCode = ex.fluenterror; | ||||
|  | ||||
|       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; | ||||
|  | ||||
|     } | ||||
|       else { | ||||
|         msg = ex.toString(); | ||||
|         exitCode = 4; | ||||
|       } | ||||
|  | ||||
|       // Deal with pesky 'Error:' prefix. | ||||
|       var idx = msg.indexOf('Error: '); | ||||
|       var trimmed = idx === -1 ? msg : msg.substring( idx + 7 ); | ||||
|  | ||||
|       // 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() ); | ||||
|       } | ||||
|  | ||||
|       // Let the error code be the process's return code. | ||||
|       if( shouldExit || ex.shouldExit ) | ||||
|         process.exit( exitCode ); | ||||
|  | ||||
|     } | ||||
|  | ||||
|     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"]. | ||||
|   | ||||
| @@ -36,6 +36,7 @@ describe('Testing CLI interface', function () { | ||||
|     var ft = 'node_modules/fresh-test-resumes/src/'; | ||||
|  | ||||
|     [ | ||||
|  | ||||
|       [ 'new',      [sb + 'new-fresh-resume.json'], [], opts, ' (FRESH format)' ], | ||||
|       [ 'new',      [sb + 'new-jrs-resume.json'], [], opts2, ' (JRS format)'], | ||||
|       [ 'new',      [sb + 'new-1.json', sb + 'new-2.json', sb + 'new-3.json'], [], opts, ' (multiple FRESH resumes)' ], | ||||
| @@ -48,35 +49,29 @@ describe('Testing CLI interface', function () { | ||||
|       [ 'validate', [sb + 'new-1.json', sb + 'new-jrs-resume.json', sb + 'new-1.json', sb + 'new-2.json', sb + 'new-3.json'], [], opts, ' (5|BOTH)' ], | ||||
|       [ 'analyze',  [ft + 'jane-fullstacker.json'], [], opts, ' (jane-q-fullstacker|FRESH)' ], | ||||
|       [ 'analyze',  ['test/resumes/jrs-0.0.0/richard-hendriks.json'], [], opts2, ' (richard-hendriks|JRS)' ], | ||||
|       [ 'build', | ||||
|         [ ft + 'jane-fullstacker.fresh.json', | ||||
|           ft + 'override/jane-fullstacker-override.fresh.json' ], | ||||
|         [ sb + 'merged/jane-fullstacker-gamedev.fresh.all'], opts, ' (jane-q-fullstacker w/ override|FRESH)' ], | ||||
|       [ 'build', | ||||
|         [ ft + 'jane-fullstacker.fresh.json'], | ||||
|         [ sb + 'shouldnt-exist.pdf' ], | ||||
|         EXTEND(true, opts, { theme: 'awesome' }), | ||||
|         ' (jane-q-fullstacker + Awesome + PDF|FRESH)' ] | ||||
|       [ 'build',    [ ft + 'jane-fullstacker.fresh.json', ft + 'override/jane-fullstacker-override.fresh.json' ], [ sb + 'merged/jane-fullstacker-gamedev.fresh.all'], opts, ' (jane-q-fullstacker w/ override|FRESH)' ], | ||||
|       [ '!build',   [ ft + 'jane-fullstacker.fresh.json'], [ sb + 'shouldnt-exist.pdf' ], EXTEND(true, opts, { theme: 'awesome' }), ' (jane-q-fullstacker + Awesome + PDF|FRESH)' ], | ||||
|       [ '!new',     [], [], opts, " (when a filename isn't specified)" ] | ||||
|  | ||||
|     ].forEach( function(a) { | ||||
|       run.apply( null, a ); | ||||
|  | ||||
|       run.apply( /* The players of */ null, a ); | ||||
|  | ||||
|     }); | ||||
|  | ||||
|     fail( 'new', [], [], opts, " (when a filename isn't specified)" ); | ||||
|  | ||||
|  | ||||
|     function logMsg() { | ||||
|  | ||||
|     } | ||||
|  | ||||
|  | ||||
|  | ||||
|     function run( verb, src, dst, opts, msg ) { | ||||
|       msg = msg || '.'; | ||||
|       it( 'The ' + verb.toUpperCase() + ' command should SUCCEED' + msg, function () { | ||||
|       var shouldSucceed = true; | ||||
|       if( verb[0] === '!' ) { | ||||
|         verb = verb.substr(1); | ||||
|         shouldSucceed = false; | ||||
|       } | ||||
|       it( 'The ' + verb.toUpperCase() + ' command should ' + (shouldSucceed ? ' SUCCEED' : ' FAIL') + msg, function () { | ||||
|         function runIt() { | ||||
|           try { | ||||
|             FCMD.verbs[verb]( src, dst, opts, opts.silent ? | ||||
|               logMsg : function(msg){ msg = msg || ''; console.log(msg); } ); | ||||
|               function(){} : function(msg){ msg = msg || ''; console.log(msg); } ); | ||||
|           } | ||||
|           catch(ex) { | ||||
|             console.error(ex); | ||||
| @@ -84,18 +79,9 @@ describe('Testing CLI interface', function () { | ||||
|             throw ex; | ||||
|           } | ||||
|         } | ||||
|         if( shouldSucceed ) | ||||
|           runIt.should.not.Throw(); | ||||
|       }); | ||||
|     } | ||||
|  | ||||
|  | ||||
|  | ||||
|     function fail( verb, src, dst, opts, msg ) { | ||||
|       msg = msg || '.'; | ||||
|       it( 'The ' + verb.toUpperCase() + ' command should FAIL' + msg, function () { | ||||
|         function runIt() { | ||||
|           FCMD.verbs[verb]( src, dst, opts, logMsg ); | ||||
|         } | ||||
|         else | ||||
|           runIt.should.Throw(); | ||||
|       }); | ||||
|     } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user