mirror of
https://github.com/JuanCanham/HackMyResume.git
synced 2025-05-02 12:27:08 +01:00
Asynchrony.
This commit is contained in:
@ -22,10 +22,8 @@ require 'string.prototype.startswith'
|
||||
|
||||
|
||||
|
||||
###*
|
||||
Error handler for HackMyResume. All errors are handled here.
|
||||
@class ErrorHandler
|
||||
###
|
||||
###* Error handler for HackMyResume. All errors are handled here.
|
||||
@class ErrorHandler ###
|
||||
ErrorHandler = module.exports =
|
||||
|
||||
init: ( debug, assert, silent ) ->
|
||||
@ -38,7 +36,7 @@ ErrorHandler = module.exports =
|
||||
err: ( ex, shouldExit ) ->
|
||||
|
||||
# Short-circuit logging output if --silent is on
|
||||
o = if this.silent then () -> else _defaultLog
|
||||
o = if @silent then () -> else _defaultLog
|
||||
|
||||
# Special case; can probably be removed.
|
||||
throw ex if ex.pass
|
||||
@ -51,7 +49,7 @@ ErrorHandler = module.exports =
|
||||
|
||||
# Output the error message
|
||||
objError = assembleError.call @, ex
|
||||
o( this[ 'format_' + objError.etype ]( objError.msg ))
|
||||
o( @[ 'format_' + objError.etype ]( objError.msg ))
|
||||
|
||||
# Output the stack (sometimes)
|
||||
if objError.withStack
|
||||
@ -59,20 +57,20 @@ ErrorHandler = module.exports =
|
||||
stack && o( chalk.gray( stack ) );
|
||||
|
||||
# Quit if necessary
|
||||
if ex.quit || objError.quit
|
||||
if shouldExit
|
||||
if @debug
|
||||
o chalk.cyan('Exiting with error code ' + ex.fluenterror.toString())
|
||||
if this.assert
|
||||
if @assert
|
||||
ex.pass = true
|
||||
throw ex
|
||||
process.exit ex.fluenterror
|
||||
|
||||
# Handle raw exceptions
|
||||
else
|
||||
o( ex )
|
||||
o ex
|
||||
stackTrace = ex.stack || (ex.inner && ex.inner.stack)
|
||||
if stackTrace && this.debug
|
||||
o( M2C(ex.stack || ex.inner.stack, 'gray') )
|
||||
o M2C(ex.stack || ex.inner.stack, 'gray')
|
||||
|
||||
|
||||
|
||||
@ -214,6 +212,11 @@ assembleError = ( ex ) ->
|
||||
msg = ex
|
||||
etype = 'error'
|
||||
|
||||
when HMSTATUS.createError
|
||||
# inner.code could be EPERM, EACCES, etc
|
||||
msg = printf M2C( this.msgs.createError.msg ), ex.inner.path
|
||||
etype = 'error'
|
||||
|
||||
msg: msg # The error message to display
|
||||
withStack: withStack # Whether to include the stack
|
||||
quit: quit
|
||||
|
@ -23,6 +23,8 @@ Command = require('commander').Command
|
||||
_opts = { }
|
||||
_title = chalk.white.bold('\n*** HackMyResume v' +PKG.version+ ' ***')
|
||||
_out = new OUTPUT( _opts )
|
||||
_err = require('./error')
|
||||
_exitCallback = null
|
||||
|
||||
|
||||
|
||||
@ -33,9 +35,9 @@ line interface as a single method accepting a parameter array.
|
||||
@param rawArgs {Array} An array of command-line parameters. Will either be
|
||||
process.argv (in production) or custom parameters (in test).
|
||||
###
|
||||
main = module.exports = (rawArgs) ->
|
||||
main = module.exports = ( rawArgs, exitCallback ) ->
|
||||
|
||||
initInfo = initialize( rawArgs )
|
||||
initInfo = initialize( rawArgs, exitCallback )
|
||||
args = initInfo.args
|
||||
|
||||
# Create the top-level (application) command...
|
||||
@ -129,10 +131,10 @@ main = module.exports = (rawArgs) ->
|
||||
|
||||
|
||||
### Massage command-line args and setup Commander.js. ###
|
||||
initialize = ( ar ) ->
|
||||
|
||||
o = initOptions( ar );
|
||||
initialize = ( ar, exitCallback ) ->
|
||||
|
||||
_exitCallback = exitCallback || process.exit
|
||||
o = initOptions ar
|
||||
o.silent || logMsg( _title )
|
||||
|
||||
# Emit debug prelude if --debug was specified
|
||||
@ -147,9 +149,11 @@ initialize = ( ar ) ->
|
||||
#_out.log(chalk.cyan(PAD(' fresh-jrs-converter:',25, null, PAD.RIGHT)) + chalk.cyan.bold( PKG.dependencies['fresh-jrs-converter'] ))
|
||||
_out.log('')
|
||||
|
||||
_err.init o.debug, o.assert, o.silent
|
||||
|
||||
# Handle invalid verbs here (a bit easier here than in commander.js)...
|
||||
if o.verb && !HMR.verbs[ o.verb ] && !HMR.alias[ o.verb ]
|
||||
throw { fluenterror: HMSTATUS.invalidCommand, quit: true, attempted: o.orgVerb }
|
||||
_err.err fluenterror: HMSTATUS.invalidCommand, quit: true, attempted: o.orgVerb, true
|
||||
|
||||
# Override the .missingArgument behavior
|
||||
Command.prototype.missingArgument = (name) ->
|
||||
@ -205,23 +209,17 @@ initOptions = ( ar ) ->
|
||||
oJSON = inf.json
|
||||
# TODO: Error handling
|
||||
|
||||
# Grab the --debug flag
|
||||
isDebug = _.some( args, (v) ->
|
||||
return v == '-d' || v == '--debug'
|
||||
)
|
||||
|
||||
# Grab the --silent flag
|
||||
isSilent = _.some( args, (v) ->
|
||||
return v == '-s' || v == '--silent'
|
||||
)
|
||||
|
||||
# Grab the --no-color flag
|
||||
# Grab the --debug flag, --silent, --assert and --no-color flags
|
||||
isDebug = _.some args, (v) -> v == '-d' || v == '--debug'
|
||||
isSilent = _.some args, (v) -> v == '-s' || v == '--silent'
|
||||
isAssert = _.some args, (v) -> v == '-a' || v == '--assert'
|
||||
isMono = _.some args, (v) -> v == '--no-color'
|
||||
|
||||
return {
|
||||
color: !isMono,
|
||||
debug: isDebug,
|
||||
silent: isSilent,
|
||||
assert: isAssert,
|
||||
orgVerb: oVerb,
|
||||
verb: verb,
|
||||
json: oJSON,
|
||||
@ -233,19 +231,29 @@ initOptions = ( ar ) ->
|
||||
### Invoke a HackMyResume verb. ###
|
||||
execute = ( src, dst, opts, log ) ->
|
||||
|
||||
loadOptions.call( this, opts, this.parent.jsonArgs )
|
||||
hand = require( './error' )
|
||||
hand.init( _opts.debug, _opts.assert, _opts.silent )
|
||||
v = new HMR.verbs[ this.name() ]()
|
||||
_opts.errHandler = v
|
||||
_out.init( _opts )
|
||||
v.on( 'hmr:status', -> _out.do.apply( _out, arguments ) )
|
||||
v.on( 'hmr:error', -> hand.err.apply( hand, arguments ) )
|
||||
v.invoke.call( v, src, dst, _opts, log )
|
||||
if v.errorCode
|
||||
console.log 'Exiting with error code ' + v.errorCode
|
||||
process.exit(v.errorCode)
|
||||
# Create the verb
|
||||
v = new HMR.verbs[ @name() ]()
|
||||
|
||||
# Initialize command-specific options
|
||||
loadOptions.call( this, opts, this.parent.jsonArgs )
|
||||
|
||||
# Set up error/output handling
|
||||
_opts.errHandler = v
|
||||
_out.init _opts
|
||||
|
||||
# Hook up event notifications
|
||||
v.on 'hmr:status', -> _out.do.apply _out, arguments
|
||||
v.on 'hmr:error', -> _err.err.apply _err, arguments
|
||||
|
||||
# Invoke the verb! Returns a promise
|
||||
prom = v.invoke.call v, src, dst, _opts, log
|
||||
|
||||
# Resolved or rejected?
|
||||
onFail = (err) ->
|
||||
_exitCallback( if err.fluenterror then err.fluenterror else err )
|
||||
return
|
||||
prom.then (->), onFail
|
||||
return
|
||||
|
||||
|
||||
|
||||
|
@ -3,6 +3,8 @@ events:
|
||||
msg: Invoking **%s** command.
|
||||
beforeCreate:
|
||||
msg: Creating new **%s** resume: **%s**
|
||||
afterCreate:
|
||||
msg: Creating new **%s** resume: **%s**
|
||||
afterRead:
|
||||
msg: Reading **%s** resume: **%s**
|
||||
beforeTheme:
|
||||
@ -96,3 +98,5 @@ errors:
|
||||
msg: "Invalid number of parameters. Expected: **%s**."
|
||||
missingParam:
|
||||
msg: The '**%s**' parameter was needed but not supplied.
|
||||
createError:
|
||||
msg: Failed to create **'%s'**.
|
||||
|
@ -50,8 +50,12 @@ OutputHandler = module.exports = Class.extend
|
||||
this.opts.debug &&
|
||||
L( M2C( this.msgs.begin.msg, dbgStyle), evt.cmd.toUpperCase() )
|
||||
|
||||
when HME.beforeCreate
|
||||
L( M2C( this.msgs.beforeCreate.msg, 'green' ), evt.fmt, evt.file )
|
||||
#when HME.beforeCreate
|
||||
#L( M2C( this.msgs.beforeCreate.msg, 'green' ), evt.fmt, evt.file )
|
||||
#break;
|
||||
|
||||
when HME.afterCreate
|
||||
L( M2C( @msgs.beforeCreate.msg, if evt.isError then 'red' else 'green' ), evt.fmt, evt.file )
|
||||
break;
|
||||
|
||||
when HME.beforeTheme
|
||||
|
@ -103,13 +103,7 @@ _parse = ( fileName, opts, eve ) ->
|
||||
return ret
|
||||
catch
|
||||
# Can be ENOENT, EACCES, SyntaxError, etc.
|
||||
ex =
|
||||
fluenterror: if rawData then HACKMYSTATUS.parseError else HACKMYSTATUS.readError
|
||||
inner: _error
|
||||
raw: rawData
|
||||
file: fileName
|
||||
shouldExit: false
|
||||
opts.quit && (ex.quit = true)
|
||||
eve && eve.err ex.fluenterror, ex
|
||||
throw ex if opts.throw
|
||||
ex
|
||||
fluenterror: if rawData then HACKMYSTATUS.parseError else HACKMYSTATUS.readError
|
||||
inner: _error
|
||||
raw: rawData
|
||||
file: fileName
|
||||
|
@ -31,3 +31,4 @@ module.exports =
|
||||
themeLoad: 22
|
||||
invalidParamCount: 23
|
||||
missingParam: 24
|
||||
createError: 25
|
||||
|
@ -24,9 +24,11 @@ If an engine isn't installed for a particular platform, error out gracefully.
|
||||
HtmlPdfCLIGenerator = module.exports = TemplateGenerator.extend
|
||||
|
||||
|
||||
|
||||
init: () -> @_super 'pdf', 'html'
|
||||
|
||||
|
||||
|
||||
###* Generate the binary PDF. ###
|
||||
onBeforeSave: ( info ) ->
|
||||
safe_eng = info.opts.pdf || 'wkhtmltopdf';
|
||||
@ -38,8 +40,14 @@ HtmlPdfCLIGenerator = module.exports = TemplateGenerator.extend
|
||||
engines[ safe_eng ].call @, info.mk, info.outputFile, @onError
|
||||
return null # halt further processing
|
||||
|
||||
|
||||
|
||||
### Low-level error callback for spawn(). May be called after HMR process
|
||||
termination, so object references may not be valid here. That's okay; if
|
||||
the references are invalid, the error was already logged. We could use
|
||||
spawn-watch here but that causes issues on legacy Node.js. ###
|
||||
onError: (ex, param) ->
|
||||
param.errHandler.err HMSTATUS.pdfGeneration, ex
|
||||
param.errHandler?.err? HMSTATUS.pdfGeneration, ex
|
||||
return
|
||||
|
||||
|
||||
|
@ -28,3 +28,4 @@ JsonYamlGenerator = module.exports = BaseGenerator.extend
|
||||
generate: ( rez, f, opts ) ->
|
||||
data = YAML.stringify JSON.parse( rez.stringify() ), Infinity, 2
|
||||
FS.writeFileSync f, data, 'utf8'
|
||||
data
|
||||
|
@ -16,40 +16,44 @@ Verb = require('../verbs/verb')
|
||||
chalk = require('chalk')
|
||||
|
||||
|
||||
|
||||
###* An invokable resume analysis command. ###
|
||||
AnalyzeVerb = module.exports = Verb.extend
|
||||
|
||||
init: -> @_super 'analyze', analyze
|
||||
init: -> @_super 'analyze', _analyze
|
||||
|
||||
|
||||
|
||||
###*
|
||||
Run the 'analyze' command.
|
||||
###
|
||||
analyze = ( sources, dst, opts ) ->
|
||||
###* Private workhorse for the 'analyze' command. ###
|
||||
_analyze = ( sources, dst, opts ) ->
|
||||
|
||||
if !sources || !sources.length
|
||||
throw
|
||||
fluenterror: HMSTATUS.resumeNotFound
|
||||
quit: true
|
||||
@err HMSTATUS.resumeNotFound, { quit: true }
|
||||
return null
|
||||
|
||||
nlzrs = _loadInspectors()
|
||||
results = _.map sources, (src) ->
|
||||
r = ResumeFactory.loadOne src, format: 'FRESH', objectify: true, @
|
||||
return { } if opts.assert and @hasError()
|
||||
|
||||
_.each(sources, (src) ->
|
||||
result = ResumeFactory.loadOne src, format: 'FRESH', objectify: true, @
|
||||
if result.fluenterror
|
||||
this.setError result.fluenterror, result
|
||||
if r.fluenterror
|
||||
r.quit = opts.assert
|
||||
@err r.fluenterror, r
|
||||
r
|
||||
else
|
||||
_analyze.call @, result, nlzrs, opts
|
||||
, @)
|
||||
_analyzeOne.call @, r, nlzrs, opts
|
||||
, @
|
||||
|
||||
|
||||
if @hasError() and !opts.assert
|
||||
@reject @errorCode
|
||||
else if !@hasError()
|
||||
@resolve results
|
||||
results
|
||||
|
||||
|
||||
|
||||
###*
|
||||
Analyze a single resume.
|
||||
###
|
||||
_analyze = ( resumeObject, nlzrs, opts ) ->
|
||||
|
||||
###* Analyze a single resume. ###
|
||||
_analyzeOne = ( resumeObject, nlzrs, opts ) ->
|
||||
rez = resumeObject.rez
|
||||
safeFormat =
|
||||
if rez.meta and rez.meta.format and rez.meta.format.startsWith 'FRESH'
|
||||
@ -58,12 +62,10 @@ _analyze = ( resumeObject, nlzrs, opts ) ->
|
||||
this.stat( HMEVENT.beforeAnalyze, { fmt: safeFormat, file: resumeObject.file })
|
||||
info = _.mapObject nlzrs, (val, key) -> val.run rez
|
||||
this.stat HMEVENT.afterAnalyze, { info: info }
|
||||
info
|
||||
|
||||
|
||||
|
||||
###*
|
||||
Load inspectors.
|
||||
###
|
||||
_loadInspectors = ->
|
||||
totals: require '../inspectors/totals-inspector'
|
||||
coverage: require '../inspectors/gap-inspector'
|
||||
|
@ -6,25 +6,25 @@ Implementation of the 'build' verb for HackMyResume.
|
||||
|
||||
|
||||
|
||||
_ = require('underscore')
|
||||
PATH = require('path')
|
||||
FS = require('fs')
|
||||
MD = require('marked')
|
||||
MKDIRP = require('mkdirp')
|
||||
extend = require('extend')
|
||||
parsePath = require('parse-filepath')
|
||||
RConverter = require('fresh-jrs-converter')
|
||||
HMSTATUS = require('../core/status-codes')
|
||||
HMEVENT = require('../core/event-codes')
|
||||
_ = require 'underscore'
|
||||
PATH = require 'path'
|
||||
FS = require 'fs'
|
||||
MD = require 'marked'
|
||||
MKDIRP = require 'mkdirp'
|
||||
extend = require 'extend'
|
||||
parsePath = require 'parse-filepath'
|
||||
RConverter = require 'fresh-jrs-converter'
|
||||
HMSTATUS = require '../core/status-codes'
|
||||
HMEVENT = require '../core/event-codes'
|
||||
RTYPES =
|
||||
FRESH: require('../core/fresh-resume')
|
||||
JRS: require('../core/jrs-resume')
|
||||
_opts = require('../core/default-options')
|
||||
FRESHTheme = require('../core/fresh-theme')
|
||||
JRSTheme = require('../core/jrs-theme')
|
||||
ResumeFactory = require('../core/resume-factory')
|
||||
_fmts = require('../core/default-formats')
|
||||
Verb = require('../verbs/verb')
|
||||
FRESH: require '../core/fresh-resume'
|
||||
JRS: require '../core/jrs-resume'
|
||||
_opts = require '../core/default-options'
|
||||
FRESHTheme = require '../core/fresh-theme'
|
||||
JRSTheme = require '../core/jrs-theme'
|
||||
ResumeFactory = require '../core/resume-factory'
|
||||
_fmts = require '../core/default-formats'
|
||||
Verb = require '../verbs/verb'
|
||||
|
||||
_err = null
|
||||
_log = null
|
||||
@ -42,7 +42,7 @@ loadTheme = null
|
||||
BuildVerb = module.exports = Verb.extend
|
||||
|
||||
###* Create a new build verb. ###
|
||||
init: () -> @_super 'build', build
|
||||
init: () -> @_super 'build', _build
|
||||
|
||||
|
||||
|
||||
@ -53,23 +53,23 @@ theme file, generate 0..N resumes in the desired formats.
|
||||
@param dst An array of paths to the target resume file(s).
|
||||
@param opts Generation options.
|
||||
###
|
||||
build = ( src, dst, opts ) ->
|
||||
_build = ( src, dst, opts ) ->
|
||||
|
||||
if !src || !src.length
|
||||
@err HMSTATUS.resumeNotFound, quit: true
|
||||
return null
|
||||
|
||||
prep src, dst, opts
|
||||
_prep src, dst, opts
|
||||
|
||||
# Load input resumes as JSON...
|
||||
sheetObjects = ResumeFactory.load(src, {
|
||||
sheetObjects = ResumeFactory.load src,
|
||||
format: null, objectify: false, quit: true, inner: { sort: _opts.sort }
|
||||
}, @);
|
||||
, @
|
||||
|
||||
# Explicit check for any resume loading errors...
|
||||
problemSheets = _.filter( sheetObjects, (so) -> return so.fluenterror )
|
||||
if( problemSheets && problemSheets.length )
|
||||
problemSheets[0].quit = true
|
||||
problemSheets = _.filter sheetObjects, (so) -> so.fluenterror
|
||||
if problemSheets and problemSheets.length
|
||||
problemSheets[0].quit = true # can't go on
|
||||
@err problemSheets[0].fluenterror, problemSheets[0]
|
||||
return null
|
||||
|
||||
@ -80,27 +80,27 @@ build = ( src, dst, opts ) ->
|
||||
theme = null
|
||||
@stat HMEVENT.beforeTheme, { theme: _opts.theme }
|
||||
try
|
||||
tFolder = verifyTheme.call @, _opts.theme
|
||||
theme = _opts.themeObj = loadTheme tFolder
|
||||
addFreebieFormats theme
|
||||
catch ex
|
||||
tFolder = _verifyTheme.call @, _opts.theme
|
||||
theme = _opts.themeObj = _loadTheme tFolder
|
||||
_addFreebieFormats theme
|
||||
catch
|
||||
newEx =
|
||||
fluenterror: HMSTATUS.themeLoad
|
||||
inner: ex
|
||||
inner: _error
|
||||
attempted: _opts.theme
|
||||
quit: true
|
||||
@err HMSTATUS.themeLoad, newEx
|
||||
return null
|
||||
|
||||
@stat HMEVENT.afterTheme, { theme: theme }
|
||||
@stat HMEVENT.afterTheme, theme: theme
|
||||
|
||||
# Check for invalid outputs...
|
||||
inv = verifyOutputs.call @, dst, theme
|
||||
inv = _verifyOutputs.call @, dst, theme
|
||||
if inv && inv.length
|
||||
@err HMSTATUS.invalidFormat, data: inv, theme: theme, quit: true
|
||||
return null
|
||||
|
||||
## Merge input resumes, yielding a single source resume.
|
||||
## Merge input resumes, yielding a single source resume...
|
||||
rez = null
|
||||
if sheets.length > 1
|
||||
isFRESH = !sheets[0].basics
|
||||
@ -108,15 +108,13 @@ build = ( src, dst, opts ) ->
|
||||
@stat HMEVENT.beforeMerge, { f: _.clone(sheetObjects), mixed: mixed }
|
||||
if mixed
|
||||
@err HMSTATUS.mixedMerge
|
||||
|
||||
rez = _.reduceRight sheets, ( a, b, idx ) ->
|
||||
extend( true, b, a )
|
||||
|
||||
@stat HMEVENT.afterMerge, { r: rez }
|
||||
else
|
||||
rez = sheets[0];
|
||||
|
||||
# Convert the merged source resume to the theme's format, if necessary
|
||||
# Convert the merged source resume to the theme's format, if necessary..
|
||||
orgFormat = if rez.basics then 'JRS' else 'FRESH';
|
||||
toFormat = if theme.render then 'JRS' else 'FRESH';
|
||||
if toFormat != orgFormat
|
||||
@ -125,30 +123,41 @@ build = ( src, dst, opts ) ->
|
||||
@stat HMEVENT.afterInlineConvert, { file: sheetObjects[0].file, fmt: toFormat }
|
||||
|
||||
# Announce the theme
|
||||
@stat HMEVENT.applyTheme, { r: rez, theme: theme }
|
||||
@stat HMEVENT.applyTheme, r: rez, theme: theme
|
||||
|
||||
# Load the resume into a FRESHResume or JRSResume object
|
||||
_rezObj = new (RTYPES[ toFormat ])().parseJSON( rez );
|
||||
|
||||
# Expand output resumes...
|
||||
targets = expand( dst, theme );
|
||||
targets = _expand dst, theme
|
||||
|
||||
# Run the transformation!
|
||||
_.each targets, (t) ->
|
||||
t.final = single.call this, t, theme, targets
|
||||
return { } if @hasError() and opts.assert
|
||||
t.final = _single.call @, t, theme, targets
|
||||
if t.final.fluenterror
|
||||
t.final.quit = opts.assert
|
||||
@err t.final.fluenterror, t.final
|
||||
return
|
||||
, @
|
||||
|
||||
# Don't send the client back empty-handed
|
||||
sheet: _rezObj
|
||||
targets: targets
|
||||
processed: targets
|
||||
results =
|
||||
sheet: _rezObj
|
||||
targets: targets
|
||||
processed: targets
|
||||
|
||||
if @hasError() and !opts.assert
|
||||
@reject results
|
||||
else if !@hasError()
|
||||
@resolve results
|
||||
results
|
||||
|
||||
|
||||
|
||||
###*
|
||||
Prepare for a BUILD run.
|
||||
###
|
||||
prep = ( src, dst, opts ) ->
|
||||
_prep = ( src, dst, opts ) ->
|
||||
|
||||
# Cherry-pick options //_opts = extend( true, _opts, opts );
|
||||
_opts.theme = (opts.theme && opts.theme.toLowerCase().trim()) || 'modern';
|
||||
@ -176,7 +185,7 @@ TODO: Refactor.
|
||||
@param targInfo Information for the target resume.
|
||||
@param theme A FRESHTheme or JRSTheme object.
|
||||
###
|
||||
single = ( targInfo, theme, finished ) ->
|
||||
_single = ( targInfo, theme, finished ) ->
|
||||
|
||||
ret = null
|
||||
ex = null
|
||||
@ -207,12 +216,13 @@ single = ( targInfo, theme, finished ) ->
|
||||
# Otherwise this is an ad-hoc format (JSON, YML, or PNG) that every theme
|
||||
# gets "for free".
|
||||
else
|
||||
|
||||
theFormat = _fmts.filter( (fmt) ->
|
||||
return fmt.name == targInfo.fmt.outFormat
|
||||
)[0];
|
||||
outFolder = PATH.dirname f
|
||||
MKDIRP.sync( outFolder ); # Ensure dest folder exists;
|
||||
ret = theFormat.gen.generate( _rezObj, f, _opts );
|
||||
MKDIRP.sync outFolder # Ensure dest folder exists;
|
||||
ret = theFormat.gen.generate _rezObj, f, _opts
|
||||
|
||||
catch e
|
||||
# Catch any errors caused by generating this file and don't let them
|
||||
@ -227,16 +237,15 @@ single = ( targInfo, theme, finished ) ->
|
||||
|
||||
if ex
|
||||
if ex.fluenterror
|
||||
this.err( ex.fluenterror, ex );
|
||||
ret = ex
|
||||
else
|
||||
this.err( HMSTATUS.generateError, { inner: ex } );
|
||||
|
||||
return ret
|
||||
ret = fluenterror: HMSTATUS.generateError, inner: ex
|
||||
ret
|
||||
|
||||
|
||||
|
||||
###* Ensure that user-specified outputs/targets are valid. ###
|
||||
verifyOutputs = ( targets, theme ) ->
|
||||
_verifyOutputs = ( targets, theme ) ->
|
||||
@stat HMEVENT.verifyOutputs, { targets: targets, theme: theme }
|
||||
_.reject targets.map( ( t ) ->
|
||||
pathInfo = parsePath t
|
||||
@ -256,7 +265,7 @@ that declares an HTML format; the theme doesn't have to provide an explicit
|
||||
PNG template.
|
||||
@param theTheme A FRESHTheme or JRSTheme object.
|
||||
###
|
||||
addFreebieFormats = ( theTheme ) ->
|
||||
_addFreebieFormats = ( theTheme ) ->
|
||||
# Add freebie formats (JSON, YAML, PNG) every theme gets...
|
||||
# Add HTML-driven PNG only if the theme has an HTML format.
|
||||
theTheme.formats.json = theTheme.formats.json || {
|
||||
@ -282,7 +291,7 @@ Expand output files. For example, "foo.all" should be expanded to
|
||||
@param dst An array of output files as specified by the user.
|
||||
@param theTheme A FRESHTheme or JRSTheme object.
|
||||
###
|
||||
expand = ( dst, theTheme ) ->
|
||||
_expand = ( dst, theTheme ) ->
|
||||
|
||||
# Set up the destination collection. It's either the array of files passed
|
||||
# by the user or 'out/resume.all' if no targets were specified.
|
||||
@ -309,7 +318,7 @@ expand = ( dst, theTheme ) ->
|
||||
###*
|
||||
Verify the specified theme name/path.
|
||||
###
|
||||
verifyTheme = ( themeNameOrPath ) ->
|
||||
_verifyTheme = ( themeNameOrPath ) ->
|
||||
tFolder = PATH.join(
|
||||
parsePath( require.resolve('fresh-themes') ).dirname,
|
||||
'/themes/',
|
||||
@ -328,7 +337,7 @@ verifyTheme = ( themeNameOrPath ) ->
|
||||
Load the specified theme, which could be either a FRESH theme or a JSON Resume
|
||||
theme.
|
||||
###
|
||||
loadTheme = ( tFolder ) ->
|
||||
_loadTheme = ( tFolder ) ->
|
||||
|
||||
# Create a FRESH or JRS theme object
|
||||
theTheme =
|
||||
|
@ -17,56 +17,68 @@ HMEVENT = require('../core/event-codes');
|
||||
|
||||
ConvertVerb = module.exports = Verb.extend
|
||||
|
||||
init: -> @_super 'convert', convert
|
||||
init: -> @_super 'convert', _convert
|
||||
|
||||
|
||||
|
||||
###*
|
||||
Convert between FRESH and JRS formats.
|
||||
###
|
||||
convert = ( srcs, dst, opts ) ->
|
||||
###* Private workhorse method. Convert 0..N resumes between FRESH and JRS
|
||||
formats. ###
|
||||
|
||||
# Housekeeping
|
||||
throw { fluenterror: 6, quit: true } if !srcs || !srcs.length
|
||||
_convert = ( srcs, dst, opts ) ->
|
||||
|
||||
# Housekeeping...
|
||||
if !srcs || !srcs.length
|
||||
@err HMSTATUS.resumeNotFound, { quit: true }
|
||||
return null
|
||||
if !dst || !dst.length
|
||||
if srcs.length == 1
|
||||
throw { fluenterror: HMSTATUS.inputOutputParity, quit: true };
|
||||
@err HMSTATUS.inputOutputParity, { quit: true }
|
||||
else if srcs.length == 2
|
||||
dst = dst || []; dst.push( srcs.pop() )
|
||||
else
|
||||
throw fluenterror: HMSTATUS.inputOutputParity, quit: true
|
||||
|
||||
@err HMSTATUS.inputOutputParity, { quit: true }
|
||||
if srcs && dst && srcs.length && dst.length && srcs.length != dst.length
|
||||
throw fluenterror: HMSTATUS.inputOutputParity quit: true
|
||||
@err HMSTATUS.inputOutputParity, { quit: true }
|
||||
|
||||
# Load source resumes
|
||||
_.each(srcs, ( src, idx ) ->
|
||||
results = _.map srcs, ( src, idx ) ->
|
||||
return { } if opts.assert and @hasError()
|
||||
r = _convertOne.call @, src, dst, idx
|
||||
if r.fluenterror
|
||||
r.quit = opts.assert
|
||||
@err r.fluenterror, r
|
||||
r
|
||||
, @
|
||||
|
||||
# Load the resume
|
||||
rinfo = ResumeFactory.loadOne src,
|
||||
format: null, objectify: true, throw: false
|
||||
if @hasError() and !opts.assert
|
||||
@reject results
|
||||
else if !@hasError()
|
||||
@resolve results
|
||||
results
|
||||
|
||||
# If a load error occurs, report it and move on to the next file (if any)
|
||||
if rinfo.fluenterror
|
||||
this.err rinfo.fluenterror, rinfo
|
||||
return
|
||||
|
||||
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,
|
||||
srcFile: rinfo.file
|
||||
srcFmt: srcFmt
|
||||
dstFile: dst[idx]
|
||||
dstFmt: targetFormat
|
||||
###* Private workhorse method. Convert a single resume. ###
|
||||
_convertOne = (src, dst, idx) ->
|
||||
# Load the resume
|
||||
rinfo = ResumeFactory.loadOne src, format: null, objectify: true
|
||||
|
||||
# Save it to the destination format
|
||||
s.saveAs dst[idx], targetFormat
|
||||
return
|
||||
# If a load error occurs, report it and move on to the next file (if any)
|
||||
if rinfo.fluenterror
|
||||
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'
|
||||
|
||||
return
|
||||
this.stat HMEVENT.beforeConvert,
|
||||
srcFile: rinfo.file
|
||||
srcFmt: srcFmt
|
||||
dstFile: dst[idx]
|
||||
dstFmt: targetFormat
|
||||
|
||||
# Save it to the destination format
|
||||
s.saveAs dst[idx], targetFormat
|
||||
s
|
||||
|
@ -5,37 +5,64 @@ Implementation of the 'create' verb for HackMyResume.
|
||||
###
|
||||
|
||||
|
||||
MKDIRP = require('mkdirp')
|
||||
PATH = require('path')
|
||||
chalk = require('chalk')
|
||||
Verb = require('../verbs/verb')
|
||||
_ = require('underscore')
|
||||
HMSTATUS = require('../core/status-codes')
|
||||
HMEVENT = require('../core/event-codes')
|
||||
|
||||
MKDIRP = require 'mkdirp'
|
||||
PATH = require 'path'
|
||||
chalk = require 'chalk'
|
||||
Verb = require '../verbs/verb'
|
||||
_ = require 'underscore'
|
||||
HMSTATUS = require '../core/status-codes'
|
||||
HMEVENT = require '../core/event-codes'
|
||||
|
||||
|
||||
|
||||
CreateVerb = module.exports = Verb.extend
|
||||
|
||||
init: -> @_super 'new', create
|
||||
init: -> @_super 'new', _create
|
||||
|
||||
|
||||
|
||||
###*
|
||||
Create a new empty resume in either FRESH or JRS format.
|
||||
###
|
||||
create = ( src, dst, opts ) ->
|
||||
###* Create a new empty resume in either FRESH or JRS format. ###
|
||||
_create = ( src, dst, opts ) ->
|
||||
|
||||
if !src || !src.length
|
||||
throw { fluenterror: HMSTATUS.createNameMissing, quit: true }
|
||||
@err HMSTATUS.createNameMissing, { quit: true }
|
||||
return null
|
||||
|
||||
_.each( src, ( t ) ->
|
||||
results = _.map src, ( t ) ->
|
||||
return { } if opts.assert and @hasError()
|
||||
r = _createOne.call @, t, opts
|
||||
if r.fluenterror
|
||||
r.quit = opts.assert
|
||||
@err r.fluenterror, r
|
||||
r
|
||||
, @
|
||||
|
||||
if @hasError() and !opts.assert
|
||||
@reject results
|
||||
else if !@hasError()
|
||||
@resolve results
|
||||
results
|
||||
|
||||
|
||||
|
||||
###* Create a single new resume ###
|
||||
_createOne = ( t, opts ) ->
|
||||
try
|
||||
ret = null
|
||||
safeFmt = opts.format.toUpperCase()
|
||||
@.stat HMEVENT.beforeCreate, { fmt: safeFmt, file: t }
|
||||
MKDIRP.sync PATH.dirname( t ) # Ensure dest folder exists;
|
||||
RezClass = require '../core/' + safeFmt.toLowerCase() + '-resume'
|
||||
RezClass.default().save t
|
||||
@.stat( HMEVENT.afterCreate, { fmt: safeFmt, file: t } )
|
||||
, @)
|
||||
|
||||
return
|
||||
newRez = RezClass.default()
|
||||
newRez.save t
|
||||
ret = newRez
|
||||
return
|
||||
catch
|
||||
ret =
|
||||
fluenterror: HMSTATUS.createError
|
||||
inner: _error
|
||||
return
|
||||
finally
|
||||
@.stat HMEVENT.afterCreate, fmt: safeFmt, file: t, isError: ret.fluenterror
|
||||
return ret
|
||||
|
@ -17,46 +17,60 @@ HMEVENT = require('../core/event-codes')
|
||||
|
||||
PeekVerb = module.exports = Verb.extend
|
||||
|
||||
init: -> @_super 'peek', peek
|
||||
init: -> @_super 'peek', _peek
|
||||
|
||||
|
||||
|
||||
###* Peek at a resume, resume section, or resume field. ###
|
||||
peek = ( src, dst, opts ) ->
|
||||
_peek = ( src, dst, opts ) ->
|
||||
|
||||
if !src || !src.length
|
||||
throw: fluenterror: HMSTATUS.resumeNotFound
|
||||
@err HMSTATUS.resumeNotFound, { quit: true }
|
||||
return null
|
||||
|
||||
objPath = (dst && dst[0]) || ''
|
||||
|
||||
_.each src, ( t ) ->
|
||||
|
||||
# Fire the 'beforePeek' event 2nd, so we have error/warning/success
|
||||
@.stat HMEVENT.beforePeek, { file: t, target: objPath }
|
||||
|
||||
# Load the input file JSON 1st
|
||||
obj = safeLoadJSON t
|
||||
|
||||
# Fetch the requested object path (or the entire file)
|
||||
tgt = null;
|
||||
if !obj.ex
|
||||
tgt = if objPath then __.get obj.json, objPath else obj.json;
|
||||
|
||||
# Fire the 'afterPeek' event with collected info
|
||||
@.stat HMEVENT.afterPeek,
|
||||
file: t
|
||||
requested: objPath
|
||||
target: tgt
|
||||
error: obj.ex
|
||||
|
||||
# safeLoadJSON can only return a READ error or a PARSE error
|
||||
if obj.ex
|
||||
errCode = if obj.ex.operation == 'parse' then HMSTATUS.parseError else HMSTATUS.readError
|
||||
if errCode == HMSTATUS.readError
|
||||
obj.ex.quiet = true
|
||||
@setError errCode, obj.ex
|
||||
@err errCode, obj.ex
|
||||
|
||||
results = _.map src, ( t ) ->
|
||||
return { } if opts.assert and @hasError()
|
||||
tgt = _peekOne.call @, t, objPath
|
||||
if tgt.fluenterror
|
||||
tgt.quit = opts.assert
|
||||
@err tgt.fluenterror, tgt
|
||||
, @
|
||||
|
||||
return
|
||||
if @hasError() and !opts.assert
|
||||
@reject @errorCode
|
||||
else if !@hasError()
|
||||
@resolve results
|
||||
results
|
||||
|
||||
|
||||
|
||||
###* Peek at a single resume, resume section, or resume field. ###
|
||||
_peekOne = ( t, objPath ) ->
|
||||
|
||||
@.stat HMEVENT.beforePeek, { file: t, target: objPath }
|
||||
|
||||
# Load the input file JSON 1st
|
||||
obj = safeLoadJSON t
|
||||
|
||||
# Fetch the requested object path (or the entire file)
|
||||
tgt = null;
|
||||
if !obj.ex
|
||||
tgt = if objPath then __.get obj.json, objPath else obj.json;
|
||||
|
||||
# Fire the 'afterPeek' event with collected info
|
||||
@.stat HMEVENT.afterPeek,
|
||||
file: t
|
||||
requested: objPath
|
||||
target: tgt
|
||||
error: obj.ex
|
||||
|
||||
## safeLoadJSON can only return a READ error or a PARSE error
|
||||
if obj.ex
|
||||
errCode = if obj.ex.operation == 'parse' then HMSTATUS.parseError else HMSTATUS.readError
|
||||
if errCode == HMSTATUS.readError
|
||||
obj.ex.quiet = true
|
||||
return fluenterror: errCode, inner: obj.ex
|
||||
|
||||
tgt
|
||||
|
@ -21,15 +21,16 @@ safeLoadJSON = require '../utils/safe-json-loader'
|
||||
###* An invokable resume validation command. ###
|
||||
ValidateVerb = module.exports = Verb.extend
|
||||
|
||||
init: -> @_super 'validate', validate
|
||||
init: -> @_super 'validate', _validate
|
||||
|
||||
|
||||
|
||||
###* Validate 1 to N resumes in FRESH or JSON Resume format. ###
|
||||
validate = (sources, unused, opts) ->
|
||||
_validate = (sources, unused, opts) ->
|
||||
|
||||
if !sources || !sources.length
|
||||
throw { fluenterror: HMSTATUS.resumeNotFoundAlt, quit: true }
|
||||
@err HMSTATUS.resumeNotFoundAlt, { quit: true }
|
||||
return null
|
||||
|
||||
validator = require 'is-my-json-valid'
|
||||
schemas =
|
||||
@ -38,47 +39,58 @@ validate = (sources, unused, opts) ->
|
||||
|
||||
# Validate input resumes. Return a { file: <f>, isValid: <v>} object for
|
||||
# each resume valid, invalid, or broken.
|
||||
_.map sources, (t) ->
|
||||
|
||||
ret = file: t, isValid: false
|
||||
|
||||
# Load the input file JSON 1st
|
||||
obj = safeLoadJSON t
|
||||
|
||||
if !obj.ex
|
||||
|
||||
# Successfully read the resume. Now parse it as JSON.
|
||||
json = obj.json
|
||||
fmt = if json.basics then 'jrs' else 'fresh'
|
||||
errors = []
|
||||
|
||||
try
|
||||
validate = validator schemas[ fmt ], { # Note [1]
|
||||
formats: { date: /^\d{4}(?:-(?:0[0-9]{1}|1[0-2]{1})(?:-[0-9]{2})?)?$/ }
|
||||
};
|
||||
ret.isValid = validate json
|
||||
if !ret.isValid
|
||||
errors = validate.errors
|
||||
catch
|
||||
ret.ex = _error
|
||||
|
||||
# safeLoadJSON can only return a READ error or a PARSE error
|
||||
else
|
||||
errCode = if obj.ex.operation == 'parse' then HMSTATUS.parseError else HMSTATUS.readError
|
||||
if errCode == HMSTATUS.readError
|
||||
obj.ex.quiet = true
|
||||
@setError errCode, obj.ex
|
||||
@err errCode, obj.ex
|
||||
|
||||
@stat HMEVENT.afterValidate,
|
||||
file: t
|
||||
isValid: ret.isValid
|
||||
fmt: fmt?.replace 'jars', 'JSON Resume'
|
||||
errors: errors
|
||||
|
||||
if opts.assert and !ret.isValid
|
||||
throw fluenterror: HMSTATUS.invalid, shouldExit: true
|
||||
|
||||
ret
|
||||
|
||||
results = _.map sources, (t) ->
|
||||
return { } if @hasError() and opts.assert
|
||||
r = _validateOne.call @, t, validator, schemas
|
||||
if r.fluenterror
|
||||
console.log r
|
||||
r.quit = opts.assert
|
||||
@err r.fluenterror, r
|
||||
r
|
||||
, @
|
||||
|
||||
if @hasError() and !opts.assert
|
||||
@reject @errorCode
|
||||
else if !@hasError()
|
||||
@resolve results
|
||||
results
|
||||
|
||||
|
||||
_validateOne = (t, validator, schemas) ->
|
||||
|
||||
ret = file: t, isValid: false
|
||||
|
||||
# Load the input file JSON 1st
|
||||
obj = safeLoadJSON t
|
||||
if obj.ex
|
||||
# safeLoadJSON can only return a READ error or a PARSE error
|
||||
errCode = if obj.ex.operation == 'parse' then HMSTATUS.parseError else HMSTATUS.readError
|
||||
if errCode == HMSTATUS.readError
|
||||
obj.ex.quiet = true
|
||||
return fluenterror: errCode, inner: obj.ex
|
||||
|
||||
# Successfully read the resume. Now parse it as JSON.
|
||||
json = obj.json
|
||||
fmt = if json.basics then 'jrs' else 'fresh'
|
||||
errors = []
|
||||
|
||||
try
|
||||
validate = validator schemas[ fmt ], { # Note [1]
|
||||
formats: { date: /^\d{4}(?:-(?:0[0-9]{1}|1[0-2]{1})(?:-[0-9]{2})?)?$/ }
|
||||
};
|
||||
ret.isValid = validate json
|
||||
if !ret.isValid
|
||||
errors = validate.errors
|
||||
catch
|
||||
ret.ex = _error
|
||||
|
||||
@stat HMEVENT.afterValidate,
|
||||
file: t
|
||||
isValid: ret.isValid
|
||||
fmt: fmt?.replace 'jars', 'JSON Resume'
|
||||
errors: errors
|
||||
|
||||
if opts.assert and !ret.isValid
|
||||
return fluenterror: HMSTATUS.invalid, errors: errors
|
||||
|
||||
ret
|
||||
|
@ -10,6 +10,7 @@ Definition of the Verb class.
|
||||
Class = require '../utils/class'
|
||||
EVENTS = require 'events'
|
||||
HMEVENT = require '../core/event-codes'
|
||||
Promise = require 'pinkie-promise'
|
||||
|
||||
|
||||
|
||||
@ -24,8 +25,8 @@ Verb = module.exports = Class.extend
|
||||
###* Constructor. Automatically called at creation. ###
|
||||
init: ( moniker, workhorse ) ->
|
||||
@moniker = moniker
|
||||
@emitter = new EVENTS.EventEmitter()
|
||||
@workhorse = workhorse
|
||||
@emitter = new EVENTS.EventEmitter()
|
||||
return
|
||||
|
||||
|
||||
@ -33,9 +34,11 @@ Verb = module.exports = Class.extend
|
||||
###* Invoke the command. ###
|
||||
invoke: ->
|
||||
@stat HMEVENT.begin, cmd: @moniker
|
||||
ret = @workhorse.apply @, arguments
|
||||
@stat HMEVENT.end
|
||||
ret
|
||||
argsArray = Array::slice.call arguments
|
||||
that = @
|
||||
@promise = new Promise (res, rej) ->
|
||||
that.resolve = res; that.reject = rej
|
||||
that.workhorse.apply that, argsArray; return
|
||||
|
||||
|
||||
|
||||
@ -59,7 +62,10 @@ Verb = module.exports = Class.extend
|
||||
payload = payload || { }
|
||||
payload.sub = payload.fluenterror = errorCode
|
||||
payload.throw = hot
|
||||
this.fire 'error', payload
|
||||
@setError errorCode, payload
|
||||
if payload.quit
|
||||
@reject payload
|
||||
@fire 'error', payload
|
||||
if hot
|
||||
throw payload
|
||||
true
|
||||
@ -75,6 +81,10 @@ Verb = module.exports = Class.extend
|
||||
|
||||
|
||||
|
||||
hasError: -> @errorCode || @errorObj
|
||||
|
||||
|
||||
|
||||
###* Associate error info with the invocation. ###
|
||||
setError: ( code, obj ) ->
|
||||
@errorCode = code
|
||||
|
Reference in New Issue
Block a user