mirror of
https://github.com/JuanCanham/HackMyResume.git
synced 2025-05-10 07:47:07 +01:00
Finish HackMyCore reshaping.
Reintroduce HackMyCore, dropping the interim submodule, and reorganize and improve tests.
This commit is contained in:
75
src/verbs/analyze.coffee
Normal file
75
src/verbs/analyze.coffee
Normal file
@ -0,0 +1,75 @@
|
||||
###*
|
||||
Implementation of the 'analyze' verb for HackMyResume.
|
||||
@module verbs/analyze
|
||||
@license MIT. See LICENSE.md for details.
|
||||
###
|
||||
|
||||
|
||||
|
||||
MKDIRP = require('mkdirp')
|
||||
PATH = require('path')
|
||||
HMEVENT = require('../core/event-codes')
|
||||
HMSTATUS = require('../core/status-codes')
|
||||
_ = require('underscore')
|
||||
ResumeFactory = require('../core/resume-factory')
|
||||
Verb = require('../verbs/verb')
|
||||
chalk = require('chalk')
|
||||
|
||||
|
||||
|
||||
AnalyzeVerb = module.exports = Verb.extend
|
||||
|
||||
init: -> @._super 'analyze'
|
||||
|
||||
invoke: ->
|
||||
@.stat HMEVENT.begin, { cmd: 'analyze' }
|
||||
analyze.apply @, arguments
|
||||
@.stat HMEVENT.end
|
||||
|
||||
|
||||
|
||||
###*
|
||||
Run the 'analyze' command.
|
||||
###
|
||||
analyze = ( sources, dst, opts ) ->
|
||||
|
||||
if !sources || !sources.length
|
||||
throw
|
||||
fluenterror: HMSTATUS.resumeNotFound
|
||||
quit: true
|
||||
|
||||
nlzrs = _loadInspectors()
|
||||
|
||||
_.each(sources, (src) ->
|
||||
result = ResumeFactory.loadOne src, format: 'FRESH', objectify: true, @
|
||||
if result.fluenterror
|
||||
this.setError result.fluenterror, result
|
||||
else
|
||||
_analyze.call @, result, nlzrs, opts
|
||||
, @)
|
||||
|
||||
|
||||
|
||||
###*
|
||||
Analyze a single resume.
|
||||
###
|
||||
_analyze = ( resumeObject, nlzrs, opts ) ->
|
||||
|
||||
rez = resumeObject.rez
|
||||
safeFormat =
|
||||
if rez.meta and rez.meta.format and rez.meta.format.startsWith 'FRESH'
|
||||
then 'FRESH' else 'JRS'
|
||||
|
||||
this.stat( HMEVENT.beforeAnalyze, { fmt: safeFormat, file: resumeObject.file })
|
||||
info = _.mapObject nlzrs, (val, key) -> val.run rez
|
||||
this.stat HMEVENT.afterAnalyze, { info: info }
|
||||
|
||||
|
||||
|
||||
###*
|
||||
Load inspectors.
|
||||
###
|
||||
_loadInspectors = ->
|
||||
totals: require '../inspectors/totals-inspector'
|
||||
coverage: require '../inspectors/gap-inspector'
|
||||
keywords: require '../inspectors/keyword-inspector'
|
340
src/verbs/build.coffee
Normal file
340
src/verbs/build.coffee
Normal file
@ -0,0 +1,340 @@
|
||||
###*
|
||||
Implementation of the 'build' verb for HackMyResume.
|
||||
@module verbs/build
|
||||
@license MIT. See LICENSE.md for details.
|
||||
###
|
||||
|
||||
|
||||
|
||||
_ = 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')
|
||||
|
||||
_err = null
|
||||
_log = null
|
||||
_rezObj = null
|
||||
build = null
|
||||
prep = null
|
||||
single = null
|
||||
verifyOutputs = null
|
||||
addFreebieFormats = null
|
||||
expand = null
|
||||
verifyTheme = null
|
||||
loadTheme = null
|
||||
|
||||
###* An invokable resume generation command. ###
|
||||
BuildVerb = module.exports = Verb.extend
|
||||
|
||||
###* Create a new build verb. ###
|
||||
init: () -> @._super 'build'
|
||||
|
||||
###* Invoke the Build command. ###
|
||||
invoke: ->
|
||||
@stat HMEVENT.begin, { cmd: 'build' }
|
||||
ret = build.apply @, arguments
|
||||
@stat HMEVENT.end
|
||||
return ret
|
||||
|
||||
|
||||
|
||||
###*
|
||||
Given a source resume in FRESH or JRS format, a destination resume path, and a
|
||||
theme file, generate 0..N resumes in the desired formats.
|
||||
@param src Path to the source JSON resume file: "rez/resume.json".
|
||||
@param dst An array of paths to the target resume file(s).
|
||||
@param theme Friendly name of the resume theme. Defaults to "modern".
|
||||
@param logger Optional logging override.
|
||||
###
|
||||
build = ( src, dst, opts ) ->
|
||||
|
||||
if !src || !src.length
|
||||
@err HMSTATUS.resumeNotFound, { quit: true }
|
||||
|
||||
prep src, dst, opts
|
||||
|
||||
# Load input resumes as JSON...
|
||||
sheetObjects = ResumeFactory.load(src, {
|
||||
format: null, objectify: false, quit: true, inner: { sort: _opts.sort }
|
||||
}, @);
|
||||
|
||||
# Explicit check for any resume loading errors...
|
||||
if !sheetObjects || _.some( sheetObjects, (so) -> return so.fluenterror )
|
||||
return null
|
||||
|
||||
sheets = sheetObjects.map((r) -> return r.json )
|
||||
|
||||
# Load the theme...
|
||||
theme = null
|
||||
@stat HMEVENT.beforeTheme, { theme: _opts.theme }
|
||||
try
|
||||
tFolder = verifyTheme.call @, _opts.theme
|
||||
theme = _opts.themeObj = loadTheme tFolder
|
||||
catch ex
|
||||
newEx =
|
||||
fluenterror: HMSTATUS.themeLoad
|
||||
inner: ex
|
||||
attempted: _opts.theme
|
||||
this.err HMSTATUS.themeLoad, newEx
|
||||
return null
|
||||
|
||||
@stat HMEVENT.afterTheme, { theme: theme }
|
||||
|
||||
# Check for invalid outputs...
|
||||
inv = verifyOutputs.call @, dst, theme
|
||||
if inv && inv.length
|
||||
@err HMSTATUS.invalidFormat, { data: inv, theme: theme }
|
||||
|
||||
## Merge input resumes, yielding a single source resume.
|
||||
rez = null
|
||||
if sheets.length > 1
|
||||
isFRESH = !sheets[0].basics
|
||||
mixed = _.any sheets, (s) -> return if isFRESH then s.basics else !s.basics
|
||||
@stat HMEVENT.beforeMerge, { f: _.clone(sheetObjects), mixed: mixed }
|
||||
if mixed
|
||||
this.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
|
||||
orgFormat = if rez.basics then 'JRS' else 'FRESH';
|
||||
toFormat = if theme.render then 'JRS' else 'FRESH';
|
||||
if toFormat != orgFormat
|
||||
@stat HMEVENT.beforeInlineConvert
|
||||
rez = RConverter[ 'to' + toFormat ]( rez );
|
||||
@stat HMEVENT.afterInlineConvert, { file: sheetObjects[0].file, fmt: toFormat }
|
||||
|
||||
# Add freebie formats to the theme
|
||||
addFreebieFormats 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 );
|
||||
|
||||
# Run the transformation!
|
||||
_.each targets, (t) ->
|
||||
t.final = single.call this, t, theme, targets
|
||||
, @
|
||||
|
||||
# Don't send the client back empty-handed
|
||||
sheet: _rezObj
|
||||
targets: targets
|
||||
processed: targets
|
||||
|
||||
|
||||
|
||||
###*
|
||||
Prepare for a BUILD run.
|
||||
###
|
||||
prep = ( src, dst, opts ) ->
|
||||
|
||||
# Cherry-pick options //_opts = extend( true, _opts, opts );
|
||||
_opts.theme = (opts.theme && opts.theme.toLowerCase().trim()) || 'modern';
|
||||
_opts.prettify = opts.prettify is true
|
||||
_opts.css = opts.css
|
||||
_opts.pdf = opts.pdf
|
||||
_opts.wrap = opts.wrap || 60
|
||||
_opts.stitles = opts.sectionTitles
|
||||
_opts.tips = opts.tips
|
||||
_opts.errHandler = opts.errHandler
|
||||
_opts.noTips = opts.noTips
|
||||
_opts.debug = opts.debug
|
||||
_opts.sort = opts.sort
|
||||
|
||||
# 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() )
|
||||
return
|
||||
|
||||
|
||||
|
||||
###*
|
||||
Generate a single target resume such as "out/rez.html" or "out/rez.doc".
|
||||
TODO: Refactor.
|
||||
@param targInfo Information for the target resume.
|
||||
@param theme A FRESHTheme or JRSTheme object.
|
||||
###
|
||||
single = ( targInfo, theme, finished ) ->
|
||||
|
||||
ret = null
|
||||
ex = null
|
||||
f = targInfo.file
|
||||
|
||||
try
|
||||
|
||||
if !targInfo.fmt
|
||||
return
|
||||
fType = targInfo.fmt.outFormat
|
||||
fName = PATH.basename f, '.' + fType
|
||||
theFormat = null
|
||||
|
||||
@.stat HMEVENT.beforeGenerate,
|
||||
fmt: targInfo.fmt.outFormat
|
||||
file: PATH.relative(process.cwd(), f)
|
||||
|
||||
# If targInfo.fmt.files exists, this format is backed by a document.
|
||||
# Fluent/FRESH themes are handled here.
|
||||
if targInfo.fmt.files && targInfo.fmt.files.length
|
||||
theFormat = _fmts.filter(
|
||||
(fmt) -> return fmt.name == targInfo.fmt.outFormat )[0];
|
||||
MKDIRP.sync( PATH.dirname( f ) ); # Ensure dest folder exists;
|
||||
_opts.targets = finished;
|
||||
ret = theFormat.gen.generate( _rezObj, f, _opts );
|
||||
|
||||
# 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 );
|
||||
|
||||
catch e
|
||||
# Catch any errors caused by generating this file and don't let them
|
||||
# propagate -- typically we want to continue processing other formats
|
||||
# even if this format failed.
|
||||
ex = e;
|
||||
|
||||
this.stat HMEVENT.afterGenerate,
|
||||
fmt: targInfo.fmt.outFormat,
|
||||
file: PATH.relative( process.cwd(), f ),
|
||||
error: ex
|
||||
|
||||
if ex
|
||||
if ex.fluenterror
|
||||
this.err( ex.fluenterror, ex );
|
||||
else
|
||||
this.err( HMSTATUS.generateError, { inner: ex } );
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
|
||||
###*
|
||||
Ensure that user-specified outputs/targets are valid.
|
||||
###
|
||||
verifyOutputs = ( targets, theme ) ->
|
||||
@.stat HMEVENT.verifyOutputs, { targets: targets, theme: theme }
|
||||
_.reject targets.map( ( t ) ->
|
||||
pathInfo = parsePath t
|
||||
{
|
||||
format: pathInfo.extname.substr(1)
|
||||
}),
|
||||
(t) -> t.format == 'all' || theme.hasFormat( t.format )
|
||||
|
||||
###*
|
||||
Reinforce the chosen theme with "freebie" formats provided by HackMyResume.
|
||||
A "freebie" format is an output format such as JSON, YML, or PNG that can be
|
||||
generated directly from the resume model or from one of the theme's declared
|
||||
output formats. For example, the PNG format can be generated for any theme
|
||||
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 ) ->
|
||||
# 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 || {
|
||||
freebie: true, title: 'json', outFormat: 'json', pre: 'json',
|
||||
ext: 'json', path: null, data: null
|
||||
}
|
||||
theTheme.formats.yml = theTheme.formats.yml || {
|
||||
freebie: true, title: 'yaml', outFormat: 'yml', pre: 'yml',
|
||||
ext: 'yml', path: null, data: null
|
||||
}
|
||||
if theTheme.formats.html && !theTheme.formats.png
|
||||
theTheme.formats.png = {
|
||||
freebie: true, title: 'png', outFormat: 'png',
|
||||
ext: 'yml', path: null, data: null
|
||||
}
|
||||
return
|
||||
|
||||
|
||||
|
||||
###*
|
||||
Expand output files. For example, "foo.all" should be expanded to
|
||||
["foo.html", "foo.doc", "foo.pdf", "etc"].
|
||||
@param dst An array of output files as specified by the user.
|
||||
@param theTheme A FRESHTheme or JRSTheme object.
|
||||
###
|
||||
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.
|
||||
destColl = (dst && dst.length && dst) || [PATH.normalize('out/resume.all')];
|
||||
|
||||
# Assemble an array of expanded target files... (can't use map() here)
|
||||
targets = [];
|
||||
destColl.forEach (t) ->
|
||||
to = PATH.resolve(t)
|
||||
pa = parsePath(to)
|
||||
fmat = pa.extname || '.all';
|
||||
targets.push.apply( targets,
|
||||
if fmat == '.all'
|
||||
then Object.keys( theTheme.formats ).map( ( k ) ->
|
||||
z = theTheme.formats[k]
|
||||
return { file: to.replace( /all$/g, z.outFormat ), fmt: z }
|
||||
)
|
||||
else [{ file: to, fmt: theTheme.getFormat( fmat.slice(1) ) }]
|
||||
)
|
||||
targets
|
||||
|
||||
|
||||
|
||||
###*
|
||||
Verify the specified theme name/path.
|
||||
###
|
||||
verifyTheme = ( themeNameOrPath ) ->
|
||||
tFolder = PATH.join(
|
||||
parsePath( require.resolve('fresh-themes') ).dirname,
|
||||
'/themes/',
|
||||
themeNameOrPath
|
||||
)
|
||||
exists = require('path-exists').sync
|
||||
if !exists( tFolder )
|
||||
tFolder = PATH.resolve themeNameOrPath
|
||||
if !exists tFolder
|
||||
this.err HMSTATUS.themeNotFound, { data: _opts.theme }
|
||||
tFolder
|
||||
|
||||
|
||||
|
||||
###*
|
||||
Load the specified theme, which could be either a FRESH theme or a JSON Resume
|
||||
theme.
|
||||
###
|
||||
loadTheme = ( tFolder ) ->
|
||||
|
||||
# Create a FRESH or JRS theme object
|
||||
theTheme =
|
||||
if _opts.theme.indexOf('jsonresume-theme-') > -1
|
||||
then new JRSTheme().open(tFolder) else new FRESHTheme().open( tFolder );
|
||||
|
||||
# Cache the theme object
|
||||
_opts.themeObj = theTheme;
|
||||
theTheme
|
77
src/verbs/convert.coffee
Normal file
77
src/verbs/convert.coffee
Normal file
@ -0,0 +1,77 @@
|
||||
###*
|
||||
Implementation of the 'convert' verb for HackMyResume.
|
||||
@module verbs/convert
|
||||
@license MIT. See LICENSE.md for details.
|
||||
###
|
||||
|
||||
|
||||
|
||||
ResumeFactory = require('../core/resume-factory')
|
||||
chalk = require('chalk')
|
||||
Verb = require('../verbs/verb')
|
||||
HMSTATUS = require('../core/status-codes')
|
||||
_ = require('underscore')
|
||||
HMEVENT = require('../core/event-codes');
|
||||
|
||||
|
||||
|
||||
ConvertVerb = module.exports = Verb.extend
|
||||
|
||||
init: -> @._super 'convert'
|
||||
|
||||
invoke: ->
|
||||
@.stat HMEVENT.begin, { cmd: 'convert' }
|
||||
convert.apply @, arguments
|
||||
@.stat HMEVENT.end
|
||||
|
||||
|
||||
|
||||
###*
|
||||
Convert between FRESH and JRS formats.
|
||||
###
|
||||
convert = ( srcs, dst, opts ) ->
|
||||
|
||||
# Housekeeping
|
||||
throw { fluenterror: 6, quit: true } if !srcs || !srcs.length
|
||||
if !dst || !dst.length
|
||||
if srcs.length == 1
|
||||
throw { fluenterror: HMSTATUS.inputOutputParity, quit: true };
|
||||
else if srcs.length == 2
|
||||
dst = dst || []; dst.push( srcs.pop() )
|
||||
else
|
||||
throw fluenterror: HMSTATUS.inputOutputParity, quit: true
|
||||
|
||||
if srcs && dst && srcs.length && dst.length && srcs.length != dst.length
|
||||
throw fluenterror: HMSTATUS.inputOutputParity quit: true
|
||||
|
||||
# Load source resumes
|
||||
_.each(srcs, ( src, idx ) ->
|
||||
|
||||
# Load the resume
|
||||
rinfo = ResumeFactory.loadOne src,
|
||||
format: null, objectify: true, throw: false
|
||||
|
||||
# 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
|
||||
|
||||
# Save it to the destination format
|
||||
s.saveAs dst[idx], targetFormat
|
||||
return
|
||||
|
||||
, @)
|
||||
|
||||
return
|
46
src/verbs/create.coffee
Normal file
46
src/verbs/create.coffee
Normal file
@ -0,0 +1,46 @@
|
||||
###*
|
||||
Implementation of the 'create' verb for HackMyResume.
|
||||
@module verbs/create
|
||||
@license MIT. See LICENSE.md for details.
|
||||
###
|
||||
|
||||
|
||||
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')
|
||||
|
||||
invoke: ->
|
||||
@.stat HMEVENT.begin, { cmd: 'create' }
|
||||
create.apply @, arguments
|
||||
@.stat HMEVENT.begin, { cmd: 'convert' }
|
||||
return
|
||||
|
||||
|
||||
###*
|
||||
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 }
|
||||
|
||||
_.each( src, ( t ) ->
|
||||
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
|
67
src/verbs/peek.coffee
Normal file
67
src/verbs/peek.coffee
Normal file
@ -0,0 +1,67 @@
|
||||
###*
|
||||
Implementation of the 'peek' verb for HackMyResume.
|
||||
@module verbs/peek
|
||||
@license MIT. See LICENSE.md for details.
|
||||
###
|
||||
|
||||
|
||||
|
||||
Verb = require('../verbs/verb')
|
||||
_ = require('underscore')
|
||||
__ = require('lodash')
|
||||
safeLoadJSON = require('../utils/safe-json-loader')
|
||||
HMSTATUS = require('../core/status-codes')
|
||||
HMEVENT = require('../core/event-codes')
|
||||
|
||||
|
||||
|
||||
PeekVerb = module.exports = Verb.extend
|
||||
|
||||
init: -> @._super('peek')
|
||||
|
||||
invoke: ->
|
||||
@.stat HMEVENT.begin, { cmd: 'peek' }
|
||||
peek.apply @, arguments
|
||||
@.stat HMEVENT.end
|
||||
|
||||
|
||||
|
||||
###* Peek at a resume, resume section, or resume field. ###
|
||||
peek = ( src, dst, opts ) ->
|
||||
|
||||
if !src || !src.length
|
||||
throw: fluenterror: HMSTATUS.resumeNotFound
|
||||
|
||||
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
|
||||
|
||||
, @
|
||||
|
||||
return
|
92
src/verbs/validate.coffee
Normal file
92
src/verbs/validate.coffee
Normal file
@ -0,0 +1,92 @@
|
||||
###*
|
||||
Implementation of the 'validate' verb for HackMyResume.
|
||||
@module verbs/validate
|
||||
@license MIT. See LICENSE.md for details.
|
||||
###
|
||||
|
||||
|
||||
|
||||
FS = require 'fs'
|
||||
ResumeFactory = require '../core/resume-factory'
|
||||
SyntaxErrorEx = require '../utils/syntax-error-ex'
|
||||
chalk = require 'chalk'
|
||||
Verb = require '../verbs/verb'
|
||||
HMSTATUS = require '../core/status-codes'
|
||||
HMEVENT = require '../core/event-codes'
|
||||
_ = require 'underscore'
|
||||
safeLoadJSON = require '../utils/safe-json-loader'
|
||||
|
||||
|
||||
|
||||
###* An invokable resume validation command. ###
|
||||
ValidateVerb = module.exports = Verb.extend
|
||||
|
||||
init: -> @_super 'validate'
|
||||
|
||||
invoke: ->
|
||||
@stat HMEVENT.begin, { cmd: 'validate' }
|
||||
ret = validate.apply @, arguments
|
||||
@stat HMEVENT.end
|
||||
return ret
|
||||
|
||||
|
||||
|
||||
###* Validate 1 to N resumes in FRESH or JSON Resume format. ###
|
||||
validate = (sources, unused, opts) ->
|
||||
|
||||
if !sources || !sources.length
|
||||
throw { fluenterror: HMSTATUS.resumeNotFoundAlt, quit: true }
|
||||
|
||||
validator = require 'is-my-json-valid'
|
||||
schemas =
|
||||
fresh: require 'fresca'
|
||||
jars: require '../core/resume.json'
|
||||
|
||||
# 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
|
||||
|
||||
console.log '1111'
|
||||
|
||||
return ret
|
||||
|
||||
, @
|
71
src/verbs/verb.coffee
Normal file
71
src/verbs/verb.coffee
Normal file
@ -0,0 +1,71 @@
|
||||
###*
|
||||
Definition of the Verb class.
|
||||
@module verbs/verb
|
||||
@license MIT. See LICENSE.md for details.
|
||||
###
|
||||
|
||||
|
||||
|
||||
|
||||
# Use J. Resig's nifty class implementation
|
||||
Class = require '../utils/class'
|
||||
EVENTS = require 'events'
|
||||
|
||||
|
||||
|
||||
###*
|
||||
An instantiation of a HackMyResume command.
|
||||
@class Verb
|
||||
###
|
||||
Verb = module.exports = Class.extend
|
||||
|
||||
|
||||
|
||||
###* Constructor. Automatically called at creation. ###
|
||||
init: ( moniker ) ->
|
||||
@.moniker = moniker
|
||||
@.emitter = new EVENTS.EventEmitter()
|
||||
return
|
||||
|
||||
|
||||
|
||||
###* Forward subscriptions to the event emitter. ###
|
||||
on: ->
|
||||
this.emitter.on.apply @.emitter, arguments
|
||||
|
||||
|
||||
|
||||
###* Fire an arbitrary event, scoped to "hmr:". ###
|
||||
fire: (evtName, payload) ->
|
||||
payload = payload || { }
|
||||
payload.cmd = this.moniker
|
||||
this.emitter.emit 'hmr:' + evtName, payload
|
||||
true
|
||||
|
||||
|
||||
|
||||
###* Handle an error condition. ###
|
||||
err: ( errorCode, payload, hot ) ->
|
||||
payload = payload || { }
|
||||
payload.sub = payload.fluenterror = errorCode
|
||||
payload.throw = hot
|
||||
this.fire 'error', payload
|
||||
if hot
|
||||
throw payload
|
||||
true
|
||||
|
||||
|
||||
|
||||
###* Fire the 'hmr:status' error event. ###
|
||||
stat: ( subEvent, payload ) ->
|
||||
payload = payload || { }
|
||||
payload.sub = subEvent
|
||||
this.fire 'status', payload
|
||||
true
|
||||
|
||||
|
||||
###* Associate error info with the invocation. ###
|
||||
setError: ( code, obj ) ->
|
||||
@errorCode = code
|
||||
@errorObj = obj
|
||||
return
|
Reference in New Issue
Block a user