mirror of
				https://github.com/JuanCanham/HackMyResume.git
				synced 2025-11-03 22:37:27 +00:00 
			
		
		
		
	Improve error handling.
Better support for spawn errors encountered during generation (for ex, PDFs through wkhtml) + general refactoring.
This commit is contained in:
		
							
								
								
									
										93
									
								
								src/core/error-handler.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								src/core/error-handler.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,93 @@
 | 
			
		||||
/**
 | 
			
		||||
@module error-handler.js
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
(function() {
 | 
			
		||||
 | 
			
		||||
  var HACKMYSTATUS = require('./status-codes')
 | 
			
		||||
    , PKG = require('../../package.json')
 | 
			
		||||
    , title = ('\n*** HackMyResume v' + PKG.version + ' ***').bold.white;
 | 
			
		||||
 | 
			
		||||
  var ErrorHandler = module.exports = {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    err: function( ex, shouldExit ) {
 | 
			
		||||
      var msg = '', exitCode;
 | 
			
		||||
 | 
			
		||||
      if( ex.fluenterror ){
 | 
			
		||||
        switch( ex.fluenterror ) { // TODO: Remove magic numbers
 | 
			
		||||
 | 
			
		||||
          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 = 'Please '.guide + 'specify a valid input resume'.guide.bold +
 | 
			
		||||
              ' in FRESH or JSON Resume format.'.guide;
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
          case HACKMYSTATUS.missingCommand:
 | 
			
		||||
            msg = title + "\nPlease ".guide + "specify a command".guide.bold + " (".guide +
 | 
			
		||||
            Object.keys( FCMD.verbs ).map( function(v, idx, ar) {
 | 
			
		||||
              return (idx === ar.length - 1 ? 'or '.guide : '') +
 | 
			
		||||
                v.toUpperCase().guide;
 | 
			
		||||
            }).join(', '.guide) + ").\n\n".guide +
 | 
			
		||||
              FS.readFileSync( PATH.join(__dirname, 'use.txt'), 'utf8' ).info.bold;
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
          case HACKMYSTATUS.invalidCommand:
 | 
			
		||||
            msg = 'Please '.guide + 'specify the output resume file'.guide.bold +
 | 
			
		||||
              ' that should be created.'.guide;
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
          case HACKMYSTATUS.resumeNotFoundAlt:
 | 
			
		||||
            msg = 'Please '.guide + 'specify a valid input resume'.guide.bold +
 | 
			
		||||
              ' in either FRESH or JSON Resume format.'.guide;
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
          case HACKMYSTATUS.inputOutputParity:
 | 
			
		||||
            msg = 'Please '.guide + 'specify an output file name'.guide.bold +
 | 
			
		||||
              ' for every input file you wish to convert.'.guide;
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
          case HACKMYSTATUS.createNameMissing:
 | 
			
		||||
            msg = 'Please '.guide + 'specify the filename of the resume'.guide.bold +
 | 
			
		||||
              ' to create.'.guide;
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
          case HACKMYSTATUS.wkhtmltopdf:
 | 
			
		||||
            msg = 'ERROR: PDF generation failed. '.red.bold + ('Make sure wkhtmltopdf is ' +
 | 
			
		||||
            'installed and accessible from your path.').red;
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
        exitCode = ex.fluenterror;
 | 
			
		||||
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        msg = ex.toString();
 | 
			
		||||
        exitCode = 4;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      var idx = msg.indexOf('Error: ');
 | 
			
		||||
      var trimmed = idx === -1 ? msg : msg.substring( idx + 7 );
 | 
			
		||||
      if( !ex.fluenterror || ex.fluenterror < 3 ) { // TODO: magic #s
 | 
			
		||||
        console.log( ('ERROR: ' + trimmed.toString()).red.bold );
 | 
			
		||||
        console.log( ex.stack.gray);
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        console.log( trimmed.toString() );
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if( shouldExit )
 | 
			
		||||
        process.exit( exitCode );
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
}());
 | 
			
		||||
							
								
								
									
										22
									
								
								src/core/spawn-watch.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								src/core/spawn-watch.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,22 @@
 | 
			
		||||
/**
 | 
			
		||||
@module spawn-watch.js
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
(function() {
 | 
			
		||||
 | 
			
		||||
// Catch various out-of-band child process errors such as ENOENT for PDFs
 | 
			
		||||
// http://stackoverflow.com/q/27688804
 | 
			
		||||
var SpawnWatcher = module.exports = function() {
 | 
			
		||||
  var childProcess = require("child_process");
 | 
			
		||||
  var oldSpawn = childProcess.spawn;
 | 
			
		||||
  childProcess.spawn = function() {
 | 
			
		||||
    return oldSpawn.apply(this, arguments)
 | 
			
		||||
      .on('error', function(err) {
 | 
			
		||||
        require('./error-handler').err( err, false );
 | 
			
		||||
      });
 | 
			
		||||
  };
 | 
			
		||||
}();
 | 
			
		||||
 | 
			
		||||
//SpawnWatcher();
 | 
			
		||||
 | 
			
		||||
}());
 | 
			
		||||
@@ -1,6 +1,5 @@
 | 
			
		||||
/**
 | 
			
		||||
Definition of the HtmlPdfGenerator class.
 | 
			
		||||
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
 | 
			
		||||
@module html-pdf-generator.js
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
@@ -23,7 +22,7 @@ Definition of the HtmlPdfGenerator class.
 | 
			
		||||
    Generate the binary PDF.
 | 
			
		||||
    */
 | 
			
		||||
    onBeforeSave: function( info ) {
 | 
			
		||||
      pdf( info.mk, info.outputFile );
 | 
			
		||||
      pdf.call( this, info.mk, info.outputFile );
 | 
			
		||||
      return null; // halt further processing
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -34,39 +33,50 @@ Definition of the HtmlPdfGenerator class.
 | 
			
		||||
  */
 | 
			
		||||
  function pdf( markup, fOut ) {
 | 
			
		||||
 | 
			
		||||
    var pdfCount = 0;
 | 
			
		||||
    if( false ) { //( _opts.pdf === 'phantom' || _opts.pdf == 'all' ) {
 | 
			
		||||
      pdfCount++;
 | 
			
		||||
      require('phantom').create( function( ph ) {
 | 
			
		||||
        ph.createPage( function( page ) {
 | 
			
		||||
          page.setContent( markup );
 | 
			
		||||
          page.set('paperSize', {
 | 
			
		||||
            format: 'A4',
 | 
			
		||||
            orientation: 'portrait',
 | 
			
		||||
            margin: '1cm'
 | 
			
		||||
          });
 | 
			
		||||
          page.set("viewportSize", {
 | 
			
		||||
            width: 1024, // TODO: option-ify
 | 
			
		||||
            height: 768 // TODO: Use "A" sizes
 | 
			
		||||
          });
 | 
			
		||||
          page.set('onLoadFinished', function(success) {
 | 
			
		||||
            page.render( fOut );
 | 
			
		||||
            pdfCount++;
 | 
			
		||||
            ph.exit();
 | 
			
		||||
          });
 | 
			
		||||
        },
 | 
			
		||||
        { dnodeOpts: {  weak: false   } } );
 | 
			
		||||
      });
 | 
			
		||||
    pdf_wkhtmltopdf.call( this, markup, fOut );
 | 
			
		||||
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
  Generate a PDF from HTML using wkhtmltopdf.
 | 
			
		||||
  */
 | 
			
		||||
  function pdf_wkhtmltopdf( markup, fOut ) {
 | 
			
		||||
    var wk;
 | 
			
		||||
    try {
 | 
			
		||||
      wk = require('wkhtmltopdf');
 | 
			
		||||
      wk( markup, { pageSize: 'letter' } )
 | 
			
		||||
        .pipe( FS.createWriteStream( fOut ) );
 | 
			
		||||
    }
 | 
			
		||||
    if( true ) { // _opts.pdf === 'wkhtmltopdf' || _opts.pdf == 'all' ) {
 | 
			
		||||
      var fOut2 = fOut;
 | 
			
		||||
      if( pdfCount == 1 ) {
 | 
			
		||||
        fOut2 = fOut2.replace(/\.pdf$/g, '.b.pdf');
 | 
			
		||||
      }
 | 
			
		||||
      require('wkhtmltopdf')( markup, { pageSize: 'letter' } )
 | 
			
		||||
        .pipe( FS.createWriteStream( fOut2 ) );
 | 
			
		||||
        pdfCount++;
 | 
			
		||||
    catch(ex) {
 | 
			
		||||
      // { [Error: write EPIPE] code: 'EPIPE', errno: 'EPIPE', syscall: 'write' }
 | 
			
		||||
      // { [Error: ENOENT] }
 | 
			
		||||
      throw { fluenterror: this.codes.wkhtmltopdf };
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  // function pdf_phantom() {
 | 
			
		||||
  //   pdfCount++;
 | 
			
		||||
  //   require('phantom').create( function( ph ) {
 | 
			
		||||
  //     ph.createPage( function( page ) {
 | 
			
		||||
  //       page.setContent( markup );
 | 
			
		||||
  //       page.set('paperSize', {
 | 
			
		||||
  //         format: 'A4',
 | 
			
		||||
  //         orientation: 'portrait',
 | 
			
		||||
  //         margin: '1cm'
 | 
			
		||||
  //       });
 | 
			
		||||
  //       page.set("viewportSize", {
 | 
			
		||||
  //         width: 1024, // TODO: option-ify
 | 
			
		||||
  //         height: 768 // TODO: Use "A" sizes
 | 
			
		||||
  //       });
 | 
			
		||||
  //       page.set('onLoadFinished', function(success) {
 | 
			
		||||
  //         page.render( fOut );
 | 
			
		||||
  //         pdfCount++;
 | 
			
		||||
  //         ph.exit();
 | 
			
		||||
  //       });
 | 
			
		||||
  //     },
 | 
			
		||||
  //     { dnodeOpts: {  weak: false   } } );
 | 
			
		||||
  //   });
 | 
			
		||||
  // }
 | 
			
		||||
 | 
			
		||||
}());
 | 
			
		||||
 
 | 
			
		||||
@@ -156,7 +156,7 @@ Definition of the TemplateGenerator class.
 | 
			
		||||
              { outputFile: fileName, mk: file.data } );
 | 
			
		||||
          }
 | 
			
		||||
          catch(ex) {
 | 
			
		||||
            console.log(ex);
 | 
			
		||||
            require('../core/error-handler').err(ex, false);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        else if( file.info.action === null/* && theme.explicit*/ ) {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										83
									
								
								src/index.js
									
									
									
									
									
								
							
							
						
						
									
										83
									
								
								src/index.js
									
									
									
									
									
								
							@@ -2,11 +2,14 @@
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
Command-line interface (CLI) for HackMyResume.
 | 
			
		||||
@license MIT. Copyright (c) 2015 James M. Devlin / FluentDesk.
 | 
			
		||||
@license MIT. Copyright (c) 2015 hacksalot (https://github.com/hacksalot)
 | 
			
		||||
@module index.js
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
var ARGS = require( 'minimist' )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
var SPAWNW = require('./core/spawn-watch')
 | 
			
		||||
  , ARGS = require( 'minimist' )
 | 
			
		||||
  , FCMD  = require( './hackmycmd')
 | 
			
		||||
  , PKG = require('../package.json')
 | 
			
		||||
  , COLORS = require('colors')
 | 
			
		||||
@@ -19,11 +22,12 @@ var ARGS = require( 'minimist' )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
try {
 | 
			
		||||
  main();
 | 
			
		||||
}
 | 
			
		||||
catch( ex ) {
 | 
			
		||||
  handleError( ex );
 | 
			
		||||
  require('./core/error-handler').err( ex, true );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -91,76 +95,3 @@ function getOpts( args ) {
 | 
			
		||||
    silent: args.s || args.silent
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TODO: refactor
 | 
			
		||||
function handleError( ex ) {
 | 
			
		||||
  var msg = '', exitCode;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  if( ex.fluenterror ){
 | 
			
		||||
    switch( ex.fluenterror ) { // TODO: Remove magic numbers
 | 
			
		||||
 | 
			
		||||
      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 = 'Please '.guide + 'specify a valid input resume'.guide.bold +
 | 
			
		||||
          ' in FRESH or JSON Resume format.'.guide;
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      case HACKMYSTATUS.missingCommand:
 | 
			
		||||
        msg = title + "\nPlease ".guide + "specify a command".guide.bold + " (".guide +
 | 
			
		||||
        Object.keys( FCMD.verbs ).map( function(v, idx, ar) {
 | 
			
		||||
          return (idx === ar.length - 1 ? 'or '.guide : '') +
 | 
			
		||||
            v.toUpperCase().guide;
 | 
			
		||||
        }).join(', '.guide) + ").\n\n".guide +
 | 
			
		||||
          FS.readFileSync( PATH.join(__dirname, 'use.txt'), 'utf8' ).info.bold;
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      case HACKMYSTATUS.invalidCommand:
 | 
			
		||||
        msg = 'Please '.guide + 'specify the output resume file'.guide.bold +
 | 
			
		||||
          ' that should be created.'.guide;
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      case HACKMYSTATUS.resumeNotFoundAlt:
 | 
			
		||||
        msg = 'Please '.guide + 'specify a valid input resume'.guide.bold +
 | 
			
		||||
          ' in either FRESH or JSON Resume format.'.guide;
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      case HACKMYSTATUS.inputOutputParity:
 | 
			
		||||
        msg = 'Please '.guide + 'specify an output file name'.guide.bold +
 | 
			
		||||
          ' for every input file you wish to convert.'.guide;
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      case HACKMYSTATUS.createNameMissing:
 | 
			
		||||
        msg = 'Please '.guide + 'specify the filename of the resume'.guide.bold +
 | 
			
		||||
          ' to create.'.guide;
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
    exitCode = ex.fluenterror;
 | 
			
		||||
 | 
			
		||||
  }
 | 
			
		||||
  else {
 | 
			
		||||
    msg = ex.toString();
 | 
			
		||||
    exitCode = 4;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  var idx = msg.indexOf('Error: ');
 | 
			
		||||
  var trimmed = idx === -1 ? msg : msg.substring( idx + 7 );
 | 
			
		||||
  if( !ex.fluenterror || ex.fluenterror < 3 ) { // TODO: magic #s
 | 
			
		||||
    console.log( ('ERROR: ' + trimmed.toString()).red.bold );
 | 
			
		||||
    console.log( ex.stack.gray);
 | 
			
		||||
  }
 | 
			
		||||
  else
 | 
			
		||||
    console.log( trimmed.toString() );
 | 
			
		||||
 | 
			
		||||
  process.exit( exitCode );
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
 | 
			
		||||
var chai = require('chai')
 | 
			
		||||
var SPAWNWATCHER = require('../src/core/spawn-watch')
 | 
			
		||||
  , chai = require('chai')
 | 
			
		||||
  , expect = chai.expect
 | 
			
		||||
  , should = chai.should()
 | 
			
		||||
  , path = require('path')
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user