1
0
mirror of https://github.com/JuanCanham/HackMyResume.git synced 2025-05-02 12:27:08 +01:00

Finish HackMyCore reshaping.

Reintroduce HackMyCore, dropping the interim submodule, and reorganize
and improve tests.
This commit is contained in:
hacksalot
2016-01-29 15:23:57 -05:00
parent e9971eb882
commit 0f65e4c9f3
130 changed files with 5384 additions and 337 deletions

102
dist/verbs/analyze.js vendored Normal file
View File

@ -0,0 +1,102 @@
/**
Implementation of the 'analyze' verb for HackMyResume.
@module verbs/analyze
@license MIT. See LICENSE.md for details.
*/
(function() {
var AnalyzeVerb, HMEVENT, HMSTATUS, MKDIRP, PATH, ResumeFactory, Verb, _, _analyze, _loadInspectors, analyze, chalk;
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: function() {
return this._super('analyze');
},
invoke: function() {
this.stat(HMEVENT.begin, {
cmd: 'analyze'
});
analyze.apply(this, arguments);
return this.stat(HMEVENT.end);
}
});
/**
Run the 'analyze' command.
*/
analyze = function(sources, dst, opts) {
var nlzrs;
if (!sources || !sources.length) {
throw {
fluenterror: HMSTATUS.resumeNotFound,
quit: true
};
}
nlzrs = _loadInspectors();
return _.each(sources, function(src) {
var result;
result = ResumeFactory.loadOne(src, {
format: 'FRESH',
objectify: true
}, this);
if (result.fluenterror) {
return this.setError(result.fluenterror, result);
} else {
return _analyze.call(this, result, nlzrs, opts);
}
}, this);
};
/**
Analyze a single resume.
*/
_analyze = function(resumeObject, nlzrs, opts) {
var info, rez, safeFormat;
rez = resumeObject.rez;
safeFormat = rez.meta && rez.meta.format && rez.meta.format.startsWith('FRESH') ? 'FRESH' : 'JRS';
this.stat(HMEVENT.beforeAnalyze, {
fmt: safeFormat,
file: resumeObject.file
});
info = _.mapObject(nlzrs, function(val, key) {
return val.run(rez);
});
return this.stat(HMEVENT.afterAnalyze, {
info: info
});
};
/**
Load inspectors.
*/
_loadInspectors = function() {
return {
totals: require('../inspectors/totals-inspector'),
coverage: require('../inspectors/gap-inspector'),
keywords: require('../inspectors/keyword-inspector')
};
};
}).call(this);

416
dist/verbs/build.js vendored Normal file
View File

@ -0,0 +1,416 @@
/**
Implementation of the 'build' verb for HackMyResume.
@module verbs/build
@license MIT. See LICENSE.md for details.
*/
(function() {
var BuildVerb, FRESHTheme, FS, HMEVENT, HMSTATUS, JRSTheme, MD, MKDIRP, PATH, RConverter, RTYPES, ResumeFactory, Verb, _, _err, _fmts, _log, _opts, _rezObj, addFreebieFormats, build, expand, extend, loadTheme, parsePath, prep, single, verifyOutputs, verifyTheme;
_ = 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: function() {
return this._super('build');
},
/** Invoke the Build command. */
invoke: function() {
var ret;
this.stat(HMEVENT.begin, {
cmd: 'build'
});
ret = build.apply(this, arguments);
this.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 = function(src, dst, opts) {
var ex, inv, isFRESH, mixed, newEx, orgFormat, rez, sheetObjects, sheets, tFolder, targets, theme, toFormat;
if (!src || !src.length) {
this.err(HMSTATUS.resumeNotFound, {
quit: true
});
}
prep(src, dst, opts);
sheetObjects = ResumeFactory.load(src, {
format: null,
objectify: false,
quit: true,
inner: {
sort: _opts.sort
}
}, this);
if (!sheetObjects || _.some(sheetObjects, function(so) {
return so.fluenterror;
})) {
return null;
}
sheets = sheetObjects.map(function(r) {
return r.json;
});
theme = null;
this.stat(HMEVENT.beforeTheme, {
theme: _opts.theme
});
try {
tFolder = verifyTheme.call(this, _opts.theme);
theme = _opts.themeObj = loadTheme(tFolder);
} catch (_error) {
ex = _error;
newEx = {
fluenterror: HMSTATUS.themeLoad,
inner: ex,
attempted: _opts.theme
};
this.err(HMSTATUS.themeLoad, newEx);
return null;
}
this.stat(HMEVENT.afterTheme, {
theme: theme
});
inv = verifyOutputs.call(this, dst, theme);
if (inv && inv.length) {
this.err(HMSTATUS.invalidFormat, {
data: inv,
theme: theme
});
}
rez = null;
if (sheets.length > 1) {
isFRESH = !sheets[0].basics;
mixed = _.any(sheets, function(s) {
if (isFRESH) {
return s.basics;
} else {
return !s.basics;
}
});
this.stat(HMEVENT.beforeMerge, {
f: _.clone(sheetObjects),
mixed: mixed
});
if (mixed) {
this.err(HMSTATUS.mixedMerge);
}
rez = _.reduceRight(sheets, function(a, b, idx) {
return extend(true, b, a);
});
this.stat(HMEVENT.afterMerge, {
r: rez
});
} else {
rez = sheets[0];
}
orgFormat = rez.basics ? 'JRS' : 'FRESH';
toFormat = theme.render ? 'JRS' : 'FRESH';
if (toFormat !== orgFormat) {
this.stat(HMEVENT.beforeInlineConvert);
rez = RConverter['to' + toFormat](rez);
this.stat(HMEVENT.afterInlineConvert, {
file: sheetObjects[0].file,
fmt: toFormat
});
}
addFreebieFormats(theme);
this.stat(HMEVENT.applyTheme, {
r: rez,
theme: theme
});
_rezObj = new RTYPES[toFormat]().parseJSON(rez);
targets = expand(dst, theme);
_.each(targets, function(t) {
return t.final = single.call(this, t, theme, targets);
}, this);
return {
sheet: _rezObj,
targets: targets,
processed: targets
};
};
/**
Prepare for a BUILD run.
*/
prep = function(src, dst, opts) {
_opts.theme = (opts.theme && opts.theme.toLowerCase().trim()) || 'modern';
_opts.prettify = opts.prettify === 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;
(src.length > 1 && (!dst || !dst.length)) && dst.push(src.pop());
};
/**
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 = function(targInfo, theme, finished) {
var e, ex, f, fName, fType, outFolder, ret, theFormat;
ret = null;
ex = null;
f = targInfo.file;
try {
if (!targInfo.fmt) {
return;
}
fType = targInfo.fmt.outFormat;
fName = PATH.basename(f, '.' + fType);
theFormat = null;
this.stat(HMEVENT.beforeGenerate, {
fmt: targInfo.fmt.outFormat,
file: PATH.relative(process.cwd(), f)
});
if (targInfo.fmt.files && targInfo.fmt.files.length) {
theFormat = _fmts.filter(function(fmt) {
return fmt.name === targInfo.fmt.outFormat;
})[0];
MKDIRP.sync(PATH.dirname(f));
_opts.targets = finished;
ret = theFormat.gen.generate(_rezObj, f, _opts);
} else {
theFormat = _fmts.filter(function(fmt) {
return fmt.name === targInfo.fmt.outFormat;
})[0];
outFolder = PATH.dirname(f);
MKDIRP.sync(outFolder);
ret = theFormat.gen.generate(_rezObj, f, _opts);
}
} catch (_error) {
e = _error;
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 = function(targets, theme) {
this.stat(HMEVENT.verifyOutputs, {
targets: targets,
theme: theme
});
return _.reject(targets.map(function(t) {
var pathInfo;
pathInfo = parsePath(t);
return {
format: pathInfo.extname.substr(1)
};
}), function(t) {
return 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 = function(theTheme) {
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
};
}
};
/**
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 = function(dst, theTheme) {
var destColl, targets;
destColl = (dst && dst.length && dst) || [PATH.normalize('out/resume.all')];
targets = [];
destColl.forEach(function(t) {
var fmat, pa, to;
to = PATH.resolve(t);
pa = parsePath(to);
fmat = pa.extname || '.all';
return targets.push.apply(targets, fmat === '.all' ? Object.keys(theTheme.formats).map(function(k) {
var z;
z = theTheme.formats[k];
return {
file: to.replace(/all$/g, z.outFormat),
fmt: z
};
}) : [
{
file: to,
fmt: theTheme.getFormat(fmat.slice(1))
}
]);
});
return targets;
};
/**
Verify the specified theme name/path.
*/
verifyTheme = function(themeNameOrPath) {
var exists, tFolder;
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
});
}
}
return tFolder;
};
/**
Load the specified theme, which could be either a FRESH theme or a JSON Resume
theme.
*/
loadTheme = function(tFolder) {
var theTheme;
theTheme = _opts.theme.indexOf('jsonresume-theme-') > -1 ? new JRSTheme().open(tFolder) : new FRESHTheme().open(tFolder);
_opts.themeObj = theTheme;
return theTheme;
};
}).call(this);

95
dist/verbs/convert.js vendored Normal file
View File

@ -0,0 +1,95 @@
/**
Implementation of the 'convert' verb for HackMyResume.
@module verbs/convert
@license MIT. See LICENSE.md for details.
*/
(function() {
var ConvertVerb, HMEVENT, HMSTATUS, ResumeFactory, Verb, _, chalk, convert;
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: function() {
return this._super('convert');
},
invoke: function() {
this.stat(HMEVENT.begin, {
cmd: 'convert'
});
convert.apply(this, arguments);
return this.stat(HMEVENT.end);
}
});
/**
Convert between FRESH and JRS formats.
*/
convert = function(srcs, dst, opts) {
if (!srcs || !srcs.length) {
throw {
fluenterror: 6,
quit: true
};
}
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
})
};
}
_.each(srcs, function(src, idx) {
var rinfo, s, srcFmt, targetFormat;
rinfo = ResumeFactory.loadOne(src, {
format: null,
objectify: true,
"throw": false
});
if (rinfo.fluenterror) {
this.err(rinfo.fluenterror, rinfo);
return;
}
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,
srcFmt: srcFmt,
dstFile: dst[idx],
dstFmt: targetFormat
});
s.saveAs(dst[idx], targetFormat);
}, this);
};
}).call(this);

69
dist/verbs/create.js vendored Normal file
View File

@ -0,0 +1,69 @@
/**
Implementation of the 'create' verb for HackMyResume.
@module verbs/create
@license MIT. See LICENSE.md for details.
*/
(function() {
var CreateVerb, HMEVENT, HMSTATUS, MKDIRP, PATH, Verb, _, chalk, create;
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: function() {
return this._super('new');
},
invoke: function() {
this.stat(HMEVENT.begin, {
cmd: 'create'
});
create.apply(this, arguments);
this.stat(HMEVENT.begin, {
cmd: 'convert'
});
}
});
/**
Create a new empty resume in either FRESH or JRS format.
*/
create = function(src, dst, opts) {
if (!src || !src.length) {
throw {
fluenterror: HMSTATUS.createNameMissing,
quit: true
};
}
_.each(src, function(t) {
var RezClass, safeFmt;
safeFmt = opts.format.toUpperCase();
this.stat(HMEVENT.beforeCreate, {
fmt: safeFmt,
file: t
});
MKDIRP.sync(PATH.dirname(t));
RezClass = require('../core/' + safeFmt.toLowerCase() + '-resume');
RezClass["default"]().save(t);
return this.stat(HMEVENT.afterCreate, {
fmt: safeFmt,
file: t
});
}, this);
};
}).call(this);

77
dist/verbs/peek.js vendored Normal file
View File

@ -0,0 +1,77 @@
/**
Implementation of the 'peek' verb for HackMyResume.
@module verbs/peek
@license MIT. See LICENSE.md for details.
*/
(function() {
var HMEVENT, HMSTATUS, PeekVerb, Verb, _, __, peek, safeLoadJSON;
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: function() {
return this._super('peek');
},
invoke: function() {
this.stat(HMEVENT.begin, {
cmd: 'peek'
});
peek.apply(this, arguments);
return this.stat(HMEVENT.end);
}
});
/** Peek at a resume, resume section, or resume field. */
peek = function(src, dst, opts) {
var objPath;
if (!src || !src.length) {
({
"throw": {
fluenterror: HMSTATUS.resumeNotFound
}
});
}
objPath = (dst && dst[0]) || '';
_.each(src, function(t) {
var errCode, obj, tgt;
this.stat(HMEVENT.beforePeek, {
file: t,
target: objPath
});
obj = safeLoadJSON(t);
tgt = null;
if (!obj.ex) {
tgt = objPath ? __.get(obj.json, objPath) : obj.json;
}
this.stat(HMEVENT.afterPeek, {
file: t,
requested: objPath,
target: tgt,
error: obj.ex
});
if (obj.ex) {
errCode = obj.ex.operation === 'parse' ? HMSTATUS.parseError : HMSTATUS.readError;
if (errCode === HMSTATUS.readError) {
obj.ex.quiet = true;
}
this.setError(errCode, obj.ex);
return this.err(errCode, obj.ex);
}
}, this);
};
}).call(this);

112
dist/verbs/validate.js vendored Normal file
View File

@ -0,0 +1,112 @@
/**
Implementation of the 'validate' verb for HackMyResume.
@module verbs/validate
@license MIT. See LICENSE.md for details.
*/
(function() {
var FS, HMEVENT, HMSTATUS, ResumeFactory, SyntaxErrorEx, ValidateVerb, Verb, _, chalk, safeLoadJSON, validate;
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: function() {
return this._super('validate');
},
invoke: function() {
var ret;
this.stat(HMEVENT.begin, {
cmd: 'validate'
});
ret = validate.apply(this, arguments);
this.stat(HMEVENT.end);
return ret;
}
});
/** Validate 1 to N resumes in FRESH or JSON Resume format. */
validate = function(sources, unused, opts) {
var schemas, validator;
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')
};
return _.map(sources, function(t) {
var errCode, errors, fmt, json, obj, ret;
ret = {
file: t,
isValid: false
};
obj = safeLoadJSON(t);
if (!obj.ex) {
json = obj.json;
fmt = json.basics ? 'jrs' : 'fresh';
errors = [];
try {
validate = validator(schemas[fmt], {
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 (_error) {
ret.ex = _error;
}
} else {
errCode = obj.ex.operation === 'parse' ? HMSTATUS.parseError : HMSTATUS.readError;
if (errCode === HMSTATUS.readError) {
obj.ex.quiet = true;
}
this.setError(errCode, obj.ex);
this.err(errCode, obj.ex);
}
this.stat(HMEVENT.afterValidate, {
file: t,
isValid: ret.isValid,
fmt: fmt != null ? fmt.replace('jars', 'JSON Resume') : void 0,
errors: errors
});
if (opts.assert && !ret.isValid) {
throw {
fluenterror: HMSTATUS.invalid,
shouldExit: true
};
}
console.log('1111');
return ret;
}, this);
};
}).call(this);

69
dist/verbs/verb.js vendored Normal file
View File

@ -0,0 +1,69 @@
/**
Definition of the Verb class.
@module verbs/verb
@license MIT. See LICENSE.md for details.
*/
(function() {
var Class, EVENTS, Verb;
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: function(moniker) {
this.moniker = moniker;
this.emitter = new EVENTS.EventEmitter();
},
/** Forward subscriptions to the event emitter. */
on: function() {
return this.emitter.on.apply(this.emitter, arguments);
},
/** Fire an arbitrary event, scoped to "hmr:". */
fire: function(evtName, payload) {
payload = payload || {};
payload.cmd = this.moniker;
this.emitter.emit('hmr:' + evtName, payload);
return true;
},
/** Handle an error condition. */
err: function(errorCode, payload, hot) {
payload = payload || {};
payload.sub = payload.fluenterror = errorCode;
payload["throw"] = hot;
this.fire('error', payload);
if (hot) {
throw payload;
}
return true;
},
/** Fire the 'hmr:status' error event. */
stat: function(subEvent, payload) {
payload = payload || {};
payload.sub = subEvent;
this.fire('status', payload);
return true;
},
/** Associate error info with the invocation. */
setError: function(code, obj) {
this.errorCode = code;
this.errorObj = obj;
}
});
}).call(this);