From 656dbe2fc2a1bcfc9ac6c52c04c3649a064037ef Mon Sep 17 00:00:00 2001 From: hacksalot Date: Sun, 10 Jan 2016 14:53:22 -0500 Subject: [PATCH] Capture. --- src/cli/out.js | 21 ++++++++++++ src/cli/use.txt | 61 +++++++++++++++++++++++++++------- src/core/error-handler.js | 15 +++++++++ src/core/event-codes.js | 7 +++- src/core/resume-factory.js | 67 ++++++++++++++------------------------ src/core/status-codes.js | 4 ++- src/verbs/build.js | 5 ++- src/verbs/validate.js | 67 +++++++++++++------------------------- 8 files changed, 146 insertions(+), 101 deletions(-) diff --git a/src/cli/out.js b/src/cli/out.js index c271bb2..704247a 100644 --- a/src/cli/out.js +++ b/src/cli/out.js @@ -52,6 +52,10 @@ Output routines for HackMyResume. chalk.green(' resume: ') + chalk.green.bold(evt.file)); break; + case HME.beforeRead: + this.log( chalk.cyan('Reading resume: ' + chalk.bold( evt.file ))); + break; + case HME.afterTheme: this.theme = evt.theme; break; @@ -148,6 +152,23 @@ Output routines for HackMyResume. chalk.green(' (' + evt.srcFmt + ') to ') + chalk.green.bold(evt.dstFile) + chalk.green(' (' + evt.dstFmt + ').')); break; + + case HME.afterValidate: + var style = evt.isValid ? 'green' : 'yellow'; + this.log( chalk.white('Validating ') + chalk.white.bold(evt.file) + chalk.white(' against ') + + chalk.white.bold( evt.fmt ).toUpperCase() + + chalk.white(' schema: ') + chalk[style].bold(evt.isValid ? 'VALID!' : 'INVALID')); + + if( evt.errors ) { + _.each(evt.errors, function(err,idx) { + this.log( chalk.yellow.bold('--> ') + + chalk.yellow(err.field.replace('data.','resume.').toUpperCase() + ' ' + + err.message) ); + }, this); + } + + break; + } } diff --git a/src/cli/use.txt b/src/cli/use.txt index 56f16a8..fe27a71 100644 --- a/src/cli/use.txt +++ b/src/cli/use.txt @@ -2,26 +2,63 @@ Usage: hackmyresume [TO ] [-t ] [-f ] - should be BUILD, NEW, CONVERT, VALIDATE, ANALYZE or HELP. -should be the path to one or more FRESH or JSON Resume format resumes. -should be the name of the destination resume to be created, if any. The + should be BUILD, ANALYZE, NEW, CONVERT, or VALIDATE. should +be the path to one or more FRESH or JSON Resume format resumes. should +be the name of the destination resume to be created, if any. The parameter should be the name of a predefined theme (for example: COMPACT, MINIMIST, MODERN, or HELLO-WORLD) or the relative path to a custom theme. should be either FRESH (for a FRESH-format resume) or JRS (for a JSON Resume-format resume). - hackmyresume BUILD resume.json TO out/resume.all - hackmyresume NEW resume.json - hackmyresume CONVERT resume.json TO resume-jrs.json - hackmyresume ANALYZE resume.json - hackmyresume VALIDATE resume.json + hackmyresume BUILD resume.json TO out/resume.all + hackmyresume ANALYZE resume.json + hackmyresume NEW resume.json + hackmyresume CONVERT resume.json TO resume-jrs.json + hackmyresume VALIDATE resume.json Both SOURCES and TARGETS can accept multiple files: - hackmyresume BUILD r1.json r2.json TO out/resume.all out2/resume.html - hackmyresume NEW r1.json r2.json r3.json - hackmyresume ANALYZE r1.json r2.json r3.json - hackmyresume VALIDATE resume.json resume2.json resume3.json + hackmyresume BUILD r1.json r2.json TO out/resume.all out2/resume.html + hackmyresume ANALYZE r1.json r2.json r3.json + hackmyresume NEW r1.json r2.json r3.json + hackmyresume CONVERT r1.json r2.json TO o1.json o2.json + hackmyresume VALIDATE r1.json r2.json r3.json + +Available options: + + --theme Specify a theme for the BUILD command. Can be the path + -t to any FRESH or JSON Resume theme, or the name of one + of HackMyResume's predefined themes. + + + --pdf Specify a PDF rendering engine. Can be "wkhtmltopdf", + -p "phantom", or "none". Requires that the corresponding + engine be installed and path-accessible. + + + --opts Load HackMyResume options via an external file. + -o + + + --color Enable/disable terminal colors. + --no-color + + + --tips Enable/disable theme tips and messages. + --no-tips + + + --debug Emit debug info. + -d + + + --help Display help documentation. + -h + + + --version Display the current HackMyResume version. + -v + See https://github.com/hacksalot/hackmyresume/blob/master/README.md for more information. diff --git a/src/core/error-handler.js b/src/core/error-handler.js index 1b732ad..c89f0f1 100644 --- a/src/core/error-handler.js +++ b/src/core/error-handler.js @@ -156,6 +156,21 @@ Error-handling routines for HackMyResume. " is inaccessible. PDF not generated." ); break; + case HACKMYSTATUS.readError: + msg = formatError( ex.inner.toString() ); + break; + + case HACKMYSTATUS.parseError: + if( SyntaxErrorEx.is( ex.inner )) { + var se = new SyntaxErrorEx( ex, ex.raw ); + msg = formatError( 'Invalid or corrupt JSON on line ' + se.line + + ' column ' + se.col ); + } + else { + msg = formatError( ex.inner.toString() ); + } + break; + } return { msg: msg, diff --git a/src/core/event-codes.js b/src/core/event-codes.js index f2961c6..13c0e2d 100644 --- a/src/core/event-codes.js +++ b/src/core/event-codes.js @@ -40,7 +40,12 @@ Event code definitions. afterAnalyze: 14, beforeConvert: 15, - afterConvert: 16 + afterConvert: 16, + + verifyOutputs: 17, + + beforeParse: 18, + afterParse: 19 }; diff --git a/src/core/resume-factory.js b/src/core/resume-factory.js index c6f0584..a881f4d 100644 --- a/src/core/resume-factory.js +++ b/src/core/resume-factory.js @@ -10,11 +10,14 @@ Definition of the ResumeFactory class. - require('string.prototype.startswith'); - var FS = require('fs'); - var ResumeConverter = require('./convert'); - var chalk = require('chalk'); - var SyntaxErrorEx = require('../utils/syntax-error-ex'); + var FS = require('fs'), + HACKMYSTATUS = require('./status-codes'), + HME = require('./event-codes'), + ResumeConverter = require('./convert'), + chalk = require('chalk'), + SyntaxErrorEx = require('../utils/syntax-error-ex'), + _ = require('underscore'); + require('string.prototype.startswith'); @@ -29,14 +32,11 @@ Definition of the ResumeFactory class. /** Load one or more resumes from disk. */ - load: function ( sources, opts ) { + load: function ( sources, opts, emitter ) { - // Loop over all inputs, parsing each to JSON and then to a FRESHResume - // or JRSResume object. - var that = this; return sources.map( function( src ) { - return that.loadOne( src, opts ); - }); + return this.loadOne( src, opts, emitter ); + }, this); }, @@ -45,9 +45,8 @@ Definition of the ResumeFactory class. /** Load a single resume from disk. */ - loadOne: function( src, opts ) { + loadOne: function( src, opts, emitter ) { - var log = opts.log; var toFormat = opts.format; var objectify = opts.objectify; @@ -55,7 +54,7 @@ Definition of the ResumeFactory class. toFormat && (toFormat = toFormat.toLowerCase().trim()); // Load and parse the resume JSON - var info = _parse( src, opts ); + var info = _parse( src, opts, emitter ); if( info.error ) return info; // Determine the resume format: FRESH or JRS @@ -88,44 +87,28 @@ Definition of the ResumeFactory class. - function _parse( fileName, opts ) { + function _parse( fileName, opts, eve ) { + var rawData; try { - // TODO: Core should not log - opts.log( chalk.cyan('Reading resume: ') + chalk.cyan.bold(fileName) ); - - // Read the file + eve && eve.stat( HME.beforeRead, { file: fileName }); rawData = FS.readFileSync( fileName, 'utf8' ); - - // Parse it to JSON - return { + eve && eve.stat( HME.afterRead, { data: rawData }); + eve && eve.stat( HME.beforeParse, { data: rawData }); + var ret = { json: JSON.parse( rawData ) }; - + eve && eve.stat( HME.afterParse, { data: ret.json } ); + return ret; } catch( ex ) { - - // JSON.parse failed due to invalid JSON - if ( !opts.muffle && ex instanceof SyntaxError) { - var info = new SyntaxErrorEx( ex, rawData ); - opts.log( chalk.red.bold(fileName.toUpperCase() + ' contains invalid JSON on line ' + - info.line + ' column ' + info.col + '.' + - chalk.red(' Unable to validate.'))); - opts.log( chalk.red.bold('INTERNAL: ' + ex) ); - ex.handled = true; - } - - // FS.readFileSync failed - if( !rawData || opts.throw ) throw ex; - - return { - error: ex, - raw: rawData, - file: fileName + throw { + fluenterror: rawData ? HACKMYSTATUS.parseError : HACKMYSTATUS.readError, + inner: ex, raw: rawData, file: fileName, shouldExit: false }; - } + } diff --git a/src/core/status-codes.js b/src/core/status-codes.js index aade38b..42d1857 100644 --- a/src/core/status-codes.js +++ b/src/core/status-codes.js @@ -20,7 +20,9 @@ Status codes for HackMyResume. missingPackageJSON: 10, invalid: 11, invalidFormat: 12, - notOnPath: 13 + notOnPath: 13, + readError: 14, + parseError: 15 }; }()); diff --git a/src/verbs/build.js b/src/verbs/build.js index 3eeaa81..82d1a67 100644 --- a/src/verbs/build.js +++ b/src/verbs/build.js @@ -31,6 +31,7 @@ Implementation of the 'generate' verb for HackMyResume. , _err, _log, rez; + var BuildVerb = module.exports = Verb.extend({ init: function() { @@ -64,7 +65,7 @@ Implementation of the 'generate' verb for HackMyResume. this.stat( HME.afterTheme, { theme: theme }); // Check for invalid outputs - var inv = verifyOutputs( dst, theme ); + var inv = verifyOutputs.call( this, dst, theme ); if( inv && inv.length ) { throw {fluenterror: HACKMYSTATUS.invalidFormat, data: inv, theme: theme}; } @@ -181,6 +182,8 @@ Implementation of the 'generate' verb for HackMyResume. */ function verifyOutputs( targets, theme ) { + this.stat(HME.verifyOutputs, { targets: targets, theme: theme }); + return _.reject( targets.map( function( t ) { var pathInfo = parsePath( t ); diff --git a/src/verbs/validate.js b/src/verbs/validate.js index 51ad436..f97d62b 100644 --- a/src/verbs/validate.js +++ b/src/verbs/validate.js @@ -12,6 +12,8 @@ Implementation of the 'validate' verb for HackMyResume. var chalk = require('chalk'); var Verb = require('../core/verb'); var HACKMYSTATUS = require('../core/status-codes'); + var HME = require('../core/event-codes'); + var _ = require('underscore'); @@ -45,74 +47,51 @@ Implementation of the 'validate' verb for HackMyResume. var resumes = ResumeFactory.load( sources, { format: null, - objectify: false, - throw: false, - muffle: true - }); + objectify: false + }, this ); - // Load input resumes... - resumes.forEach(function( src ) { + // Validate input resumes. Return a { file: , isValid: } object for + // each resume (valid, invalid, or broken). + return resumes.map( function( src ) { + var ret = { file: src, isValid: false }; + + // If there was an error reading the resume if( src.error ) { - // TODO: Core should not log - _log( chalk.white('Validating ') + chalk.gray.bold(src.file) + - chalk.white(' against ') + chalk.gray.bold('AUTO') + - chalk.white(' schema:') + chalk.red.bold(' BROKEN') ); - - var ex = src.error; // alias - if ( ex instanceof SyntaxError) { - var info = new SyntaxErrorEx( ex, src.raw ); - _log( chalk.red.bold('--> ' + src.file.toUpperCase() + ' contains invalid JSON on line ' + - info.line + ' column ' + info.col + '.' + - chalk.red(' Unable to validate.') ) ); - _log( chalk.red.bold(' INTERNAL: ' + ex) ); - } - else { - _log(chalk.red.bold('ERROR: ' + ex.toString())); - } if( opts.assert ) throw { fluenterror: HACKMYSTATUS.invalid }; - return; + return ret; } - var json = src.json; - var isValid = false; - var style = 'green'; - var errors = []; - var fmt = json.basics ? 'jrs' : 'fresh'; - + // Successfully read the resume. Now parse it as JSON. + var json = src.json, fmt = json.basics ? 'jrs' : 'fresh', errors = []; try { var validate = validator( schemas[ fmt ], { // Note [1] formats: { - date: /^\d{4}(?:-(?:0[0-9]{1}|1[0-2]{1})(?:-[0-9]{2})?)?$/ + date: /^\d{4}(?:-(?:0[0-9]{1}|1[0-2]{1})(?:-[0-9]{2})?)?$/ } }); - isValid = validate( json ); - if( !isValid ) { - style = 'yellow'; + ret.isValid = validate( json ); + if( !ret.isValid ) { errors = validate.errors; } } catch(exc) { - return; + return ret; } - _log( chalk.white('Validating ') + chalk.white.bold(src.file) + chalk.white(' against ') + - chalk.white.bold(fmt.replace('jars','JSON Resume').toUpperCase()) + - chalk.white(' schema: ') + chalk[style].bold(isValid ? 'VALID!' : 'INVALID') ); - - errors.forEach(function(err,idx) { - _log( chalk.yellow.bold('--> ') + - chalk.yellow(err.field.replace('data.','resume.').toUpperCase() + ' ' + - err.message) ); - }); + this.stat(HME.afterValidate, { file: src.file, isValid: isValid, + fmt: fmt.replace('jars', 'JSON Resume'), errors: errors }); if( opts.assert && !isValid ) { throw { fluenterror: HACKMYSTATUS.invalid, shouldExit: true }; } - }); + return ret; + + }, this); + } }());