diff --git a/dist/cli/error.js b/dist/cli/error.js index 73964e3..431c051 100644 --- a/dist/cli/error.js +++ b/dist/cli/error.js @@ -258,6 +258,10 @@ Error-handling routines for HackMyResume. case HMSTATUS.optionsFileNotFound: msg = M2C(this.msgs.optionsFileNotFound.msg); etype = 'error'; + break; + case HMSTATUS.unknownSchema: + msg = M2C(this.msgs.unknownSchema.msg[0]); + etype = 'error'; } return { msg: msg, diff --git a/dist/cli/main.js b/dist/cli/main.js index bc388ce..391f2a2 100644 --- a/dist/cli/main.js +++ b/dist/cli/main.js @@ -257,9 +257,16 @@ Definition of the `main` function. executeFail = function(err) { var finalErrorCode, msgs; + console.dir(err); finalErrorCode = -1; if (err) { - finalErrorCode = err.fluenterror ? err.fluenterror : err; + if (err.fluenterror) { + finalErrorCode = err.fluenterror; + } else if (err.length) { + finalErrorCode = err[0].fluenterror; + } else { + finalErrorCode = err; + } } if (_opts.debug) { msgs = require('./msg').errors; diff --git a/dist/cli/msg.yml b/dist/cli/msg.yml index 015ba90..b2d9ca2 100644 --- a/dist/cli/msg.yml +++ b/dist/cli/msg.yml @@ -115,3 +115,20 @@ errors: - "\nMake sure the options file contains valid JSON." optionsFileNotFound: msg: "The specified options file is missing or inaccessible." + unknownSchema: + msg: + - "Unknown resume schema. Did you specify a valid FRESH or JRS resume?" + - | + At a minimum, a FRESH resume must include a "name" field and a "meta" + property. + + "name": "John Doe", + "meta": { + "format": "FRESH@0.1.0" + } + + JRS-format resumes must include a "basics" section with a "name": + + "basics": { + "name": "John Doe" + } diff --git a/dist/cli/out.js b/dist/cli/out.js index 43b7843..8dbc757 100644 --- a/dist/cli/out.js +++ b/dist/cli/out.js @@ -128,7 +128,7 @@ Output routines for HackMyResume. output = template(info); return this.log(chalk.cyan(output)); case HME.beforeConvert: - return L(M2C(this.msgs.beforeConvert.msg, 'green'), evt.srcFile, evt.srcFmt, evt.dstFile, evt.dstFmt); + return L(M2C(this.msgs.beforeConvert.msg, evt.error ? 'red' : 'green'), evt.srcFile, evt.srcFmt, evt.dstFile, evt.dstFmt); case HME.afterInlineConvert: return L(M2C(this.msgs.afterInlineConvert.msg, 'gray', 'white.dim'), evt.file, evt.fmt); case HME.afterValidate: diff --git a/dist/core/resume-factory.js b/dist/core/resume-factory.js index 93430e8..ca11c95 100644 --- a/dist/core/resume-factory.js +++ b/dist/core/resume-factory.js @@ -6,11 +6,11 @@ Definition of the ResumeFactory class. */ (function() { - var FS, HACKMYSTATUS, HME, ResumeConverter, ResumeFactory, SyntaxErrorEx, _, _parse, chalk; + var FS, HME, HMS, ResumeConverter, ResumeFactory, SyntaxErrorEx, _, _parse, chalk, resumeDetect; FS = require('fs'); - HACKMYSTATUS = require('./status-codes'); + HMS = require('./status-codes'); HME = require('./event-codes'); @@ -22,6 +22,8 @@ Definition of the ResumeFactory class. _ = require('underscore'); + resumeDetect = require('../utils/resume-detector'); + require('string.prototype.startswith'); @@ -54,7 +56,7 @@ Definition of the ResumeFactory class. /** Load a single resume from disk. */ loadOne: function(src, opts, emitter) { - var ResumeClass, info, isFRESH, json, objectify, orgFormat, rez, toFormat; + var ResumeClass, info, json, objectify, orgFormat, reqLib, rez, toFormat; toFormat = opts.format; objectify = opts.objectify; toFormat && (toFormat = toFormat.toLowerCase().trim()); @@ -63,14 +65,18 @@ Definition of the ResumeFactory class. return info; } json = info.json; - isFRESH = json.meta && json.meta.format && json.meta.format.startsWith('FRESH@'); - orgFormat = isFRESH ? 'fresh' : 'jrs'; + orgFormat = resumeDetect(json); + if (orgFormat === 'unk') { + info.fluenterror = HMS.unknownSchema; + return info; + } if (toFormat && (orgFormat !== toFormat)) { json = ResumeConverter['to' + toFormat.toUpperCase()](json); } rez = null; if (objectify) { - ResumeClass = require('../core/' + (toFormat || orgFormat) + '-resume'); + reqLib = '../core/' + (toFormat || orgFormat) + '-resume'; + ResumeClass = require(reqLib); rez = new ResumeClass().parseJSON(json, opts.inner); rez.i().file = src; } @@ -109,7 +115,7 @@ Definition of the ResumeFactory class. return ret; } catch (_error) { return { - fluenterror: rawData ? HACKMYSTATUS.parseError : HACKMYSTATUS.readError, + fluenterror: rawData ? HMS.parseError : HMS.readError, inner: _error, raw: rawData, file: fileName diff --git a/dist/core/status-codes.js b/dist/core/status-codes.js index ec2ff1e..4cd2be7 100644 --- a/dist/core/status-codes.js +++ b/dist/core/status-codes.js @@ -35,7 +35,8 @@ Status codes for HackMyResume. createError: 25, validateError: 26, invalidOptionsFile: 27, - optionsFileNotFound: 28 + optionsFileNotFound: 28, + unknownSchema: 29 }; }).call(this); diff --git a/dist/renderers/handlebars-generator.js b/dist/renderers/handlebars-generator.js index 2848b2d..8955215 100644 --- a/dist/renderers/handlebars-generator.js +++ b/dist/renderers/handlebars-generator.js @@ -34,13 +34,12 @@ Definition of the HandlebarsGenerator class. HandlebarsGenerator = module.exports = { generateSimple: function(data, tpl) { - var noesc, template; + var template; try { - noesc = data.opts.noescape || false; template = HANDLEBARS.compile(tpl, { strict: false, assumeObjects: false, - noEscape: noesc + noEscape: data.opts.noescape || false }); return template(data); } catch (_error) { diff --git a/dist/utils/resume-detector.js b/dist/utils/resume-detector.js new file mode 100644 index 0000000..b1adcf6 --- /dev/null +++ b/dist/utils/resume-detector.js @@ -0,0 +1,21 @@ + +/** +Definition of the ResumeDetector class. +@module utils/resume-detector +@license MIT. See LICENSE.md for details. + */ + +(function() { + module.exports = function(rez) { + if (rez.meta && rez.meta.format) { + return 'fresh'; + } else if (rez.basics) { + return 'jrs'; + } else { + return 'unk'; + } + }; + +}).call(this); + +//# sourceMappingURL=resume-detector.js.map diff --git a/dist/verbs/convert.js b/dist/verbs/convert.js index ade42f8..cedd0a3 100644 --- a/dist/verbs/convert.js +++ b/dist/verbs/convert.js @@ -65,11 +65,12 @@ Implementation of the 'convert' verb for HackMyResume. quit: true }); } + if (this.hasError()) { + this.reject(this.errorCode); + return null; + } results = _.map(srcs, function(src, idx) { var r; - if (opts.assert && this.hasError()) { - return {}; - } r = _convertOne.call(this, src, dst, idx); if (r.fluenterror) { r.quit = opts.assert; @@ -89,16 +90,31 @@ Implementation of the 'convert' verb for HackMyResume. /** Private workhorse method. Convert a single resume. */ _convertOne = function(src, dst, idx) { - var rinfo, s, srcFmt, targetFormat; + var rez, rinfo, srcFmt, targetFormat; rinfo = ResumeFactory.loadOne(src, { format: null, objectify: true }); if (rinfo.fluenterror) { + this.stat(HMEVENT.beforeConvert, { + srcFile: src, + srcFmt: '???', + dstFile: dst[idx], + dstFmt: '???', + error: true + }); + return rinfo; + } + rez = rinfo.rez; + srcFmt = ''; + if (rez.meta && rez.meta.format) { + srcFmt = 'FRESH'; + } else if (rez.basics) { + srcFmt = 'JRS'; + } else { + rinfo.fluenterror = HMSTATUS.unknownSchema; return rinfo; } - s = rinfo.rez; - srcFmt = ((s.basics && s.basics.imp) || s.imp).orgFormat === 'JRS' ? 'JRS' : 'FRESH'; targetFormat = srcFmt === 'JRS' ? 'FRESH' : 'JRS'; this.stat(HMEVENT.beforeConvert, { srcFile: rinfo.file, @@ -106,8 +122,8 @@ Implementation of the 'convert' verb for HackMyResume. dstFile: dst[idx], dstFmt: targetFormat }); - s.saveAs(dst[idx], targetFormat); - return s; + rez.saveAs(dst[idx], targetFormat); + return rez; }; }).call(this); diff --git a/src/cli/error.coffee b/src/cli/error.coffee index 5398e68..d130616 100644 --- a/src/cli/error.coffee +++ b/src/cli/error.coffee @@ -248,6 +248,11 @@ assembleError = ( ex ) -> msg = M2C( @msgs.optionsFileNotFound.msg ) etype = 'error' + when HMSTATUS.unknownSchema + msg = M2C( @msgs.unknownSchema.msg[0] ) + #msg += "\n" + M2C( @msgs.unknownSchema.msg[1], 'yellow' ) + etype = 'error' + msg: msg # The error message to display withStack: withStack # Whether to include the stack quit: quit diff --git a/src/cli/main.coffee b/src/cli/main.coffee index ad61b38..dbbf1f0 100644 --- a/src/cli/main.coffee +++ b/src/cli/main.coffee @@ -262,7 +262,7 @@ execute = ( src, dst, opts, log ) -> v = new HMR.verbs[ @name() ]() # Initialize command-specific options - loadOptions.call( this, opts, this.parent.jsonArgs ) + loadOptions.call this, opts, this.parent.jsonArgs # Set up error/output handling _opts.errHandler = v @@ -288,9 +288,15 @@ executeSuccess = (obj) -> ### Failure handler for verb invocations. Calls process.exit by default ### executeFail = (err) -> + console.dir err finalErrorCode = -1 if err - finalErrorCode = if err.fluenterror then err.fluenterror else err + if err.fluenterror + finalErrorCode = err.fluenterror + else if err.length + finalErrorCode = err[0].fluenterror + else + finalErrorCode = err if _opts.debug msgs = require('./msg').errors; logMsg printf M2C( msgs.exiting.msg, 'cyan' ), finalErrorCode diff --git a/src/cli/msg.yml b/src/cli/msg.yml index 015ba90..b2d9ca2 100644 --- a/src/cli/msg.yml +++ b/src/cli/msg.yml @@ -115,3 +115,20 @@ errors: - "\nMake sure the options file contains valid JSON." optionsFileNotFound: msg: "The specified options file is missing or inaccessible." + unknownSchema: + msg: + - "Unknown resume schema. Did you specify a valid FRESH or JRS resume?" + - | + At a minimum, a FRESH resume must include a "name" field and a "meta" + property. + + "name": "John Doe", + "meta": { + "format": "FRESH@0.1.0" + } + + JRS-format resumes must include a "basics" section with a "name": + + "basics": { + "name": "John Doe" + } diff --git a/src/cli/out.coffee b/src/cli/out.coffee index 3d997cc..9c18e1f 100644 --- a/src/cli/out.coffee +++ b/src/cli/out.coffee @@ -131,7 +131,7 @@ module.exports = class OutputHandler @log( chalk.cyan(output) ) when HME.beforeConvert - L( M2C( this.msgs.beforeConvert.msg, 'green' ), + L( M2C( this.msgs.beforeConvert.msg, if evt.error then 'red' else 'green' ), evt.srcFile, evt.srcFmt, evt.dstFile, evt.dstFmt ); diff --git a/src/core/resume-factory.coffee b/src/core/resume-factory.coffee index f336b81..328c74a 100644 --- a/src/core/resume-factory.coffee +++ b/src/core/resume-factory.coffee @@ -6,14 +6,15 @@ Definition of the ResumeFactory class. -FS = require('fs') -HACKMYSTATUS = require('./status-codes') -HME = require('./event-codes') -ResumeConverter = require('fresh-jrs-converter') -chalk = require('chalk') -SyntaxErrorEx = require('../utils/syntax-error-ex') -_ = require('underscore') -require('string.prototype.startswith') +FS = require 'fs' +HMS = require './status-codes' +HME = require './event-codes' +ResumeConverter = require 'fresh-jrs-converter' +chalk = require 'chalk' +SyntaxErrorEx = require '../utils/syntax-error-ex' +_ = require 'underscore' +resumeDetect = require '../utils/resume-detector' +require 'string.prototype.startswith' @@ -62,20 +63,23 @@ ResumeFactory = module.exports = # Determine the resume format: FRESH or JRS json = info.json - isFRESH = json.meta && json.meta.format && json.meta.format.startsWith('FRESH@'); - orgFormat = if isFRESH then 'fresh' else 'jrs' + orgFormat = resumeDetect json + if orgFormat == 'unk' + info.fluenterror = HMS.unknownSchema + return info # Convert between formats if necessary if toFormat and ( orgFormat != toFormat ) - json = ResumeConverter[ 'to' + toFormat.toUpperCase() ]( json ) + json = ResumeConverter[ 'to' + toFormat.toUpperCase() ] json # Objectify the resume, that is, convert it from JSON to a FRESHResume # or JRSResume object. rez = null if objectify - ResumeClass = require('../core/' + (toFormat || orgFormat) + '-resume'); - rez = new ResumeClass().parseJSON( json, opts.inner ); - rez.i().file = src; + reqLib = '../core/' + (toFormat || orgFormat) + '-resume' + ResumeClass = require reqLib + rez = new ResumeClass().parseJSON( json, opts.inner ) + rez.i().file = src file: src json: info.json @@ -103,7 +107,7 @@ _parse = ( fileName, opts, eve ) -> return ret catch # Can be ENOENT, EACCES, SyntaxError, etc. - fluenterror: if rawData then HACKMYSTATUS.parseError else HACKMYSTATUS.readError + fluenterror: if rawData then HMS.parseError else HMS.readError inner: _error raw: rawData file: fileName diff --git a/src/core/status-codes.coffee b/src/core/status-codes.coffee index d19c344..992d90b 100644 --- a/src/core/status-codes.coffee +++ b/src/core/status-codes.coffee @@ -35,3 +35,4 @@ module.exports = validateError: 26 invalidOptionsFile: 27 optionsFileNotFound: 28 + unknownSchema: 29 diff --git a/src/utils/resume-detector.coffee b/src/utils/resume-detector.coffee new file mode 100644 index 0000000..f2cf0f7 --- /dev/null +++ b/src/utils/resume-detector.coffee @@ -0,0 +1,13 @@ +###* +Definition of the ResumeDetector class. +@module utils/resume-detector +@license MIT. See LICENSE.md for details. +### + +module.exports = ( rez ) -> + if rez.meta && rez.meta.format #&& rez.meta.format.substr(0, 5).toUpperCase() == 'FRESH' + 'fresh' + else if rez.basics + 'jrs' + else + 'unk' diff --git a/src/verbs/convert.coffee b/src/verbs/convert.coffee index a88a8d7..9fdebc1 100644 --- a/src/verbs/convert.coffee +++ b/src/verbs/convert.coffee @@ -26,10 +26,14 @@ formats. ### _convert = ( srcs, dst, opts ) -> - # Housekeeping... + # If no source resumes are specified, error out if !srcs || !srcs.length @err HMSTATUS.resumeNotFound, { quit: true } return null + + # If no destination resumes are specified, error out except for the special + # case of two resumes: + # hackmyresume CONVERT r1.json r2.json if !dst || !dst.length if srcs.length == 1 @err HMSTATUS.inputOutputParity, { quit: true } @@ -37,19 +41,30 @@ _convert = ( srcs, dst, opts ) -> dst = dst || []; dst.push( srcs.pop() ) else @err HMSTATUS.inputOutputParity, { quit: true } + + # Different number of source and dest resumes? Error out. if srcs && dst && srcs.length && dst.length && srcs.length != dst.length @err HMSTATUS.inputOutputParity, { quit: true } - # Load source resumes + # If any errors have occurred this early, we're done. + if @hasError() + @reject @errorCode + return null + + # Map each source resume to the converted destination resume results = _.map srcs, ( src, idx ) -> - return { } if opts.assert and @hasError() + + # Convert each resume in turn r = _convertOne.call @, src, dst, idx + + # Handle conversion errors if r.fluenterror r.quit = opts.assert @err r.fluenterror, r r , @ + if @hasError() and !opts.assert @reject results else if !@hasError() @@ -60,17 +75,31 @@ _convert = ( srcs, dst, opts ) -> ###* Private workhorse method. Convert a single resume. ### _convertOne = (src, dst, idx) -> + # Load the resume rinfo = ResumeFactory.loadOne src, format: null, objectify: true # If a load error occurs, report it and move on to the next file (if any) if rinfo.fluenterror + @stat HMEVENT.beforeConvert, + srcFile: src #rinfo.file + srcFmt: '???' + dstFile: dst[idx] + dstFmt: '???' + error: true + #@err rinfo.fluenterror, rinfo + return rinfo + + rez = rinfo.rez + srcFmt = '' + if rez.meta && rez.meta.format #&& rez.meta.format.substr(0, 5).toUpperCase() == 'FRESH' + srcFmt = 'FRESH' + else if rez.basics + srcFmt = 'JRS' + else + rinfo.fluenterror = HMSTATUS.unknownSchema return rinfo - s = rinfo.rez - srcFmt = - if ((s.basics && s.basics.imp) || s.imp).orgFormat == 'JRS' - then 'JRS' else 'FRESH' targetFormat = if srcFmt == 'JRS' then 'FRESH' else 'JRS' this.stat HMEVENT.beforeConvert, @@ -80,5 +109,5 @@ _convertOne = (src, dst, idx) -> dstFmt: targetFormat # Save it to the destination format - s.saveAs dst[idx], targetFormat - s + rez.saveAs dst[idx], targetFormat + rez diff --git a/src/verbs/verb.coffee b/src/verbs/verb.coffee index 436bee4..1f7b233 100644 --- a/src/verbs/verb.coffee +++ b/src/verbs/verb.coffee @@ -32,12 +32,20 @@ module.exports = class Verb ###* Invoke the command. ### invoke: -> + + # Sent the 'begin' notification for this verb @stat HMEVENT.begin, cmd: @moniker + + # Prepare command arguments argsArray = Array::slice.call arguments + + # Create a promise for this verb instance that = @ @promise = new Promise (res, rej) -> - that.resolve = res; that.reject = rej - that.workhorse.apply that, argsArray; return + that.resolve = res + that.reject = rej + that.workhorse.apply that, argsArray + return diff --git a/test/scripts/test-hmr.txt b/test/scripts/test-hmr.txt index fb88bef..f14d46a 100644 --- a/test/scripts/test-hmr.txt +++ b/test/scripts/test-hmr.txt @@ -13,6 +13,10 @@ 0|analyze node_modules/fresh-test-resumes/src/fresh/johnny-trouble.json 3|convert 7|convert doesnt-exist.json +14|convert from.json to.json +7|convert z1.json z2.json z3.json +7|convert z1.json z2.json z3.json z4.json +14|convert z1.json z2.json to z3.json z4.json 3|validate 14|validate doesnt-exist.json 0|validate node_modules/fresh-test-resumes/src/fresh/jane-fullstacker.json