2016-01-27 10:29:26 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
Implementation of the 'build' verb for HackMyResume.
|
|
|
|
@module verbs/build
|
|
|
|
@license MIT. See LICENSE.md for details.
|
|
|
|
*/
|
|
|
|
|
|
|
|
(function() {
|
2016-02-02 04:16:49 +00:00
|
|
|
var BuildVerb, FRESHTheme, FS, HMEVENT, HMSTATUS, JRSTheme, MD, MKDIRP, PATH, RConverter, RTYPES, ResumeFactory, Verb, _, _addFreebieFormats, _build, _err, _expand, _fmts, _loadTheme, _log, _opts, _prep, _rezObj, _single, _verifyOutputs, _verifyTheme, addFreebieFormats, build, expand, extend, loadTheme, parsePath, prep, single, verifyOutputs, verifyTheme,
|
|
|
|
extend1 = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
|
|
|
|
hasProp = {}.hasOwnProperty;
|
2016-01-27 10:29:26 +00:00
|
|
|
|
|
|
|
_ = 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. */
|
|
|
|
|
2016-02-02 04:16:49 +00:00
|
|
|
module.exports = BuildVerb = (function(superClass) {
|
|
|
|
extend1(BuildVerb, superClass);
|
|
|
|
|
2016-01-27 10:29:26 +00:00
|
|
|
|
|
|
|
/** Create a new build verb. */
|
2016-02-02 04:16:49 +00:00
|
|
|
|
|
|
|
function BuildVerb() {
|
|
|
|
BuildVerb.__super__.constructor.call(this, 'build', _build);
|
2016-01-27 10:29:26 +00:00
|
|
|
}
|
2016-02-02 04:16:49 +00:00
|
|
|
|
|
|
|
return BuildVerb;
|
|
|
|
|
|
|
|
})(Verb);
|
2016-01-27 10:29:26 +00:00
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
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).
|
2016-01-31 13:37:12 +00:00
|
|
|
@param opts Generation options.
|
2016-01-27 10:29:26 +00:00
|
|
|
*/
|
|
|
|
|
2016-02-02 02:14:36 +00:00
|
|
|
_build = function(src, dst, opts) {
|
|
|
|
var inv, isFRESH, mixed, newEx, orgFormat, problemSheets, results, rez, sheetObjects, sheets, tFolder, targets, theme, toFormat;
|
2016-01-27 10:29:26 +00:00
|
|
|
if (!src || !src.length) {
|
|
|
|
this.err(HMSTATUS.resumeNotFound, {
|
|
|
|
quit: true
|
|
|
|
});
|
2016-01-31 13:37:12 +00:00
|
|
|
return null;
|
2016-01-27 10:29:26 +00:00
|
|
|
}
|
2016-02-09 20:27:34 +00:00
|
|
|
_prep.call(this, src, dst, opts);
|
2016-01-27 10:29:26 +00:00
|
|
|
sheetObjects = ResumeFactory.load(src, {
|
|
|
|
format: null,
|
|
|
|
objectify: false,
|
|
|
|
quit: true,
|
|
|
|
inner: {
|
2016-02-14 18:15:47 +00:00
|
|
|
sort: _opts.sort,
|
|
|
|
"private": _opts["private"]
|
2016-01-27 10:29:26 +00:00
|
|
|
}
|
|
|
|
}, this);
|
2016-01-31 13:37:12 +00:00
|
|
|
problemSheets = _.filter(sheetObjects, function(so) {
|
2016-01-27 10:29:26 +00:00
|
|
|
return so.fluenterror;
|
2016-01-31 13:37:12 +00:00
|
|
|
});
|
|
|
|
if (problemSheets && problemSheets.length) {
|
|
|
|
problemSheets[0].quit = true;
|
|
|
|
this.err(problemSheets[0].fluenterror, problemSheets[0]);
|
2016-01-27 10:29:26 +00:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
sheets = sheetObjects.map(function(r) {
|
|
|
|
return r.json;
|
|
|
|
});
|
|
|
|
theme = null;
|
|
|
|
this.stat(HMEVENT.beforeTheme, {
|
|
|
|
theme: _opts.theme
|
|
|
|
});
|
|
|
|
try {
|
2016-02-02 02:14:36 +00:00
|
|
|
tFolder = _verifyTheme.call(this, _opts.theme);
|
2016-02-02 19:13:38 +00:00
|
|
|
if (tFolder.fluenterror) {
|
|
|
|
tFolder.quit = true;
|
|
|
|
this.err(tFolder.fluenterror, tFolder);
|
|
|
|
return;
|
|
|
|
}
|
2016-02-02 02:14:36 +00:00
|
|
|
theme = _opts.themeObj = _loadTheme(tFolder);
|
|
|
|
_addFreebieFormats(theme);
|
2016-01-27 10:29:26 +00:00
|
|
|
} catch (_error) {
|
|
|
|
newEx = {
|
|
|
|
fluenterror: HMSTATUS.themeLoad,
|
2016-02-02 02:14:36 +00:00
|
|
|
inner: _error,
|
2016-01-31 13:37:12 +00:00
|
|
|
attempted: _opts.theme,
|
|
|
|
quit: true
|
2016-01-27 10:29:26 +00:00
|
|
|
};
|
|
|
|
this.err(HMSTATUS.themeLoad, newEx);
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
this.stat(HMEVENT.afterTheme, {
|
|
|
|
theme: theme
|
|
|
|
});
|
2016-02-02 02:14:36 +00:00
|
|
|
inv = _verifyOutputs.call(this, dst, theme);
|
2016-01-27 10:29:26 +00:00
|
|
|
if (inv && inv.length) {
|
|
|
|
this.err(HMSTATUS.invalidFormat, {
|
|
|
|
data: inv,
|
2016-01-31 13:37:12 +00:00
|
|
|
theme: theme,
|
|
|
|
quit: true
|
2016-01-27 10:29:26 +00:00
|
|
|
});
|
2016-01-31 13:37:12 +00:00
|
|
|
return null;
|
2016-01-27 10:29:26 +00:00
|
|
|
}
|
|
|
|
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
|
|
|
|
});
|
|
|
|
}
|
|
|
|
this.stat(HMEVENT.applyTheme, {
|
|
|
|
r: rez,
|
|
|
|
theme: theme
|
|
|
|
});
|
2016-02-14 18:15:47 +00:00
|
|
|
_rezObj = new RTYPES[toFormat]().parseJSON(rez, {
|
|
|
|
"private": _opts["private"]
|
|
|
|
});
|
2016-02-02 02:14:36 +00:00
|
|
|
targets = _expand(dst, theme);
|
2016-01-27 10:29:26 +00:00
|
|
|
_.each(targets, function(t) {
|
2016-02-03 00:02:56 +00:00
|
|
|
var ref;
|
2016-02-02 02:14:36 +00:00
|
|
|
if (this.hasError() && opts.assert) {
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
t.final = _single.call(this, t, theme, targets);
|
2016-02-03 00:02:56 +00:00
|
|
|
if ((ref = t.final) != null ? ref.fluenterror : void 0) {
|
2016-02-02 02:14:36 +00:00
|
|
|
t.final.quit = opts.assert;
|
|
|
|
this.err(t.final.fluenterror, t.final);
|
|
|
|
}
|
2016-01-27 10:29:26 +00:00
|
|
|
}, this);
|
2016-02-02 02:14:36 +00:00
|
|
|
results = {
|
2016-01-27 10:29:26 +00:00
|
|
|
sheet: _rezObj,
|
|
|
|
targets: targets,
|
|
|
|
processed: targets
|
|
|
|
};
|
2016-02-02 02:14:36 +00:00
|
|
|
if (this.hasError() && !opts.assert) {
|
|
|
|
this.reject(results);
|
|
|
|
} else if (!this.hasError()) {
|
|
|
|
this.resolve(results);
|
|
|
|
}
|
|
|
|
return results;
|
2016-01-27 10:29:26 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
Prepare for a BUILD run.
|
|
|
|
*/
|
|
|
|
|
2016-02-02 02:14:36 +00:00
|
|
|
_prep = function(src, dst, opts) {
|
2016-02-09 20:27:34 +00:00
|
|
|
var that;
|
2016-01-27 10:29:26 +00:00
|
|
|
_opts.theme = (opts.theme && opts.theme.toLowerCase().trim()) || 'modern';
|
|
|
|
_opts.prettify = opts.prettify === true;
|
2016-02-14 18:15:47 +00:00
|
|
|
_opts["private"] = opts["private"] === true;
|
2016-01-27 10:29:26 +00:00
|
|
|
_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;
|
2016-02-09 20:27:34 +00:00
|
|
|
that = this;
|
|
|
|
_opts.onTransform = function(info) {
|
|
|
|
that.stat(HMEVENT.afterTransform, info);
|
|
|
|
};
|
|
|
|
_opts.beforeWrite = function(info) {
|
|
|
|
that.stat(HMEVENT.beforeWrite, info);
|
|
|
|
};
|
|
|
|
_opts.afterWrite = function(info) {
|
|
|
|
that.stat(HMEVENT.afterWrite, info);
|
|
|
|
};
|
2016-01-27 10:29:26 +00:00
|
|
|
(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.
|
|
|
|
*/
|
|
|
|
|
2016-02-02 02:14:36 +00:00
|
|
|
_single = function(targInfo, theme, finished) {
|
2016-01-27 10:29:26 +00:00
|
|
|
var e, ex, f, fName, fType, outFolder, ret, theFormat;
|
|
|
|
ret = null;
|
|
|
|
ex = null;
|
|
|
|
f = targInfo.file;
|
|
|
|
try {
|
|
|
|
if (!targInfo.fmt) {
|
2016-02-03 00:02:56 +00:00
|
|
|
return {};
|
2016-01-27 10:29:26 +00:00
|
|
|
}
|
|
|
|
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)
|
|
|
|
});
|
2016-01-31 14:41:00 +00:00
|
|
|
_opts.targets = finished;
|
2016-01-27 10:29:26 +00:00
|
|
|
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));
|
|
|
|
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) {
|
2016-02-02 02:14:36 +00:00
|
|
|
ret = ex;
|
2016-01-27 10:29:26 +00:00
|
|
|
} else {
|
2016-02-02 02:14:36 +00:00
|
|
|
ret = {
|
|
|
|
fluenterror: HMSTATUS.generateError,
|
2016-01-27 10:29:26 +00:00
|
|
|
inner: ex
|
2016-02-02 02:14:36 +00:00
|
|
|
};
|
2016-01-27 10:29:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2016-01-31 13:37:12 +00:00
|
|
|
/** Ensure that user-specified outputs/targets are valid. */
|
2016-01-27 10:29:26 +00:00
|
|
|
|
2016-02-02 02:14:36 +00:00
|
|
|
_verifyOutputs = function(targets, theme) {
|
2016-01-27 10:29:26 +00:00
|
|
|
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.
|
|
|
|
*/
|
|
|
|
|
2016-02-02 02:14:36 +00:00
|
|
|
_addFreebieFormats = function(theTheme) {
|
2016-01-27 10:29:26 +00:00
|
|
|
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.
|
|
|
|
*/
|
|
|
|
|
2016-02-02 02:14:36 +00:00
|
|
|
_expand = function(dst, theTheme) {
|
2016-01-27 10:29:26 +00:00
|
|
|
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.
|
|
|
|
*/
|
|
|
|
|
2016-02-02 02:14:36 +00:00
|
|
|
_verifyTheme = function(themeNameOrPath) {
|
2016-01-27 10:29:26 +00:00
|
|
|
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)) {
|
2016-02-02 19:13:38 +00:00
|
|
|
return {
|
|
|
|
fluenterror: HMSTATUS.themeNotFound,
|
2016-01-27 10:29:26 +00:00
|
|
|
data: _opts.theme
|
2016-02-02 19:13:38 +00:00
|
|
|
};
|
2016-01-27 10:29:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return tFolder;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
Load the specified theme, which could be either a FRESH theme or a JSON Resume
|
|
|
|
theme.
|
|
|
|
*/
|
|
|
|
|
2016-02-02 02:14:36 +00:00
|
|
|
_loadTheme = function(tFolder) {
|
2016-01-27 10:29:26 +00:00
|
|
|
var theTheme;
|
|
|
|
theTheme = _opts.theme.indexOf('jsonresume-theme-') > -1 ? new JRSTheme().open(tFolder) : new FRESHTheme().open(tFolder);
|
|
|
|
_opts.themeObj = theTheme;
|
|
|
|
return theTheme;
|
|
|
|
};
|
|
|
|
|
|
|
|
}).call(this);
|
2016-02-02 02:14:36 +00:00
|
|
|
|
|
|
|
//# sourceMappingURL=build.js.map
|