1
0
mirror of https://github.com/JuanCanham/HackMyResume.git synced 2024-11-22 00:10:15 +00:00

Integrate HMC.

This commit is contained in:
hacksalot 2016-01-27 05:29:26 -05:00
parent 4440d23584
commit beb60d4074
62 changed files with 5697 additions and 31 deletions

View File

@ -10,16 +10,22 @@ module.exports = function (grunt) {
main: {
expand: true,
cwd: 'src',
src: ['**/*','!**/*.coffee'],
src: ['**/*','!**/*.coffee','!hmc/**'],
dest: 'dist/',
},
core: {
expand: true,
cwd: 'src',
src: ['hmc/dist/**/*','hmc/package.json'],
dest: 'dist/',
}
},
coffee: {
main: {
expand: true,
cwd: 'src',
src: ['**/*.coffee'],
src: ['cli/**/*.coffee'],
dest: 'dist/',
ext: '.js'
}
@ -69,7 +75,7 @@ module.exports = function (grunt) {
laxcomma: true,
expr: true
},
all: ['Gruntfile.js', 'src/**/*.js', 'test/*.js']
all: ['Gruntfile.js', 'dist/cli/**/*.js', 'test/*.js']
}
};

8
dist/cli/error.js vendored
View File

@ -8,19 +8,19 @@ Error-handling routines for HackMyResume.
(function() {
var ErrorHandler, FCMD, FS, HMSTATUS, M2C, PATH, PKG, SyntaxErrorEx, WRAP, YAML, _defaultLog, assembleError, chalk, extend, printf;
HMSTATUS = require('hackmycore/dist/core/status-codes');
HMSTATUS = require('../hmc/dist/core/status-codes');
PKG = require('../../package.json');
FS = require('fs');
FCMD = require('hackmycore');
FCMD = require('../hmc');
PATH = require('path');
WRAP = require('word-wrap');
M2C = require('hackmycore/dist/utils/md2chalk.js');
M2C = require('../hmc/dist/utils/md2chalk.js');
chalk = require('chalk');
@ -30,7 +30,7 @@ Error-handling routines for HackMyResume.
printf = require('printf');
SyntaxErrorEx = require('hackmycore/dist/utils/syntax-error-ex');
SyntaxErrorEx = require('../hmc/dist/utils/syntax-error-ex');
require('string.prototype.startswith');

10
dist/cli/main.js vendored
View File

@ -8,7 +8,7 @@ Definition of the `main` function.
(function() {
var Command, EXTEND, FS, HME, HMR, HMSTATUS, OUTPUT, PAD, PATH, PKG, StringUtils, _, _opts, _out, _title, chalk, execute, initOptions, initialize, loadOptions, logMsg, main, safeLoadJSON, splitSrcDest;
HMR = require('hackmycore');
HMR = require('../hmc');
PKG = require('../../package.json');
@ -20,13 +20,13 @@ Definition of the `main` function.
PATH = require('path');
HMSTATUS = require('hackmycore/dist/core/status-codes');
HMSTATUS = require('../hmc/dist/core/status-codes');
HME = require('hackmycore/dist/core/event-codes');
HME = require('../hmc/dist/core/event-codes');
safeLoadJSON = require('hackmycore/dist/utils/safe-json-loader');
safeLoadJSON = require('../hmc/dist/utils/safe-json-loader');
StringUtils = require('hackmycore/dist/utils/string.js');
StringUtils = require('../hmc/dist/utils/string.js');
_ = require('underscore');

10
dist/cli/out.js vendored
View File

@ -10,13 +10,13 @@ Output routines for HackMyResume.
chalk = require('chalk');
HME = require('hackmycore/dist/core/event-codes');
HME = require('../hmc/dist/core/event-codes');
_ = require('underscore');
Class = require('hackmycore/dist/utils/class.js');
Class = require('../hmc/dist/utils/class.js');
M2C = require('hackmycore/dist/utils/md2chalk.js');
M2C = require('../hmc/dist/utils/md2chalk.js');
PATH = require('path');
@ -42,7 +42,7 @@ Output routines for HackMyResume.
OutputHandler = module.exports = Class.extend({
init: function(opts) {
this.opts = EXTEND(true, this.opts || {}, opts);
return this.msgs = YAML.load(PATH.join(__dirname, 'msg.yml')).events;
this.msgs = YAML.load(PATH.join(__dirname, 'msg.yml')).events;
},
log: function(msg) {
var finished;
@ -110,7 +110,7 @@ Output routines for HackMyResume.
case HME.afterAnalyze:
info = evt.info;
rawTpl = FS.readFileSync(PATH.join(__dirname, 'analyze.hbs'), 'utf8');
HANDLEBARS.registerHelper(require('hackmycore/dist/helpers/console-helpers'));
HANDLEBARS.registerHelper(require('../hmc/dist/helpers/console-helpers'));
template = HANDLEBARS.compile(rawTpl, {
strict: false,
assumeObjects: false

60
dist/hmc/dist/core/default-formats.js vendored Normal file
View File

@ -0,0 +1,60 @@
/*
Event code definitions.
@module core/default-formats
@license MIT. See LICENSE.md for details.
*/
/** Supported resume formats. */
(function() {
module.exports = [
{
name: 'html',
ext: 'html',
gen: new (require('../generators/html-generator'))()
}, {
name: 'txt',
ext: 'txt',
gen: new (require('../generators/text-generator'))()
}, {
name: 'doc',
ext: 'doc',
fmt: 'xml',
gen: new (require('../generators/word-generator'))()
}, {
name: 'pdf',
ext: 'pdf',
fmt: 'html',
is: false,
gen: new (require('../generators/html-pdf-cli-generator'))()
}, {
name: 'png',
ext: 'png',
fmt: 'html',
is: false,
gen: new (require('../generators/html-png-generator'))()
}, {
name: 'md',
ext: 'md',
fmt: 'txt',
gen: new (require('../generators/markdown-generator'))()
}, {
name: 'json',
ext: 'json',
gen: new (require('../generators/json-generator'))()
}, {
name: 'yml',
ext: 'yml',
fmt: 'yml',
gen: new (require('../generators/json-yaml-generator'))()
}, {
name: 'latex',
ext: 'tex',
fmt: 'latex',
gen: new (require('../generators/latex-generator'))()
}
];
}).call(this);

18
dist/hmc/dist/core/default-options.js vendored Normal file
View File

@ -0,0 +1,18 @@
/*
Event code definitions.
@module core/default-options
@license MIT. See LICENSE.md for details.
*/
(function() {
module.exports = {
theme: 'modern',
prettify: {
indent_size: 2,
unformatted: ['em', 'strong'],
max_char: 80
}
};
}).call(this);

77
dist/hmc/dist/core/empty-jrs.json vendored Normal file
View File

@ -0,0 +1,77 @@
{
"basics": {
"name": "",
"label": "",
"picture": "",
"email": "",
"phone": "",
"degree": "",
"website": "",
"summary": "",
"location": {
"address": "",
"postalCode": "",
"city": "",
"countryCode": "",
"region": ""
},
"profiles": [{
"network": "",
"username": "",
"url": ""
}]
},
"work": [{
"company": "",
"position": "",
"website": "",
"startDate": "",
"endDate": "",
"summary": "",
"highlights": [
""
]
}],
"awards": [{
"title": "",
"date": "",
"awarder": "",
"summary": ""
}],
"education": [{
"institution": "",
"area": "",
"studyType": "",
"startDate": "",
"endDate": "",
"gpa": "",
"courses": [ "" ]
}],
"publications": [{
"name": "",
"publisher": "",
"releaseDate": "",
"website": "",
"summary": ""
}],
"volunteer": [{
"organization": "",
"position": "",
"website": "",
"startDate": "",
"endDate": "",
"summary": "",
"highlights": [ "" ]
}],
"skills": [{
"name": "",
"level": "",
"keywords": [""]
}]
}

39
dist/hmc/dist/core/event-codes.js vendored Normal file
View File

@ -0,0 +1,39 @@
/*
Event code definitions.
@module core/event-codes
@license MIT. See LICENSE.md for details.
*/
(function() {
module.exports = {
error: -1,
success: 0,
begin: 1,
end: 2,
beforeRead: 3,
afterRead: 4,
beforeCreate: 5,
afterCreate: 6,
beforeTheme: 7,
afterTheme: 8,
beforeMerge: 9,
afterMerge: 10,
beforeGenerate: 11,
afterGenerate: 12,
beforeAnalyze: 13,
afterAnalyze: 14,
beforeConvert: 15,
afterConvert: 16,
verifyOutputs: 17,
beforeParse: 18,
afterParse: 19,
beforePeek: 20,
afterPeek: 21,
beforeInlineConvert: 22,
afterInlineConvert: 23,
beforeValidate: 24,
afterValidate: 25
};
}).call(this);

125
dist/hmc/dist/core/fluent-date.js vendored Normal file
View File

@ -0,0 +1,125 @@
/**
The HackMyResume date representation.
@license MIT. See LICENSE.md for details.
@module core/fluent-date
*/
(function() {
var FluentDate, abbr, moment, months;
moment = require('moment');
/**
Create a FluentDate from a string or Moment date object. There are a few date
formats to be aware of here.
1. The words "Present" and "Now", referring to the current date
2. The default "YYYY-MM-DD" format used in JSON Resume ("2015-02-10")
3. Year-and-month only ("2015-04")
4. Year-only "YYYY" ("2015")
5. The friendly HackMyResume "mmm YYYY" format ("Mar 2015" or "Dec 2008")
6. Empty dates ("", " ")
7. Any other date format that Moment.js can parse from
Note: Moment can transparently parse all or most of these, without requiring us
to specify a date format...but for maximum parsing safety and to avoid Moment
deprecation warnings, it's recommended to either a) explicitly specify the date
format or b) use an ISO format. For clarity, we handle these cases explicitly.
@class FluentDate
*/
FluentDate = (function() {
function FluentDate(dt) {
this.rep = this.fmt(dt);
}
return FluentDate;
})();
months = {};
abbr = {};
moment.months().forEach(function(m, idx) {
return months[m.toLowerCase()] = idx + 1;
});
moment.monthsShort().forEach(function(m, idx) {
return abbr[m.toLowerCase()] = idx + 1;
});
abbr.sept = 9;
module.exports = FluentDate;
FluentDate.fmt = function(dt, throws) {
var defTime, month, mt, parts, ref, temp;
throws = (throws === void 0 || throws === null) || throws;
if (typeof dt === 'string' || dt instanceof String) {
dt = dt.toLowerCase().trim();
if (/^(present|now|current)$/.test(dt)) {
return moment();
} else if (/^\D+\s+\d{4}$/.test(dt)) {
parts = dt.split(' ');
month = months[parts[0]] || abbr[parts[0]];
temp = parts[1] + '-' + ((ref = month < 10) != null ? ref : '0' + {
month: month.toString()
});
return moment(temp, 'YYYY-MM');
} else if (/^\d{4}-\d{1,2}$/.test(dt)) {
return moment(dt, 'YYYY-MM');
} else if (/^\s*\d{4}\s*$/.test(dt)) {
return moment(dt, 'YYYY');
} else if (/^\s*$/.test(dt)) {
defTime = {
isNull: true,
isBefore: function(other) {
if (other && !other.isNull) {
return true;
} else {
return false;
}
},
isAfter: function(other) {
if (other && !other.isNull) {
return false;
} else {
return false;
}
},
unix: function() {
return 0;
},
format: function() {
return '';
},
diff: function() {
return 0;
}
};
return defTime;
} else {
mt = moment(dt);
if (mt.isValid()) {
return mt;
}
if (throws) {
throw 'Invalid date format encountered.';
}
return null;
}
} else {
if (!dt) {
return moment();
} else if (dt.isValid && dt.isValid()) {
return dt;
}
if (throws) {
throw 'Unknown date object encountered.';
}
return null;
}
};
}).call(this);

525
dist/hmc/dist/core/fresh-resume.js vendored Normal file
View File

@ -0,0 +1,525 @@
/**
Definition of the FRESHResume class.
@license MIT. See LICENSE.md for details.
@module core/fresh-resume
*/
(function() {
var CONVERTER, FS, FreshResume, JRSResume, MD, PATH, XML, _, __, _parseDates, extend, moment, validator;
FS = require('fs');
extend = require('extend');
validator = require('is-my-json-valid');
_ = require('underscore');
__ = require('lodash');
PATH = require('path');
moment = require('moment');
XML = require('xml-escape');
MD = require('marked');
CONVERTER = require('fresh-jrs-converter');
JRSResume = require('./jrs-resume');
/**
A FRESH resume or CV. FRESH resumes are backed by JSON, and each FreshResume
object is an instantiation of that JSON decorated with utility methods.
@constructor
*/
FreshResume = (function() {
function FreshResume() {}
/** Initialize the FreshResume from file. */
FreshResume.prototype.open = function(file, opts) {
var raw, ret;
raw = FS.readFileSync(file, 'utf8');
ret = this.parse(raw, opts);
this.imp.file = file;
return ret;
};
/** Initialize the the FreshResume from JSON string data. */
FreshResume.prototype.parse = function(stringData, opts) {
return this.parseJSON(JSON.parse(stringData), opts);
};
/**
Initialize the FreshResume from JSON.
Open and parse the specified FRESH resume. Merge the JSON object model onto
this Sheet instance with extend() and convert sheet dates to a safe &
consistent format. Then sort each section by startDate descending.
@param rep {Object} The raw JSON representation.
@param opts {Object} Resume loading and parsing options.
{
date: Perform safe date conversion.
sort: Sort resume items by date.
compute: Prepare computed resume totals.
}
*/
FreshResume.prototype.parseJSON = function(rep, opts) {
var ignoreList, scrubbed, that, traverse;
that = this;
traverse = require('traverse');
ignoreList = [];
scrubbed = traverse(rep).map(function(x) {
if (!this.isLeaf && this.node.ignore) {
if (this.node.ignore === true || this.node.ignore === 'true') {
ignoreList.push(this.node);
return this.remove();
}
}
});
extend(true, this, scrubbed);
if (!this.imp) {
opts = opts || {};
if (opts.imp === void 0 || opts.imp) {
this.imp = this.imp || {};
this.imp.title = (opts.title || this.imp.title) || this.name;
}
(opts.date === void 0 || opts.date) && _parseDates.call(this);
(opts.sort === void 0 || opts.sort) && this.sort();
(opts.compute === void 0 || opts.compute) && (this.computed = {
numYears: this.duration(),
keywords: this.keywords()
});
}
return this;
};
/** Save the sheet to disk (for environments that have disk access). */
FreshResume.prototype.save = function(filename) {
this.imp.file = filename || this.imp.file;
FS.writeFileSync(this.imp.file, this.stringify(), 'utf8');
return this;
};
/**
Save the sheet to disk in a specific format, either FRESH or JSON Resume.
*/
FreshResume.prototype.saveAs = function(filename, format) {
var newRep;
if (format !== 'JRS') {
this.imp.file = filename || this.imp.file;
FS.writeFileSync(this.imp.file, this.stringify(), 'utf8');
} else {
newRep = CONVERTER.toJRS(this);
FS.writeFileSync(filename, JRSResume.stringify(newRep), 'utf8');
}
return this;
};
/**
Duplicate this FreshResume instance.
This method first extend()s this object onto an empty, creating a deep copy,
and then passes the result into a new FreshResume instance via .parseJSON.
We do it this way to create a true clone of the object without re-running any
of the associated processing.
*/
FreshResume.prototype.dupe = function() {
var jso, rnew;
jso = extend(true, {}, this);
rnew = new FreshResume();
rnew.parseJSON(jso, {});
return rnew;
};
/**
Convert this object to a JSON string, sanitizing meta-properties along the
way.
*/
FreshResume.prototype.stringify = function() {
return FreshResume.stringify(this);
};
/**
Create a copy of this resume in which all string fields have been run through
a transformation function (such as a Markdown filter or XML encoder).
TODO: Move this out of FRESHResume.
*/
FreshResume.prototype.transformStrings = function(filt, transformer) {
var ret, trx;
ret = this.dupe();
trx = require('../utils/string-transformer');
return trx(ret, filt, transformer);
};
/**
Create a copy of this resume in which all fields have been interpreted as
Markdown.
*/
FreshResume.prototype.markdownify = function() {
var MDIN, trx;
MDIN = function(txt) {
return MD(txt || '').replace(/^\s*<p>|<\/p>\s*$/gi, '');
};
trx = function(key, val) {
if (key === 'summary') {
return MD(val);
}
return MDIN(val);
};
return this.transformStrings(['skills', 'url', 'start', 'end', 'date'], trx);
};
/**
Create a copy of this resume in which all fields have been interpreted as
Markdown.
*/
FreshResume.prototype.xmlify = function() {
var trx;
trx = function(key, val) {
return XML(val);
};
return this.transformStrings([], trx);
};
/** Return the resume format. */
FreshResume.prototype.format = function() {
return 'FRESH';
};
/**
Return internal metadata. Create if it doesn't exist.
*/
FreshResume.prototype.i = function() {
return this.imp = this.imp || {};
};
/** Return a unique list of all keywords across all skills. */
FreshResume.prototype.keywords = function() {
var flatSkills;
flatSkills = [];
if (this.skills) {
if (this.skills.sets) {
flatSkills = this.skills.sets.map(function(sk) {
return sk.skills;
}).reduce(function(a, b) {
return a.concat(b);
});
} else if (this.skills.list) {
flatSkills = flatSkills.concat(this.skills.list.map(function(sk) {
return sk.name;
}));
}
flatSkills = _.uniq(flatSkills);
}
return flatSkills;
};
/**
Reset the sheet to an empty state. TODO: refactor/review
*/
FreshResume.prototype.clear = function(clearMeta) {
clearMeta = ((clearMeta === void 0) && true) || clearMeta;
if (clearMeta) {
delete this.imp;
}
delete this.computed;
delete this.employment;
delete this.service;
delete this.education;
delete this.recognition;
delete this.reading;
delete this.writing;
delete this.interests;
delete this.skills;
return delete this.social;
};
/**
Get a safe count of the number of things in a section.
*/
FreshResume.prototype.count = function(obj) {
if (!obj) {
return 0;
}
if (obj.history) {
return obj.history.length;
}
if (obj.sets) {
return obj.sets.length;
}
return obj.length || 0;
};
/** Add work experience to the sheet. */
FreshResume.prototype.add = function(moniker) {
var defSheet, newObject;
defSheet = FreshResume["default"]();
newObject = defSheet[moniker].history ? $.extend(true, {}, defSheet[moniker].history[0]) : moniker === 'skills' ? $.extend(true, {}, defSheet.skills.sets[0]) : $.extend(true, {}, defSheet[moniker][0]);
this[moniker] = this[moniker] || [];
if (this[moniker].history) {
this[moniker].history.push(newObject);
} else if (moniker === 'skills') {
this.skills.sets.push(newObject);
} else {
this[moniker].push(newObject);
}
return newObject;
};
/**
Determine if the sheet includes a specific social profile (eg, GitHub).
*/
FreshResume.prototype.hasProfile = function(socialNetwork) {
socialNetwork = socialNetwork.trim().toLowerCase();
return this.social && _.some(this.social, function(p) {
return p.network.trim().toLowerCase() === socialNetwork;
});
};
/** Return the specified network profile. */
FreshResume.prototype.getProfile = function(socialNetwork) {
socialNetwork = socialNetwork.trim().toLowerCase();
return this.social && _.find(this.social, function(sn) {
return sn.network.trim().toLowerCase() === socialNetwork;
});
};
/**
Return an array of profiles for the specified network, for when the user
has multiple eg. GitHub accounts.
*/
FreshResume.prototype.getProfiles = function(socialNetwork) {
socialNetwork = socialNetwork.trim().toLowerCase();
return this.social && _.filter(this.social, function(sn) {
return sn.network.trim().toLowerCase() === socialNetwork;
});
};
/** Determine if the sheet includes a specific skill. */
FreshResume.prototype.hasSkill = function(skill) {
skill = skill.trim().toLowerCase();
return this.skills && _.some(this.skills, function(sk) {
return sk.keywords && _.some(sk.keywords, function(kw) {
return kw.trim().toLowerCase() === skill;
});
});
};
/** Validate the sheet against the FRESH Resume schema. */
FreshResume.prototype.isValid = function(info) {
var ret, schemaObj, validate;
schemaObj = require('fresca');
validator = require('is-my-json-valid');
validate = validator(schemaObj, {
formats: {
date: /^\d{4}(?:-(?:0[0-9]{1}|1[0-2]{1})(?:-[0-9]{2})?)?$/
}
});
ret = validate(this);
if (!ret) {
this.imp = this.imp || {};
this.imp.validationErrors = validate.errors;
}
return ret;
};
/**
Calculate the total duration of the sheet. Assumes this.work has been sorted
by start date descending, perhaps via a call to Sheet.sort().
@returns The total duration of the sheet's work history, that is, the number
of years between the start date of the earliest job on the resume and the
*latest end date of all jobs in the work history*. This last condition is for
sheets that have overlapping jobs.
*/
FreshResume.prototype.duration = function(unit) {
var careerLast, careerStart, empHist, firstJob;
unit = unit || 'years';
empHist = __.get(this, 'employment.history');
if (empHist && empHist.length) {
firstJob = _.last(empHist);
careerStart = firstJob.start ? firstJob.safe.start : '';
if ((typeof careerStart === 'string' || careerStart instanceof String) && !careerStart.trim()) {
return 0;
}
careerLast = _.max(empHist, function(w) {
if (w.safe && w.safe.end) {
return w.safe.end.unix();
} else {
return moment().unix();
}
});
return careerLast.safe.end.diff(careerStart, unit);
}
return 0;
};
/**
Sort dated things on the sheet by start date descending. Assumes that dates
on the sheet have been processed with _parseDates().
*/
FreshResume.prototype.sort = function() {
var byDateDesc, sortSection;
byDateDesc = function(a, b) {
if (a.safe.start.isBefore(b.safe.start)) {
return 1;
} else {
return (a.safe.start.isAfter(b.safe.start) && -1) || 0;
}
};
sortSection = function(key) {
var ar, datedThings;
ar = __.get(this, key);
if (ar && ar.length) {
datedThings = obj.filter(function(o) {
return o.start;
});
return datedThings.sort(byDateDesc);
}
};
sortSection('employment.history');
sortSection('education.history');
sortSection('service.history');
sortSection('projects');
return this.writing && this.writing.sort(function(a, b) {
if (a.safe.date.isBefore(b.safe.date)) {
return 1;
} else {
return (a.safe.date.isAfter(b.safe.date) && -1) || 0;
}
});
};
return FreshResume;
})();
/**
Get the default (starter) sheet.
*/
FreshResume["default"] = function() {
return new FreshResume().parseJSON(require('fresh-resume-starter'));
};
/**
Convert the supplied FreshResume to a JSON string, sanitizing meta-properties
along the way.
*/
FreshResume.stringify = function(obj) {
var replacer;
replacer = function(key, value) {
var exKeys;
exKeys = ['imp', 'warnings', 'computed', 'filt', 'ctrl', 'index', 'safe', 'result', 'isModified', 'htmlPreview', 'display_progress_bar'];
if (_.some(exKeys, function(val) {
return key.trim() === val;
})) {
return void 0;
} else {
return value;
}
};
return JSON.stringify(obj, replacer, 2);
};
/**
Convert human-friendly dates into formal Moment.js dates for all collections.
We don't want to lose the raw textual date as entered by the user, so we store
the Moment-ified date as a separate property with a prefix of .safe. For ex:
job.startDate is the date as entered by the user. job.safeStartDate is the
parsed Moment.js date that we actually use in processing.
*/
_parseDates = function() {
var _fmt, replaceDatesInObject, that;
_fmt = require('./fluent-date').fmt;
that = this;
replaceDatesInObject = function(obj) {
if (!obj) {
return;
}
if (Object.prototype.toString.call(obj) === '[object Array]') {
return obj.forEach(function(elem) {
return replaceDatesInObject(elem);
});
} else if (typeof obj === 'object') {
if (obj._isAMomentObject || obj.safe) {
return;
}
Object.keys(obj).forEach(function(key) {
return replaceDatesInObject(obj[key]);
});
return ['start', 'end', 'date'].forEach(function(val) {
if ((obj[val] !== void 0) && (!obj.safe || !obj.safe[val])) {
obj.safe = obj.safe || {};
obj.safe[val] = _fmt(obj[val]);
if (obj[val] && (val === 'start') && !obj.end) {
return obj.safe.end = _fmt('current');
}
}
});
}
};
return Object.keys(this).forEach(function(member) {
return replaceDatesInObject(that[member]);
});
};
/** Export the Sheet function/ctor. */
module.exports = FreshResume;
}).call(this);

279
dist/hmc/dist/core/fresh-theme.js vendored Normal file
View File

@ -0,0 +1,279 @@
/**
Definition of the FRESHTheme class.
@module core/fresh-theme
@license MIT. See LICENSE.md for details.
*/
(function() {
var EXTEND, FRESHTheme, FS, HMSTATUS, PATH, READFILES, _, friendlyName, loadExplicit, loadImplicit, loadSafeJson, moment, parsePath, pathExists, validator;
FS = require('fs');
validator = require('is-my-json-valid');
_ = require('underscore');
PATH = require('path');
parsePath = require('parse-filepath');
pathExists = require('path-exists').sync;
EXTEND = require('extend');
HMSTATUS = require('./status-codes');
moment = require('moment');
loadSafeJson = require('../utils/safe-json-loader');
READFILES = require('recursive-readdir-sync');
/*
The FRESHTheme class is a representation of a FRESH theme
asset. See also: JRSTheme.
@class FRESHTheme
*/
FRESHTheme = (function() {
function FRESHTheme() {}
/*
Open and parse the specified theme.
*/
FRESHTheme.prototype.open = function(themeFolder) {
var cached, formatsHash, pathInfo, that, themeFile, themeInfo;
this.folder = themeFolder;
pathInfo = parsePath(themeFolder);
formatsHash = {};
themeFile = PATH.join(themeFolder, 'theme.json');
themeInfo = loadSafeJson(themeFile);
if (themeInfo.ex) {
throw {
fluenterror: themeInfo.ex.operation === 'parse' ? HMSTATUS.parseError : HMSTATUS.readError,
inner: themeInfo.ex.inner
};
}
that = this;
EXTEND(true, this, themeInfo.json);
if (this.inherits) {
cached = {};
_.each(this.inherits, function(th, key) {
var d, themePath, themesFolder;
themesFolder = require.resolve('fresh-themes');
d = parsePath(themeFolder).dirname;
themePath = PATH.join(d, th);
cached[th] = cached[th] || new FRESHTheme().open(themePath);
return formatsHash[key] = cached[th].getFormat(key);
});
}
if (!!this.formats) {
formatsHash = loadExplicit.call(this, formatsHash);
this.explicit = true;
} else {
formatsHash = loadImplicit.call(this, formatsHash);
}
this.formats = formatsHash;
this.name = parsePath(this.folder).name;
return this;
};
/* Determine if the theme supports the specified output format. */
FRESHTheme.prototype.hasFormat = function(fmt) {
return _.has(this.formats, fmt);
};
/* Determine if the theme supports the specified output format. */
FRESHTheme.prototype.getFormat = function(fmt) {
return this.formats[fmt];
};
return FRESHTheme;
})();
/* Load the theme implicitly, by scanning the theme folder for files. TODO:
Refactor duplicated code with loadExplicit.
*/
loadImplicit = function(formatsHash) {
var fmts, major, that, tplFolder;
that = this;
major = false;
tplFolder = PATH.join(this.folder, 'src');
fmts = READFILES(tplFolder).map(function(absPath) {
var idx, isMajor, obj, outFmt, pathInfo, portion, reg, res;
pathInfo = parsePath(absPath);
outFmt = '';
isMajor = false;
portion = pathInfo.dirname.replace(tplFolder, '');
if (portion && portion.trim()) {
if (portion[1] === '_') {
return;
}
reg = /^(?:\/|\\)(html|latex|doc|pdf|png|partials)(?:\/|\\)?/ig;
res = reg.exec(portion);
if (res) {
if (res[1] !== 'partials') {
outFmt = res[1];
} else {
that.partials = that.partials || [];
that.partials.push({
name: pathInfo.name,
path: absPath
});
return null;
}
}
}
if (!outFmt) {
idx = pathInfo.name.lastIndexOf('-');
outFmt = idx === -1 ? pathInfo.name : pathInfo.name.substr(idx + 1);
isMajor = true;
}
formatsHash[outFmt] = formatsHash[outFmt] || {
outFormat: outFmt,
files: []
};
obj = {
action: 'transform',
path: absPath,
major: isMajor,
orgPath: PATH.relative(tplFolder, absPath),
ext: pathInfo.extname.slice(1),
title: friendlyName(outFmt),
pre: outFmt,
data: FS.readFileSync(absPath, 'utf8'),
css: null
};
formatsHash[outFmt].files.push(obj);
return obj;
});
this.cssFiles = fmts.filter(function(fmt) {
return fmt && (fmt.ext === 'css');
});
this.cssFiles.forEach(function(cssf) {
var idx;
idx = _.findIndex(fmts, function(fmt) {
return fmt && fmt.pre === cssf.pre && fmt.ext === 'html';
});
cssf.major = false;
if (idx > -1) {
fmts[idx].css = cssf.data;
return fmts[idx].cssPath = cssf.path;
} else {
if (that.inherits) {
return that.overrides = {
file: cssf.path,
data: cssf.data
};
}
}
});
return formatsHash;
};
/*
Load the theme explicitly, by following the 'formats' hash
in the theme's JSON settings file.
*/
loadExplicit = function(formatsHash) {
var act, fmts, that, tplFolder;
tplFolder = PATH.join(this.folder, 'src');
act = null;
that = this;
fmts = READFILES(tplFolder).map(function(absPath) {
var absPathSafe, idx, obj, outFmt, pathInfo, portion, reg, res;
act = null;
pathInfo = parsePath(absPath);
absPathSafe = absPath.trim().toLowerCase();
outFmt = _.find(Object.keys(that.formats), function(fmtKey) {
var fmtVal;
fmtVal = that.formats[fmtKey];
return _.some(fmtVal.transform, function(fpath) {
var absPathB;
absPathB = PATH.join(that.folder, fpath).trim().toLowerCase();
return absPathB === absPathSafe;
});
});
if (outFmt) {
act = 'transform';
}
if (!outFmt) {
portion = pathInfo.dirname.replace(tplFolder, '');
if (portion && portion.trim()) {
reg = /^(?:\/|\\)(html|latex|doc|pdf)(?:\/|\\)?/ig;
res = reg.exec(portion);
res && (outFmt = res[1]);
}
}
if (!outFmt) {
idx = pathInfo.name.lastIndexOf('-');
outFmt = idx === -1 ? pathInfo.name : pathInfo.name.substr(idx + 1);
}
formatsHash[outFmt] = formatsHash[outFmt] || {
outFormat: outFmt,
files: [],
symLinks: that.formats[outFmt].symLinks
};
obj = {
action: act,
orgPath: PATH.relative(that.folder, absPath),
path: absPath,
ext: pathInfo.extname.slice(1),
title: friendlyName(outFmt),
pre: outFmt,
data: FS.readFileSync(absPath, 'utf8'),
css: null
};
formatsHash[outFmt].files.push(obj);
return obj;
});
this.cssFiles = fmts.filter(function(fmt) {
return fmt.ext === 'css';
});
this.cssFiles.forEach(function(cssf) {
var idx;
idx = _.findIndex(fmts, function(fmt) {
return fmt.pre === cssf.pre && fmt.ext === 'html';
});
fmts[idx].css = cssf.data;
return fmts[idx].cssPath = cssf.path;
});
fmts = fmts.filter(function(fmt) {
return fmt.ext !== 'css';
});
return formatsHash;
};
/*
Return a more friendly name for certain formats.
TODO: Refactor
*/
friendlyName = function(val) {
var friendly;
val = val.trim().toLowerCase();
friendly = {
yml: 'yaml',
md: 'markdown',
txt: 'text'
};
return friendly[val] || val;
};
module.exports = FRESHTheme;
}).call(this);

438
dist/hmc/dist/core/jrs-resume.js vendored Normal file
View File

@ -0,0 +1,438 @@
/**
Definition of the JRSResume class.
@license MIT. See LICENSE.md for details.
@module core/jrs-resume
*/
(function() {
var CONVERTER, FS, JRSResume, MD, PATH, _, _parseDates, extend, moment, validator;
FS = require('fs');
extend = require('extend');
validator = require('is-my-json-valid');
_ = require('underscore');
PATH = require('path');
MD = require('marked');
CONVERTER = require('fresh-jrs-converter');
moment = require('moment');
/**
A JRS resume or CV. JRS resumes are backed by JSON, and each JRSResume object
is an instantiation of that JSON decorated with utility methods.
@class JRSResume
*/
JRSResume = (function() {
var clear, format;
function JRSResume() {}
/** Initialize the JSResume from file. */
JRSResume.prototype.open = function(file, title) {
this.basics = {
imp: {
file: file,
raw: FS.readFileSync(file, 'utf8')
}
};
return this.parse(this.basics.imp.raw, title);
};
/** Initialize the the JSResume from string. */
JRSResume.prototype.parse = function(stringData, opts) {
var rep;
opts = opts || {};
rep = JSON.parse(stringData);
return this.parseJSON(rep, opts);
};
/**
Initialize the JRSResume object from JSON.
Open and parse the specified JRS resume. Merge the JSON object model onto
this Sheet instance with extend() and convert sheet dates to a safe &
consistent format. Then sort each section by startDate descending.
@param rep {Object} The raw JSON representation.
@param opts {Object} Resume loading and parsing options.
{
date: Perform safe date conversion.
sort: Sort resume items by date.
compute: Prepare computed resume totals.
}
*/
JRSResume.prototype.parseJSON = function(rep, opts) {
var ignoreList, scrubbed, that, traverse;
opts = opts || {};
that = this;
traverse = require('traverse');
ignoreList = [];
scrubbed = traverse(rep).map(function(x) {
if (!this.isLeaf && this.node.ignore) {
if (this.node.ignore === true || this.node.ignore === 'true') {
ignoreList.push(this.node);
return this.remove();
}
}
});
extend(true, this, scrubbed);
if (opts.imp === void 0 || opts.imp) {
this.basics.imp = this.basics.imp || {};
this.basics.imp.title = (opts.title || this.basics.imp.title) || this.basics.name;
this.basics.imp.orgFormat = 'JRS';
}
(opts.date === void 0 || opts.date) && _parseDates.call(this);
(opts.sort === void 0 || opts.sort) && this.sort();
if (opts.compute === void 0 || opts.compute) {
this.basics.computed = {
numYears: this.duration(),
keywords: this.keywords()
};
}
return this;
};
/** Save the sheet to disk (for environments that have disk access). */
JRSResume.prototype.save = function(filename) {
this.basics.imp.file = filename || this.basics.imp.file;
FS.writeFileSync(this.basics.imp.file, this.stringify(this), 'utf8');
return this;
};
/** Save the sheet to disk in a specific format, either FRESH or JRS. */
JRSResume.prototype.saveAs = function(filename, format) {
var newRep, stringRep;
if (format === 'JRS') {
this.basics.imp.file = filename || this.basics.imp.file;
FS.writeFileSync(this.basics.imp.file, this.stringify(), 'utf8');
} else {
newRep = CONVERTER.toFRESH(this);
stringRep = CONVERTER.toSTRING(newRep);
FS.writeFileSync(filename, stringRep, 'utf8');
}
return this;
};
/** Return the resume format. */
format = function() {
return 'JRS';
};
JRSResume.prototype.stringify = function() {
return JRSResume.stringify(this);
};
/** Return a unique list of all keywords across all skills. */
JRSResume.prototype.keywords = function() {
var flatSkills;
flatSkills = [];
if (this.skills && this.skills.length) {
this.skills.forEach(function(s) {
return flatSkills = _.union(flatSkills, s.keywords);
});
}
return flatSkills;
};
/**
Return internal metadata. Create if it doesn't exist.
JSON Resume v0.0.0 doesn't allow additional properties at the root level,
so tuck this into the .basic sub-object.
*/
JRSResume.prototype.i = function() {
this.basics = this.basics || {};
this.basics.imp = this.basics.imp || {};
return this.basics.imp;
};
/** Reset the sheet to an empty state. */
clear = function(clearMeta) {
clearMeta = ((clearMeta === void 0) && true) || clearMeta;
if (clearMeta) {
delete this.imp;
}
delete this.basics.computed;
delete this.work;
delete this.volunteer;
delete this.education;
delete this.awards;
delete this.publications;
delete this.interests;
delete this.skills;
return delete this.basics.profiles;
};
/** Add work experience to the sheet. */
JRSResume.prototype.add = function(moniker) {
var defSheet, newObject;
defSheet = JRSResume["default"]();
newObject = $.extend(true, {}, defSheet[moniker][0]);
this[moniker] = this[moniker] || [];
this[moniker].push(newObject);
return newObject;
};
/** Determine if the sheet includes a specific social profile (eg, GitHub). */
JRSResume.prototype.hasProfile = function(socialNetwork) {
socialNetwork = socialNetwork.trim().toLowerCase();
return this.basics.profiles && _.some(this.basics.profiles, function(p) {
return p.network.trim().toLowerCase() === socialNetwork;
});
};
/** Determine if the sheet includes a specific skill. */
JRSResume.prototype.hasSkill = function(skill) {
skill = skill.trim().toLowerCase();
return this.skills && _.some(this.skills, function(sk) {
return sk.keywords && _.some(sk.keywords, function(kw) {
return kw.trim().toLowerCase() === skill;
});
});
};
/** Validate the sheet against the JSON Resume schema. */
JRSResume.prototype.isValid = function() {
var ret, schema, schemaObj, validate;
schema = FS.readFileSync(PATH.join(__dirname, 'resume.json'), 'utf8');
schemaObj = JSON.parse(schema);
validator = require('is-my-json-valid');
validate = validator(schemaObj, {
formats: {
date: /^\d{4}(?:-(?:0[0-9]{1}|1[0-2]{1})(?:-[0-9]{2})?)?$/
}
});
ret = validate(this);
if (!ret) {
this.basics.imp = this.basics.imp || {};
this.basics.imp.validationErrors = validate.errors;
}
return ret;
};
/**
Calculate the total duration of the sheet. Assumes this.work has been sorted
by start date descending, perhaps via a call to Sheet.sort().
@returns The total duration of the sheet's work history, that is, the number
of years between the start date of the earliest job on the resume and the
*latest end date of all jobs in the work history*. This last condition is for
sheets that have overlapping jobs.
*/
JRSResume.prototype.duration = function(unit) {
var careerLast, careerStart;
unit = unit || 'years';
if (this.work && this.work.length) {
careerStart = this.work[this.work.length - 1].safeStartDate;
if ((typeof careerStart === 'string' || careerStart instanceof String) && !careerStart.trim()) {
return 0;
}
careerLast = _.max(this.work, function(w) {
return w.safeEndDate.unix();
}).safeEndDate;
return careerLast.diff(careerStart, unit);
}
return 0;
};
/**
Sort dated things on the sheet by start date descending. Assumes that dates
on the sheet have been processed with _parseDates().
*/
JRSResume.prototype.sort = function() {
var byDateDesc;
byDateDesc = function(a, b) {
if (a.safeStartDate.isBefore(b.safeStartDate)) {
return 1;
} else {
return (a.safeStartDate.isAfter(b.safeStartDate) && -1) || 0;
}
};
this.work && this.work.sort(byDateDesc);
this.education && this.education.sort(byDateDesc);
this.volunteer && this.volunteer.sort(byDateDesc);
this.awards && this.awards.sort(function(a, b) {
if (a.safeDate.isBefore(b.safeDate)) {
return 1;
} else {
return (a.safeDate.isAfter(b.safeDate) && -1) || 0;
}
});
return this.publications && this.publications.sort(function(a, b) {
if (a.safeReleaseDate.isBefore(b.safeReleaseDate)) {
return 1;
} else {
return (a.safeReleaseDate.isAfter(b.safeReleaseDate) && -1) || 0;
}
});
};
JRSResume.prototype.dupe = function() {
var rnew;
rnew = new JRSResume();
rnew.parse(this.stringify(), {});
return rnew;
};
/**
Create a copy of this resume in which all fields have been interpreted as
Markdown.
*/
JRSResume.prototype.harden = function() {
var HD, HDIN, hardenStringsInObject, ret, that;
that = this;
ret = this.dupe();
HD = function(txt) {
return '@@@@~' + txt + '~@@@@';
};
HDIN = function(txt) {
return HD(txt);
};
hardenStringsInObject = function(obj, inline) {
if (!obj) {
return;
}
inline = inline === void 0 || inline;
if (Object.prototype.toString.call(obj) === '[object Array]') {
return obj.forEach(function(elem, idx, ar) {
if (typeof elem === 'string' || elem instanceof String) {
return ar[idx] = inline ? HDIN(elem) : HD(elem);
} else {
return hardenStringsInObject(elem);
}
});
} else if (typeof obj === 'object') {
return Object.keys(obj).forEach(function(key) {
var sub;
sub = obj[key];
if (typeof sub === 'string' || sub instanceof String) {
if (_.contains(['skills', 'url', 'website', 'startDate', 'endDate', 'releaseDate', 'date', 'phone', 'email', 'address', 'postalCode', 'city', 'country', 'region'], key)) {
return;
}
if (key === 'summary') {
return obj[key] = HD(obj[key]);
} else {
return obj[key] = inline ? HDIN(obj[key]) : HD(obj[key]);
}
} else {
return hardenStringsInObject(sub);
}
});
}
};
Object.keys(ret).forEach(function(member) {
return hardenStringsInObject(ret[member]);
});
return ret;
};
return JRSResume;
})();
/** Get the default (empty) sheet. */
JRSResume["default"] = function() {
return new JRSResume().open(PATH.join(__dirname, 'empty-jrs.json'), 'Empty');
};
/**
Convert this object to a JSON string, sanitizing meta-properties along the
way. Don't override .toString().
*/
JRSResume.stringify = function(obj) {
var replacer;
replacer = function(key, value) {
var temp;
temp = _.some(['imp', 'warnings', 'computed', 'filt', 'ctrl', 'index', 'safeStartDate', 'safeEndDate', 'safeDate', 'safeReleaseDate', 'result', 'isModified', 'htmlPreview', 'display_progress_bar'], function(val) {
return key.trim() === val;
});
if (temp) {
return void 0;
} else {
return value;
}
};
return JSON.stringify(obj, replacer, 2);
};
/**
Convert human-friendly dates into formal Moment.js dates for all collections.
We don't want to lose the raw textual date as entered by the user, so we store
the Moment-ified date as a separate property with a prefix of .safe. For ex:
job.startDate is the date as entered by the user. job.safeStartDate is the
parsed Moment.js date that we actually use in processing.
*/
_parseDates = function() {
var _fmt;
_fmt = require('./fluent-date').fmt;
this.work && this.work.forEach(function(job) {
job.safeStartDate = _fmt(job.startDate);
return job.safeEndDate = _fmt(job.endDate);
});
this.education && this.education.forEach(function(edu) {
edu.safeStartDate = _fmt(edu.startDate);
return edu.safeEndDate = _fmt(edu.endDate);
});
this.volunteer && this.volunteer.forEach(function(vol) {
vol.safeStartDate = _fmt(vol.startDate);
return vol.safeEndDate = _fmt(vol.endDate);
});
this.awards && this.awards.forEach(function(awd) {
return awd.safeDate = _fmt(awd.date);
});
return this.publications && this.publications.forEach(function(pub) {
return pub.safeReleaseDate = _fmt(pub.releaseDate);
});
};
/**
Export the JRSResume function/ctor.
*/
module.exports = JRSResume;
}).call(this);

103
dist/hmc/dist/core/jrs-theme.js vendored Normal file
View File

@ -0,0 +1,103 @@
/**
Definition of the JRSTheme class.
@module core/jrs-theme
@license MIT. See LICENSE.MD for details.
*/
(function() {
var JRSTheme, PATH, _, getFormat, parsePath, pathExists;
_ = require('underscore');
PATH = require('path');
parsePath = require('parse-filepath');
pathExists = require('path-exists').sync;
/**
The JRSTheme class is a representation of a JSON Resume theme asset.
@class JRSTheme
*/
JRSTheme = (function() {
function JRSTheme() {}
return JRSTheme;
})();
({
/**
Open and parse the specified theme.
@method open
*/
open: function(thFolder) {
var pathInfo, pkgJsonPath, thApi, thPkg;
this.folder = thFolder;
pathInfo = parsePath(thFolder);
pkgJsonPath = PATH.join(thFolder, 'package.json');
if (pathExists(pkgJsonPath)) {
thApi = require(thFolder);
thPkg = require(pkgJsonPath);
this.name = thPkg.name;
this.render = (thApi && thApi.render) || void 0;
this.engine = 'jrs';
this.formats = {
html: {
outFormat: 'html',
files: [
{
action: 'transform',
render: this.render,
major: true,
ext: 'html',
css: null
}
]
},
pdf: {
outFormat: 'pdf',
files: [
{
action: 'transform',
render: this.render,
major: true,
ext: 'pdf',
css: null
}
]
}
};
} else {
throw {
fluenterror: HACKMYSTATUS.missingPackageJSON
};
}
return this;
},
/**
Determine if the theme supports the output format.
@method hasFormat
*/
hasFormat: function(fmt) {
return _.has(this.formats, fmt);
}
/**
Return the requested output format.
@method getFormat
*/
});
getFormat = function(fmt) {
return this.formats[fmt];
};
module.exports = JRSTheme;
}).call(this);

127
dist/hmc/dist/core/resume-factory.js vendored Normal file
View File

@ -0,0 +1,127 @@
/**
Definition of the ResumeFactory class.
@license MIT. See LICENSE.md for details.
@module core/resume-factory
*/
(function() {
var FS, HACKMYSTATUS, HME, ResumeConverter, ResumeFactory, SyntaxErrorEx, _, _parse, chalk;
FS = require('fs');
HACKMYSTATUS = require('./status-codes');
HME = require('./event-codes');
ResumeConverter = require('fresh-jrs-converter');
chalk = require('chalk');
SyntaxErrorEx = require('../utils/syntax-error-ex');
_ = require('underscore');
require('string.prototype.startswith');
/**
A simple factory class for FRESH and JSON Resumes.
@class ResumeFactory
*/
ResumeFactory = module.exports = {
/**
Load one or more resumes from disk.
@param {Object} opts An options object with settings for the factory as well
as passthrough settings for FRESHResume or JRSResume. Structure:
{
format: 'FRESH', // Format to open as. ('FRESH', 'JRS', null)
objectify: true, // FRESH/JRSResume or raw JSON?
inner: { // Passthru options for FRESH/JRSResume
sort: false
}
}
*/
load: function(sources, opts, emitter) {
return sources.map(function(src) {
return this.loadOne(src, opts, emitter);
}, this);
},
/** Load a single resume from disk. */
loadOne: function(src, opts, emitter) {
var ResumeClass, info, isFRESH, json, objectify, orgFormat, rez, toFormat;
toFormat = opts.format;
objectify = opts.objectify;
toFormat && (toFormat = toFormat.toLowerCase().trim());
info = _parse(src, opts, emitter);
if (info.fluenterror) {
return info;
}
json = info.json;
isFRESH = json.meta && json.meta.format && json.meta.format.startsWith('FRESH@');
orgFormat = isFRESH ? 'fresh' : 'jrs';
if (toFormat && (orgFormat !== toFormat)) {
json = ResumeConverter['to' + toFormat.toUpperCase()](json);
}
rez = null;
if (objectify) {
ResumeClass = require('../core/' + (toFormat || orgFormat) + '-resume');
rez = new ResumeClass().parseJSON(json, opts.inner);
rez.i().file = src;
}
return {
file: src,
json: info.json,
rez: rez
};
}
};
_parse = function(fileName, opts, eve) {
var ex, orgFormat, rawData, ret;
rawData = null;
try {
eve && eve.stat(HME.beforeRead, {
file: fileName
});
rawData = FS.readFileSync(fileName, 'utf8');
eve && eve.stat(HME.afterRead, {
file: fileName,
data: rawData
});
eve && eve.stat(HME.beforeParse, {
data: rawData
});
ret = {
json: JSON.parse(rawData)
};
orgFormat = ret.json.meta && ret.json.meta.format && ret.json.meta.format.startsWith('FRESH@') ? 'fresh' : 'jrs';
eve && eve.stat(HME.afterParse, {
file: fileName,
data: ret.json,
fmt: orgFormat
});
return ret;
} catch (_error) {
ex = {
fluenterror: rawData ? HACKMYSTATUS.parseError : HACKMYSTATUS.readError,
inner: _error,
raw: rawData,
file: fileName,
shouldExit: false
};
opts.quit && (ex.quit = true);
eve && eve.err(ex.fluenterror, ex);
if (opts["throw"]) {
throw ex;
}
return ex;
}
};
}).call(this);

380
dist/hmc/dist/core/resume.json vendored Normal file
View File

@ -0,0 +1,380 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "Resume Schema",
"type": "object",
"additionalProperties": false,
"properties": {
"basics": {
"type": "object",
"additionalProperties": true,
"properties": {
"name": {
"type": "string"
},
"label": {
"type": "string",
"description": "e.g. Web Developer"
},
"picture": {
"type": "string",
"description": "URL (as per RFC 3986) to a picture in JPEG or PNG format"
},
"email": {
"type": "string",
"description": "e.g. thomas@gmail.com",
"format": "email"
},
"phone": {
"type": "string",
"description": "Phone numbers are stored as strings so use any format you like, e.g. 712-117-2923"
},
"website": {
"type": "string",
"description": "URL (as per RFC 3986) to your website, e.g. personal homepage",
"format": "uri"
},
"summary": {
"type": "string",
"description": "Write a short 2-3 sentence biography about yourself"
},
"location": {
"type": "object",
"additionalProperties": true,
"properties": {
"address": {
"type": "string",
"description": "To add multiple address lines, use \n. For example, 1234 Glücklichkeit Straße\nHinterhaus 5. Etage li."
},
"postalCode": {
"type": "string"
},
"city": {
"type": "string"
},
"countryCode": {
"type": "string",
"description": "code as per ISO-3166-1 ALPHA-2, e.g. US, AU, IN"
},
"region": {
"type": "string",
"description": "The general region where you live. Can be a US state, or a province, for instance."
}
}
},
"profiles": {
"type": "array",
"description": "Specify any number of social networks that you participate in",
"additionalItems": false,
"items": {
"type": "object",
"additionalProperties": true,
"properties": {
"network": {
"type": "string",
"description": "e.g. Facebook or Twitter"
},
"username": {
"type": "string",
"description": "e.g. neutralthoughts"
},
"url": {
"type": "string",
"description": "e.g. http://twitter.com/neutralthoughts"
}
}
}
}
}
},
"work": {
"type": "array",
"additionalItems": false,
"items": {
"type": "object",
"additionalProperties": true,
"properties": {
"company": {
"type": "string",
"description": "e.g. Facebook"
},
"position": {
"type": "string",
"description": "e.g. Software Engineer"
},
"website": {
"type": "string",
"description": "e.g. http://facebook.com",
"format": "uri"
},
"startDate": {
"type": "string",
"description": "resume.json uses the ISO 8601 date standard e.g. 2014-06-29",
"format": "date"
},
"endDate": {
"type": "string",
"description": "e.g. 2012-06-29",
"format": "date"
},
"summary": {
"type": "string",
"description": "Give an overview of your responsibilities at the company"
},
"highlights": {
"type": "array",
"description": "Specify multiple accomplishments",
"additionalItems": false,
"items": {
"type": "string",
"description": "e.g. Increased profits by 20% from 2011-2012 through viral advertising"
}
}
}
}
},
"volunteer": {
"type": "array",
"additionalItems": false,
"items": {
"type": "object",
"additionalProperties": true,
"properties": {
"organization": {
"type": "string",
"description": "e.g. Facebook"
},
"position": {
"type": "string",
"description": "e.g. Software Engineer"
},
"website": {
"type": "string",
"description": "e.g. http://facebook.com",
"format": "uri"
},
"startDate": {
"type": "string",
"description": "resume.json uses the ISO 8601 date standard e.g. 2014-06-29",
"format": "date"
},
"endDate": {
"type": "string",
"description": "e.g. 2012-06-29",
"format": "date"
},
"summary": {
"type": "string",
"description": "Give an overview of your responsibilities at the company"
},
"highlights": {
"type": "array",
"description": "Specify multiple accomplishments",
"additionalItems": false,
"items": {
"type": "string",
"description": "e.g. Increased profits by 20% from 2011-2012 through viral advertising"
}
}
}
}
},
"education": {
"type": "array",
"additionalItems": false,
"items": {
"type": "object",
"additionalProperties": true,
"properties": {
"institution": {
"type": "string",
"description": "e.g. Massachusetts Institute of Technology"
},
"area": {
"type": "string",
"description": "e.g. Arts"
},
"studyType": {
"type": "string",
"description": "e.g. Bachelor"
},
"startDate": {
"type": "string",
"description": "e.g. 2014-06-29",
"format": "date"
},
"endDate": {
"type": "string",
"description": "e.g. 2012-06-29",
"format": "date"
},
"gpa": {
"type": "string",
"description": "grade point average, e.g. 3.67/4.0"
},
"courses": {
"type": "array",
"description": "List notable courses/subjects",
"additionalItems": false,
"items": {
"type": "string",
"description": "e.g. H1302 - Introduction to American history"
}
}
}
}
},
"awards": {
"type": "array",
"description": "Specify any awards you have received throughout your professional career",
"additionalItems": false,
"items": {
"type": "object",
"additionalProperties": true,
"properties": {
"title": {
"type": "string",
"description": "e.g. One of the 100 greatest minds of the century"
},
"date": {
"type": "string",
"description": "e.g. 1989-06-12",
"format": "date"
},
"awarder": {
"type": "string",
"description": "e.g. Time Magazine"
},
"summary": {
"type": "string",
"description": "e.g. Received for my work with Quantum Physics"
}
}
}
},
"publications": {
"type": "array",
"description": "Specify your publications through your career",
"additionalItems": false,
"items": {
"type": "object",
"additionalProperties": true,
"properties": {
"name": {
"type": "string",
"description": "e.g. The World Wide Web"
},
"publisher": {
"type": "string",
"description": "e.g. IEEE, Computer Magazine"
},
"releaseDate": {
"type": "string",
"description": "e.g. 1990-08-01"
},
"website": {
"type": "string",
"description": "e.g. http://www.computer.org/csdl/mags/co/1996/10/rx069-abs.html"
},
"summary": {
"type": "string",
"description": "Short summary of publication. e.g. Discussion of the World Wide Web, HTTP, HTML."
}
}
}
},
"skills": {
"type": "array",
"description": "List out your professional skill-set",
"additionalItems": false,
"items": {
"type": "object",
"additionalProperties": true,
"properties": {
"name": {
"type": "string",
"description": "e.g. Web Development"
},
"level": {
"type": "string",
"description": "e.g. Master"
},
"keywords": {
"type": "array",
"description": "List some keywords pertaining to this skill",
"additionalItems": false,
"items": {
"type": "string",
"description": "e.g. HTML"
}
}
}
}
},
"languages": {
"type": "array",
"description": "List any other languages you speak",
"additionalItems": false,
"items": {
"type": "object",
"additionalProperties": true,
"properties": {
"language": {
"type": "string",
"description": "e.g. English, Spanish"
},
"fluency": {
"type": "string",
"description": "e.g. Fluent, Beginner"
}
}
}
},
"interests": {
"type": "array",
"additionalItems": false,
"items": {
"type": "object",
"additionalProperties": true,
"properties": {
"name": {
"type": "string",
"description": "e.g. Philosophy"
},
"keywords": {
"type": "array",
"additionalItems": false,
"items": {
"type": "string",
"description": "e.g. Friedrich Nietzsche"
}
}
}
}
},
"references": {
"type": "array",
"description": "List references you have received",
"additionalItems": false,
"items": {
"type": "object",
"additionalProperties": true,
"properties": {
"name": {
"type": "string",
"description": "e.g. Timothy Cook"
},
"reference": {
"type": "string",
"description": "e.g. Joe blogs was a great employee, who turned up to work at least once a week. He exceeded my expectations when it came to doing nothing."
}
}
}
}
}
}

37
dist/hmc/dist/core/status-codes.js vendored Normal file
View File

@ -0,0 +1,37 @@
/**
Status codes for HackMyResume.
@module core/status-codes
@license MIT. See LICENSE.MD for details.
*/
(function() {
module.exports = {
success: 0,
themeNotFound: 1,
copyCss: 2,
resumeNotFound: 3,
missingCommand: 4,
invalidCommand: 5,
resumeNotFoundAlt: 6,
inputOutputParity: 7,
createNameMissing: 8,
pdfgeneration: 9,
missingPackageJSON: 10,
invalid: 11,
invalidFormat: 12,
notOnPath: 13,
readError: 14,
parseError: 15,
fileSaveError: 16,
generateError: 17,
invalidHelperUse: 18,
mixedMerge: 19,
invokeTemplate: 20,
compileTemplate: 21,
themeLoad: 22,
invalidParamCount: 23,
missingParam: 24
};
}).call(this);

View File

@ -0,0 +1,33 @@
/**
Definition of the BaseGenerator class.
@module base-generator.js
@license MIT. See LICENSE.md for details.
*/
(function() {
var BaseGenerator, Class;
Class = require('../utils/class');
/**
The BaseGenerator class is the root of the generator hierarchy. Functionality
common to ALL generators lives here.
*/
BaseGenerator = module.exports = Class.extend({
/** Base-class initialize. */
init: function(outputFormat) {
return this.format = outputFormat;
},
/** Status codes. */
codes: require('../core/status-codes'),
/** Generator options. */
opts: {}
});
}).call(this);

View File

@ -0,0 +1,42 @@
/**
Definition of the HTMLGenerator class.
@license MIT. See LICENSE.md for details.
@module html-generator.js
*/
(function() {
var FS, HTML, HtmlGenerator, PATH, TemplateGenerator;
TemplateGenerator = require('./template-generator');
FS = require('fs-extra');
HTML = require('html');
PATH = require('path');
require('string.prototype.endswith');
HtmlGenerator = module.exports = TemplateGenerator.extend({
init: function() {
return this._super('html');
},
/**
Copy satellite CSS files to the destination and optionally pretty-print
the HTML resume prior to saving.
*/
onBeforeSave: function(info) {
if (info.outputFile.endsWith('.css')) {
return info.mk;
}
if (this.opts.prettify) {
return HTML.prettyPrint(info.mk, this.opts.prettify);
} else {
return info.mk;
}
}
});
}).call(this);

View File

@ -0,0 +1,98 @@
/**
Definition of the HtmlPdfCLIGenerator class.
@module html-pdf-generator.js
@license MIT. See LICENSE.md for details.
*/
(function() {
var FS, HTML, HtmlPdfCLIGenerator, PATH, SLASH, SPAWN, TemplateGenerator, engines;
TemplateGenerator = require('./template-generator');
FS = require('fs-extra');
HTML = require('html');
PATH = require('path');
SPAWN = require('../utils/safe-spawn');
SLASH = require('slash');
/**
An HTML-driven PDF resume generator for HackMyResume. Talks to Phantom,
wkhtmltopdf, and other PDF engines over a CLI (command-line interface).
If an engine isn't installed for a particular platform, error out gracefully.
*/
HtmlPdfCLIGenerator = module.exports = TemplateGenerator.extend({
init: function() {
return this._super('pdf', 'html');
},
/** Generate the binary PDF. */
onBeforeSave: function(info) {
var ex, safe_eng;
try {
safe_eng = info.opts.pdf || 'wkhtmltopdf';
if (safe_eng !== 'none') {
engines[safe_eng].call(this, info.mk, info.outputFile);
return null;
}
} catch (_error) {
ex = _error;
if (ex.inner && ex.inner.code === 'ENOENT') {
throw {
fluenterror: this.codes.notOnPath,
inner: ex.inner,
engine: ex.cmd,
stack: ex.inner && ex.inner.stack
};
} else {
throw {
fluenterror: this.codes.pdfGeneration,
inner: ex,
stack: ex.stack
};
}
}
}
});
engines = {
/**
Generate a PDF from HTML using wkhtmltopdf's CLI interface.
Spawns a child process with `wkhtmltopdf <source> <target>`. wkhtmltopdf
must be installed and path-accessible.
TODO: If HTML generation has run, reuse that output
TODO: Local web server to ease wkhtmltopdf rendering
*/
wkhtmltopdf: function(markup, fOut) {
var info, tempFile;
tempFile = fOut.replace(/\.pdf$/i, '.pdf.html');
FS.writeFileSync(tempFile, markup, 'utf8');
return info = SPAWN('wkhtmltopdf', [tempFile, fOut]);
},
/**
Generate a PDF from HTML using Phantom's CLI interface.
Spawns a child process with `phantomjs <script> <source> <target>`. Phantom
must be installed and path-accessible.
TODO: If HTML generation has run, reuse that output
TODO: Local web server to ease Phantom rendering
*/
phantom: function(markup, fOut) {
var destPath, info, scriptPath, sourcePath, tempFile;
tempFile = fOut.replace(/\.pdf$/i, '.pdf.html');
FS.writeFileSync(tempFile, markup, 'utf8');
scriptPath = SLASH(PATH.relative(process.cwd(), PATH.resolve(__dirname, '../utils/rasterize.js')));
sourcePath = SLASH(PATH.relative(process.cwd(), tempFile));
destPath = SLASH(PATH.relative(process.cwd(), fOut));
return info = SPAWN('phantomjs', [scriptPath, sourcePath, destPath]);
}
};
}).call(this);

View File

@ -0,0 +1,64 @@
/**
Definition of the HtmlPngGenerator class.
@license MIT. See LICENSE.MD for details.
@module html-png-generator.js
*/
(function() {
var FS, HTML, HtmlPngGenerator, PATH, SLASH, SPAWN, TemplateGenerator, phantom;
TemplateGenerator = require('./template-generator');
FS = require('fs-extra');
HTML = require('html');
SLASH = require('slash');
SPAWN = require('../utils/safe-spawn');
PATH = require('path');
/**
An HTML-based PNG resume generator for HackMyResume.
*/
HtmlPngGenerator = module.exports = TemplateGenerator.extend({
init: function() {
return this._super('png', 'html');
},
invoke: function(rez, themeMarkup, cssInfo, opts) {},
generate: function(rez, f, opts) {
var htmlFile, htmlResults;
htmlResults = opts.targets.filter(function(t) {
return t.fmt.outFormat === 'html';
});
htmlFile = htmlResults[0].final.files.filter(function(fl) {
return fl.info.ext === 'html';
});
phantom(htmlFile[0].data, f);
}
});
/**
Generate a PDF from HTML using Phantom's CLI interface.
Spawns a child process with `phantomjs <script> <source> <target>`. Phantom
must be installed and path-accessible.
TODO: If HTML generation has run, reuse that output
TODO: Local web server to ease Phantom rendering
*/
phantom = function(markup, fOut) {
var destPath, info, scriptPath, sourcePath, tempFile;
tempFile = fOut.replace(/\.png$/i, '.png.html');
FS.writeFileSync(tempFile, markup, 'utf8');
scriptPath = SLASH(PATH.relative(process.cwd(), PATH.resolve(__dirname, '../utils/rasterize.js')));
sourcePath = SLASH(PATH.relative(process.cwd(), tempFile));
destPath = SLASH(PATH.relative(process.cwd(), fOut));
info = SPAWN('phantomjs', [scriptPath, sourcePath, destPath]);
};
}).call(this);

View File

@ -0,0 +1,45 @@
/**
Definition of the JsonGenerator class.
@license MIT. See LICENSE.md for details.
@module generators/json-generator
*/
(function() {
var BaseGenerator, FS, JsonGenerator, _;
BaseGenerator = require('./base-generator');
FS = require('fs');
_ = require('underscore');
/**
The JsonGenerator generates a JSON resume directly.
*/
JsonGenerator = module.exports = BaseGenerator.extend({
init: function() {
return this._super('json');
},
keys: ['imp', 'warnings', 'computed', 'filt', 'ctrl', 'index', 'safeStartDate', 'safeEndDate', 'safeDate', 'safeReleaseDate', 'result', 'isModified', 'htmlPreview', 'safe'],
invoke: function(rez) {
var replacer;
replacer = function(key, value) {
if (_.some(this.keys, function(val) {
return key.trim() === val;
})) {
return void 0;
} else {
return value;
}
};
return JSON.stringify(rez, replacer, 2);
},
generate: function(rez, f) {
FS.writeFileSync(f, this.invoke(rez), 'utf8');
}
});
}).call(this);

View File

@ -0,0 +1,38 @@
/**
Definition of the JsonYamlGenerator class.
@module json-yaml-generator.js
@license MIT. See LICENSE.md for details.
*/
(function() {
var BaseGenerator, FS, JsonYamlGenerator, YAML;
BaseGenerator = require('./base-generator');
FS = require('fs');
YAML = require('yamljs');
/**
JsonYamlGenerator takes a JSON resume object and translates it directly to
JSON without a template, producing an equivalent YAML-formatted resume. See
also YamlGenerator (yaml-generator.js).
*/
JsonYamlGenerator = module.exports = BaseGenerator.extend({
init: function() {
return this._super('yml');
},
invoke: function(rez, themeMarkup, cssInfo, opts) {
return YAML.stringify(JSON.parse(rez.stringify()), Infinity, 2);
},
generate: function(rez, f, opts) {
var data;
data = YAML.stringify(JSON.parse(rez.stringify()), Infinity, 2);
return FS.writeFileSync(f, data, 'utf8');
}
});
}).call(this);

View File

@ -0,0 +1,24 @@
/**
Definition of the LaTeXGenerator class.
@license MIT. See LICENSE.md for details.
@module generators/latex-generator
*/
(function() {
var LaTeXGenerator, TemplateGenerator;
TemplateGenerator = require('./template-generator');
/**
LaTeXGenerator generates a LaTeX resume via TemplateGenerator.
*/
LaTeXGenerator = module.exports = TemplateGenerator.extend({
init: function() {
return this._super('latex', 'tex');
}
});
}).call(this);

View File

@ -0,0 +1,24 @@
/**
Definition of the MarkdownGenerator class.
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
@module markdown-generator.js
*/
(function() {
var MarkdownGenerator, TemplateGenerator;
TemplateGenerator = require('./template-generator');
/**
MarkdownGenerator generates a Markdown-formatted resume via TemplateGenerator.
*/
MarkdownGenerator = module.exports = TemplateGenerator.extend({
init: function() {
return this._super('md', 'txt');
}
});
}).call(this);

View File

@ -0,0 +1,243 @@
/**
Definition of the TemplateGenerator class. TODO: Refactor
@license MIT. See LICENSE.md for details.
@module template-generator.js
*/
(function() {
var BaseGenerator, EXTEND, FRESHTheme, FS, JRSTheme, MD, MKDIRP, PATH, TemplateGenerator, XML, _, _defaultOpts, _reg, freeze, parsePath, unfreeze;
FS = require('fs-extra');
_ = require('underscore');
MD = require('marked');
XML = require('xml-escape');
PATH = require('path');
parsePath = require('parse-filepath');
MKDIRP = require('mkdirp');
BaseGenerator = require('./base-generator');
EXTEND = require('extend');
FRESHTheme = require('../core/fresh-theme');
JRSTheme = require('../core/jrs-theme');
/**
TemplateGenerator performs resume generation via local Handlebar or Underscore
style template expansion and is appropriate for text-based formats like HTML,
plain text, and XML versions of Microsoft Word, Excel, and OpenOffice.
@class TemplateGenerator
*/
TemplateGenerator = module.exports = BaseGenerator.extend({
/** Constructor. Set the output format and template format for this
generator. Will usually be called by a derived generator such as
HTMLGenerator or MarkdownGenerator.
*/
init: function(outputFormat, templateFormat, cssFile) {
this._super(outputFormat);
this.tplFormat = templateFormat || outputFormat;
},
/** Generate a resume using string-based inputs and outputs without touching
the filesystem.
@method invoke
@param rez A FreshResume object.
@param opts Generator options.
@returns {Array} An array of objects representing the generated output
files.
*/
invoke: function(rez, opts) {
var curFmt, results;
opts = opts ? (this.opts = EXTEND(true, {}, _defaultOpts, opts)) : this.opts;
curFmt = opts.themeObj.getFormat(this.format);
curFmt.files = _.sortBy(curFmt.files, function(fi) {
return fi.ext !== 'css';
});
results = curFmt.files.map(function(tplInfo, idx) {
var trx;
trx = this.single(rez, tplInfo.data, this.format, opts, opts.themeObj, curFmt);
if (tplInfo.ext === 'css') {
curFmt.files[idx].data = trx;
} else {
tplInfo.ext === 'html';
}
return {
info: tplInfo,
data: trx
};
}, this);
return {
files: results
};
},
/** Generate a resume using file-based inputs and outputs. Requires access
to the local filesystem.
@method generate
@param rez A FreshResume object.
@param f Full path to the output resume file to generate.
@param opts Generator options.
*/
generate: function(rez, f, opts) {
var curFmt, genInfo, outFolder;
this.opts = EXTEND(true, {}, _defaultOpts, opts);
genInfo = this.invoke(rez, null);
outFolder = parsePath(f).dirname;
curFmt = opts.themeObj.getFormat(this.format);
genInfo.files.forEach(function(file) {
var fileName, thisFilePath;
file.info.orgPath = file.info.orgPath || '';
thisFilePath = PATH.join(outFolder, file.info.orgPath);
if (this.onBeforeSave) {
file.data = this.onBeforeSave({
theme: opts.themeObj,
outputFile: file.info.major ? f : thisFilePath,
mk: file.data,
opts: this.opts
});
if (!file.data) {
return;
}
}
fileName = file.info.major ? f : thisFilePath;
MKDIRP.sync(PATH.dirname(fileName));
FS.writeFileSync(fileName, file.data, {
encoding: 'utf8',
flags: 'w'
});
if (this.onAfterSave) {
return this.onAfterSave({
outputFile: fileName,
mk: file.data,
opts: this.opts
});
}
}, this);
if (curFmt.symLinks) {
Object.keys(curFmt.symLinks).forEach(function(loc) {
var absLoc, absTarg, ref, type;
absLoc = PATH.join(outFolder, loc);
absTarg = PATH.join(PATH.dirname(absLoc), curFmt.symLinks[loc]);
type = (ref = parsePath(absLoc).extname) != null ? ref : {
'file': 'junction'
};
return FS.symlinkSync(absTarg, absLoc, type);
});
}
return genInfo;
},
/** Perform a single resume resume transformation using string-based inputs
and outputs without touching the local file system.
@param json A FRESH or JRS resume object.
@param jst The stringified template data
@param format The format name, such as "html" or "latex"
@param cssInfo Needs to be refactored.
@param opts Options and passthrough data.
*/
single: function(json, jst, format, opts, theme, curFmt) {
var eng, result;
if (this.opts.freezeBreaks) {
jst = freeze(jst);
}
eng = require('../renderers/' + theme.engine + '-generator');
result = eng.generate(json, jst, format, curFmt, opts, theme);
if (this.opts.freezeBreaks) {
result = unfreeze(result);
}
return result;
}
});
/** Export the TemplateGenerator function/ctor. */
module.exports = TemplateGenerator;
/** Freeze newlines for protection against errant JST parsers. */
freeze = function(markup) {
markup.replace(_reg.regN, _defaultOpts.nSym);
return markup.replace(_reg.regR, _defaultOpts.rSym);
};
/** Unfreeze newlines when the coast is clear. */
unfreeze = function(markup) {
markup.replace(_reg.regSymR, '\r');
return markup.replace(_reg.regSymN, '\n');
};
/** Default template generator options. */
_defaultOpts = {
engine: 'underscore',
keepBreaks: true,
freezeBreaks: false,
nSym: '&newl;',
rSym: '&retn;',
template: {
interpolate: /\{\{(.+?)\}\}/g,
escape: /\{\{\=(.+?)\}\}/g,
evaluate: /\{\%(.+?)\%\}/g,
comment: /\{\#(.+?)\#\}/g
},
filters: {
out: function(txt) {
return txt;
},
raw: function(txt) {
return txt;
},
xml: function(txt) {
return XML(txt);
},
md: function(txt) {
return MD(txt || '');
},
mdin: function(txt) {
return MD(txt || '').replace(/^\s*<p>|<\/p>\s*$/gi, '');
},
lower: function(txt) {
return txt.toLowerCase();
},
link: function(name, url) {
if (url) {
return '<a href="' + url + '">' + name + '</a>';
} else {
return name;
}
}
},
prettify: {
indent_size: 2,
unformatted: ['em', 'strong', 'a'],
max_char: 80
}
};
/** Regexes for linebreak preservation. */
_reg = {
regN: new RegExp('\n', 'g'),
regR: new RegExp('\r', 'g'),
regSymN: new RegExp(_defaultOpts.nSym, 'g'),
regSymR: new RegExp(_defaultOpts.rSym, 'g')
};
}).call(this);

View File

@ -0,0 +1,24 @@
/**
Definition of the TextGenerator class.
@license MIT. See LICENSE.md for details.
@module text-generator.js
*/
(function() {
var TemplateGenerator, TextGenerator;
TemplateGenerator = require('./template-generator');
/**
The TextGenerator generates a plain-text resume via the TemplateGenerator.
*/
TextGenerator = module.exports = TemplateGenerator.extend({
init: function() {
return this._super('txt');
}
});
}).call(this);

View File

@ -0,0 +1,19 @@
/*
Definition of the WordGenerator class.
@license MIT. See LICENSE.md for details.
@module generators/word-generator
*/
(function() {
var TemplateGenerator, WordGenerator;
TemplateGenerator = require('./template-generator');
WordGenerator = module.exports = TemplateGenerator.extend({
init: function() {
return this._super('doc', 'xml');
}
});
}).call(this);

View File

@ -0,0 +1,24 @@
/**
Definition of the XMLGenerator class.
@license MIT. See LICENSE.md for details.
@module generatprs/xml-generator
*/
(function() {
var BaseGenerator, XMLGenerator;
BaseGenerator = require('./base-generator');
/**
The XmlGenerator generates an XML resume via the TemplateGenerator.
*/
XMLGenerator = module.exports = BaseGenerator.extend({
init: function() {
return this._super('xml');
}
});
}).call(this);

View File

@ -0,0 +1,24 @@
/**
Definition of the YAMLGenerator class.
@module yaml-generator.js
@license MIT. See LICENSE.md for details.
*/
(function() {
var TemplateGenerator, YAMLGenerator;
TemplateGenerator = require('./template-generator');
/**
YamlGenerator generates a YAML-formatted resume via TemplateGenerator.
*/
YAMLGenerator = module.exports = TemplateGenerator.extend({
init: function() {
return this._super('yml', 'yml');
}
});
}).call(this);

View File

@ -0,0 +1,64 @@
/**
Generic template helper definitions for command-line output.
@module console-helpers.js
@license MIT. See LICENSE.md for details.
*/
(function() {
var CHALK, LO, PAD, _, consoleFormatHelpers;
PAD = require('string-padding');
LO = require('lodash');
CHALK = require('chalk');
_ = require('underscore');
require('../utils/string');
consoleFormatHelpers = module.exports = {
v: function(val, defaultVal, padding, style) {
var retVal, spaces;
retVal = val === null || val === void 0 ? defaultVal : val;
spaces = 0;
if (String.is(padding)) {
spaces = parseInt(padding, 10);
if (isNaN(spaces)) {
spaces = 0;
}
} else if (_.isNumber(padding)) {
spaces = padding;
}
if (spaces !== 0) {
retVal = PAD(retVal, Math.abs(spaces), null, spaces > 0 ? PAD.LEFT : PAD.RIGHT);
}
if (style && String.is(style)) {
retVal = LO.get(CHALK, style)(retVal);
}
return retVal;
},
gapLength: function(val) {
if (val < 35) {
return CHALK.green.bold(val);
} else if (val < 95) {
return CHALK.yellow.bold(val);
} else {
return CHALK.red.bold(val);
}
},
style: function(val, style) {
return LO.get(CHALK, style)(val);
},
isPlural: function(val, options) {
if (val > 1) {
return options.fn(this);
}
},
pad: function(val, spaces) {
return PAD(val, Math.abs(spaces), null, spaces > 0 ? PAD.LEFT : PAD.RIGHT);
}
};
}).call(this);

612
dist/hmc/dist/helpers/generic-helpers.js vendored Normal file
View File

@ -0,0 +1,612 @@
/**
Generic template helper definitions for HackMyResume / FluentCV.
@license MIT. See LICENSE.md for details.
@module helpers/generic-helpers
*/
(function() {
var FS, FluentDate, GenericHelpers, H2W, HMSTATUS, LO, MD, PATH, XML, _, _fromTo, _reportError, moment, printf, skillLevelToIndex, unused;
MD = require('marked');
H2W = require('../utils/html-to-wpml');
XML = require('xml-escape');
FluentDate = require('../core/fluent-date');
HMSTATUS = require('../core/status-codes');
moment = require('moment');
FS = require('fs');
LO = require('lodash');
PATH = require('path');
printf = require('printf');
_ = require('underscore');
unused = require('../utils/string');
/** Generic template helper function definitions. */
GenericHelpers = module.exports = {
/**
Convert the input date to a specified format through Moment.js.
If date is invalid, will return the time provided by the user,
or default to the fallback param or 'Present' if that is set to true
@method formatDate
*/
formatDate: function(datetime, format, fallback) {
var momentDate, ref, ref1;
if (moment) {
momentDate = moment(datetime);
if (momentDate.isValid()) {
return momentDate.format(format);
}
}
return datetime || ((ref = typeof fallback === 'string') != null ? ref : {
fallback: (ref1 = fallback === true) != null ? ref1 : {
'Present': null
}
});
},
/**
Given a resume sub-object with a start/end date, format a representation of
the date range.
@method dateRange
*/
dateRange: function(obj, fmt, sep, fallback, options) {
if (!obj) {
return '';
}
return _fromTo(obj.start, obj.end, fmt, sep, fallback, options);
},
/**
Format a from/to date range for display.
@method toFrom
*/
fromTo: function() {
return _fromTo.apply(this, arguments);
},
/**
Return a named color value as an RRGGBB string.
@method toFrom
*/
color: function(colorName, colorDefault) {
var ret;
if (!(colorName && colorName.trim())) {
return _reportError(HMSTATUS.invalidHelperUse, {
helper: 'fontList',
error: HMSTATUS.missingParam,
expected: 'name'
});
} else {
if (!GenericHelpers.theme.colors) {
return colorDefault;
}
ret = GenericHelpers.theme.colors[colorName];
if (!(ret && ret.trim())) {
return colorDefault;
}
return ret;
}
},
/**
Return true if the section is present on the resume and has at least one
element.
@method section
*/
section: function(title, options) {
var obj;
title = title.trim().toLowerCase();
obj = LO.get(this.r, title);
if (_.isArray(obj)) {
if (obj.length) {
return options.fn(this);
} else {
return void 0;
}
} else if (_.isObject(obj)) {
return (obj.history && obj.history.length) || (obj.sets && obj.sets.length ? options.fn(this) : void 0);
}
},
/**
Emit the size of the specified named font.
@param key {String} A named style from the "fonts" section of the theme's
theme.json file. For example: 'default' or 'heading1'.
*/
fontSize: function(key, defSize, units) {
var fontSpec, hasDef, ret;
ret = '';
hasDef = defSize && (String.is(defSize) || _.isNumber(defSize));
if (!(key && key.trim())) {
_reportError(HMSTATUS.invalidHelperUse, {
helper: 'fontSize',
error: HMSTATUS.missingParam,
expected: 'key'
});
return ret;
} else if (GenericHelpers.theme.fonts) {
fontSpec = LO.get(GenericHelpers.theme.fonts, this.format + '.' + key);
if (!fontSpec) {
if (GenericHelpers.theme.fonts.all) {
fontSpec = GenericHelpers.theme.fonts.all[key];
}
}
if (fontSpec) {
if (String.is(fontSpec)) {
} else if (_.isArray(fontSpec)) {
if (!String.is(fontSpec[0])) {
ret = fontSpec[0].size;
}
} else {
ret = fontSpec.size;
}
}
}
if (!ret) {
if (hasDef) {
ret = defSize;
} else {
_reportError(HMSTATUS.invalidHelperUse, {
helper: 'fontSize',
error: HMSTATUS.missingParam,
expected: 'defSize'
});
ret = '';
}
}
return ret;
},
/**
Emit the font face (such as 'Helvetica' or 'Calibri') associated with the
provided key.
@param key {String} A named style from the "fonts" section of the theme's
theme.json file. For example: 'default' or 'heading1'.
@param defFont {String} The font to use if the specified key isn't present.
Can be any valid font-face name such as 'Helvetica Neue' or 'Calibri'.
*/
fontFace: function(key, defFont) {
var fontSpec, hasDef, ret;
ret = '';
hasDef = defFont && String.is(defFont);
if (!(key && key.trim())) {
_reportError(HMSTATUS.invalidHelperUse, {
helper: 'fontFace',
error: HMSTATUS.missingParam,
expected: 'key'
});
return ret;
} else if (GenericHelpers.theme.fonts) {
fontSpec = LO.get(GenericHelpers.theme.fonts, this.format + '.' + key);
if (!fontSpec) {
if (GenericHelpers.theme.fonts.all) {
fontSpec = GenericHelpers.theme.fonts.all[key];
}
}
if (fontSpec) {
if (String.is(fontSpec)) {
ret = fontSpec;
} else if (_.isArray(fontSpec)) {
ret = String.is(fontSpec[0]) ? fontSpec[0] : fontSpec[0].name;
} else {
ret = fontSpec.name;
}
}
}
if (!(ret && ret.trim())) {
ret = defFont;
if (!hasDef) {
_reportError(HMSTATUS.invalidHelperUse, {
helper: 'fontFace',
error: HMSTATUS.missingParam,
expected: 'defFont'
});
ret = '';
}
}
return ret;
},
/**
Emit a comma-delimited list of font names suitable associated with the
provided key.
@param key {String} A named style from the "fonts" section of the theme's
theme.json file. For example: 'default' or 'heading1'.
@param defFontList {Array} The font list to use if the specified key isn't
present. Can be an array of valid font-face name such as 'Helvetica Neue'
or 'Calibri'.
@param sep {String} The default separator to use in the rendered output.
Defaults to ", " (comma with a space).
*/
fontList: function(key, defFontList, sep) {
var fontSpec, hasDef, ret;
ret = '';
hasDef = defFontList && String.is(defFontList);
if (!(key && key.trim())) {
_reportError(HMSTATUS.invalidHelperUse, {
helper: 'fontList',
error: HMSTATUS.missingParam,
expected: 'key'
});
} else if (GenericHelpers.theme.fonts) {
fontSpec = LO.get(GenericHelpers.theme.fonts, this.format + '.' + key);
if (!fontSpec) {
if (GenericHelpers.theme.fonts.all) {
fontSpec = GenericHelpers.theme.fonts.all[key];
}
}
if (fontSpec) {
if (String.is(fontSpec)) {
ret = fontSpec;
} else if (_.isArray(fontSpec)) {
fontSpec = fontSpec.map(function(ff) {
return "'" + (String.is(ff) ? ff : ff.name) + "'";
});
ret = fontSpec.join(sep === void 0 ? ', ' : sep || '');
} else {
ret = fontSpec.name;
}
}
}
if (!(ret && ret.trim())) {
if (!hasDef) {
_reportError(HMSTATUS.invalidHelperUse, {
helper: 'fontList',
error: HMSTATUS.missingParam,
expected: 'defFontList'
});
ret = '';
} else {
ret = defFontList;
}
}
return ret;
},
/**
Capitalize the first letter of the word.
@method section
*/
camelCase: function(val) {
val = (val && val.trim()) || '';
if (val) {
return val.charAt(0).toUpperCase() + val.slice(1);
} else {
return val;
}
},
/**
Return true if the context has the property or subpropery.
@method has
*/
has: function(title, options) {
title = title && title.trim().toLowerCase();
if (LO.get(this.r, title)) {
return options.fn(this);
}
},
/**
Generic template helper function to display a user-overridable section
title for a FRESH resume theme. Use this in lieue of hard-coding section
titles.
Usage:
{{sectionTitle "sectionName"}}
{{sectionTitle "sectionName" "sectionTitle"}}
Example:
{{sectionTitle "Education"}}
{{sectionTitle "Employment" "Project History"}}
@param sect_name The name of the section being title. Must be one of the
top-level FRESH resume sections ("info", "education", "employment", etc.).
@param sect_title The theme-specified section title. May be replaced by the
user.
@method sectionTitle
*/
sectionTitle: function(sname, stitle) {
stitle = (stitle && String.is(stitle) && stitle) || sname;
return (this.opts.stitles && this.opts.stitles[sname.toLowerCase().trim()]) || stitle;
},
/**
Convert inline Markdown to inline WordProcessingML.
@method wpml
*/
wpml: function(txt, inline) {
if (!txt) {
return '';
}
inline = (inline && !inline.hash) || false;
txt = XML(txt.trim());
txt = inline ? MD(txt).replace(/^\s*<p>|<\/p>\s*$/gi, '') : MD(txt);
txt = H2W(txt);
return txt;
},
/**
Emit a conditional link.
@method link
*/
link: function(text, url) {
if (url && url.trim()) {
return '<a href="' + url + '">' + text + '</a>';
} else {
return text;
}
},
/**
Return the last word of the specified text.
@method lastWord
*/
lastWord: function(txt) {
if (txt && txt.trim()) {
return _.last(txt.split(' '));
} else {
return '';
}
},
/**
Convert a skill level to an RGB color triplet. TODO: refactor
@method skillColor
@param lvl Input skill level. Skill level can be expressed as a string
("beginner", "intermediate", etc.), as an integer (1,5,etc), as a string
integer ("1", "5", etc.), or as an RRGGBB color triplet ('#C00000',
'#FFFFAA').
*/
skillColor: function(lvl) {
var idx, skillColors;
idx = skillLevelToIndex(lvl);
skillColors = (this.theme && this.theme.palette && this.theme.palette.skillLevels) || ['#FFFFFF', '#5CB85C', '#F1C40F', '#428BCA', '#C00000'];
return skillColors[idx];
},
/**
Return an appropriate height. TODO: refactor
@method lastWord
*/
skillHeight: function(lvl) {
var idx;
idx = skillLevelToIndex(lvl);
return ['38.25', '30', '16', '8', '0'][idx];
},
/**
Return all but the last word of the input text.
@method initialWords
*/
initialWords: function(txt) {
if (txt && txt.trim()) {
return _.initial(txt.split(' ')).join(' ');
} else {
return '';
}
},
/**
Trim the protocol (http or https) from a URL/
@method trimURL
*/
trimURL: function(url) {
if (url && url.trim()) {
return url.trim().replace(/^https?:\/\//i, '');
} else {
return '';
}
},
/**
Convert text to lowercase.
@method toLower
*/
toLower: function(txt) {
if (txt && txt.trim()) {
return txt.toLowerCase();
} else {
return '';
}
},
/**
Convert text to lowercase.
@method toLower
*/
toUpper: function(txt) {
if (txt && txt.trim()) {
return txt.toUpperCase();
} else {
return '';
}
},
/**
Return true if either value is truthy.
@method either
*/
either: function(lhs, rhs, options) {
if (lhs || rhs) {
return options.fn(this);
}
},
/**
Conditional stylesheet link. Creates a link to the specified stylesheet with
<link> or embeds the styles inline with <style></style>, depending on the
theme author's and user's preferences.
@param url {String} The path to the CSS file.
@param linkage {String} The default link method. Can be either `embed` or
`link`. If omitted, defaults to `embed`. Can be overridden by the `--css`
command-line switch.
*/
styleSheet: function(url, linkage) {
var rawCss, renderedCss, ret;
linkage = this.opts.css || linkage || 'embed';
ret = '';
if (linkage === 'link') {
ret = printf('<link href="%s" rel="stylesheet" type="text/css">', url);
} else {
rawCss = FS.readFileSync(PATH.join(this.opts.themeObj.folder, '/src/', url), 'utf8');
renderedCss = this.engine.generateSimple(this, rawCss);
ret = printf('<style>%s</style>', renderedCss);
}
if (this.opts.themeObj.inherits && this.opts.themeObj.inherits.html && this.format === 'html') {
ret += linkage === 'link' ? '<link href="' + this.opts.themeObj.overrides.path + '" rel="stylesheet" type="text/css">' : '<style>' + this.opts.themeObj.overrides.data + '</style>';
}
return ret;
},
/**
Perform a generic comparison.
See: http://doginthehat.com.au/2012/02/comparison-block-helper-for-handlebars-templates
@method compare
*/
compare: function(lvalue, rvalue, options) {
var operator, operators, result;
if (arguments.length < 3) {
throw new Error("Handlerbars Helper 'compare' needs 2 parameters");
}
operator = options.hash.operator || "==";
operators = {
'==': function(l, r) {
return l === r;
},
'===': function(l, r) {
return l === r;
},
'!=': function(l, r) {
return l !== r;
},
'<': function(l, r) {
return l < r;
},
'>': function(l, r) {
return l > r;
},
'<=': function(l, r) {
return l <= r;
},
'>=': function(l, r) {
return l >= r;
},
'typeof': function(l, r) {
return typeof l === r;
}
};
if (!operators[operator]) {
throw new Error("Handlerbars Helper 'compare' doesn't know the operator " + operator);
}
result = operators[operator](lvalue, rvalue);
if (result) {
return options.fn(this);
} else {
return options.inverse(this);
}
}
};
/**
Report an error to the outside world without throwing an exception. Currently
relies on kludging the running verb into. opts.
*/
_reportError = function(code, params) {
return GenericHelpers.opts.errHandler.err(code, params);
};
/**
Format a from/to date range for display.
*/
_fromTo = function(dateA, dateB, fmt, sep, fallback) {
var dateATrim, dateBTrim, dateFrom, dateTemp, dateTo, reserved;
if (moment.isMoment(dateA) || moment.isMoment(dateB)) {
_reportError(HMSTATUS.invalidHelperUse, {
helper: 'dateRange'
});
return '';
}
dateFrom = null;
dateTo = null;
dateTemp = null;
dateA = dateA || '';
dateB = dateB || '';
dateATrim = dateA.trim().toLowerCase();
dateBTrim = dateB.trim().toLowerCase();
reserved = ['current', 'present', 'now', ''];
fmt = (fmt && String.is(fmt) && fmt) || 'YYYY-MM';
sep = (sep && String.is(sep) && sep) || ' — ';
if (_.contains(reserved, dateATrim)) {
dateFrom = fallback || '???';
} else {
dateTemp = FluentDate.fmt(dateA);
dateFrom = dateTemp.format(fmt);
}
if (_.contains(reserved, dateBTrim)) {
dateTo = fallback || 'Current';
} else {
dateTemp = FluentDate.fmt(dateB);
dateTo = dateTemp.format(fmt);
}
if (dateFrom && dateTo) {
return dateFrom + sep + dateTo;
} else if (dateFrom || dateTo) {
return dateFrom || dateTo;
}
return '';
};
skillLevelToIndex = function(lvl) {
var idx, intVal;
idx = 0;
if (String.is(lvl)) {
lvl = lvl.trim().toLowerCase();
intVal = parseInt(lvl);
if (isNaN(intVal)) {
switch (lvl) {
case 'beginner':
idx = 1;
break;
case 'intermediate':
idx = 2;
break;
case 'advanced':
idx = 3;
break;
case 'master':
idx = 4;
}
} else {
idx = Math.min(intVal / 2, 4);
idx = Math.max(0, idx);
}
} else {
idx = Math.min(lvl / 2, 4);
idx = Math.max(0, idx);
}
return idx;
};
}).call(this);

View File

@ -0,0 +1,29 @@
/**
Template helper definitions for Handlebars.
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
@module handlebars-helpers.js
*/
(function() {
var HANDLEBARS, _, helpers;
HANDLEBARS = require('handlebars');
_ = require('underscore');
helpers = require('./generic-helpers');
/**
Register useful Handlebars helpers.
@method registerHelpers
*/
module.exports = function(theme, opts) {
helpers.theme = theme;
helpers.opts = opts;
return HANDLEBARS.registerHelper(helpers);
};
}).call(this);

View File

@ -0,0 +1,36 @@
/**
Template helper definitions for Underscore.
@license MIT. Copyright (c) 2016 hacksalot (https://github.com/hacksalot)
@module handlebars-helpers.js
*/
(function() {
var HANDLEBARS, _, helpers;
HANDLEBARS = require('handlebars');
_ = require('underscore');
helpers = require('./generic-helpers');
/**
Register useful Underscore helpers.
@method registerHelpers
*/
module.exports = function(theme, opts, cssInfo, ctx, eng) {
helpers.theme = theme;
helpers.opts = opts;
helpers.cssInfo = cssInfo;
helpers.engine = eng;
ctx.h = helpers;
return _.each(helpers, function(hVal, hKey) {
if (_.isFunction(hVal)) {
return _.bind(hVal, ctx);
}
}, this);
};
}).call(this);

49
dist/hmc/dist/index.js vendored Normal file
View File

@ -0,0 +1,49 @@
/**
External API surface for HackMyResume.
@license MIT. See LICENSE.md for details.
@module hackmycore/index
*/
/**
API facade for HackMyCore.
*/
(function() {
var HackMyCore;
HackMyCore = module.exports = {
verbs: {
build: require('./verbs/build'),
analyze: require('./verbs/analyze'),
validate: require('./verbs/validate'),
convert: require('./verbs/convert'),
"new": require('./verbs/create'),
peek: require('./verbs/peek')
},
alias: {
generate: require('./verbs/build'),
create: require('./verbs/create')
},
options: require('./core/default-options'),
formats: require('./core/default-formats'),
Sheet: require('./core/fresh-resume'),
FRESHResume: require('./core/fresh-resume'),
JRSResume: require('./core/jrs-resume'),
FRESHTheme: require('./core/fresh-theme'),
JRSTheme: require('./core/jrs-theme'),
FluentDate: require('./core/fluent-date'),
HtmlGenerator: require('./generators/html-generator'),
TextGenerator: require('./generators/text-generator'),
HtmlPdfCliGenerator: require('./generators/html-pdf-cli-generator'),
WordGenerator: require('./generators/word-generator'),
MarkdownGenerator: require('./generators/markdown-generator'),
JsonGenerator: require('./generators/json-generator'),
YamlGenerator: require('./generators/yaml-generator'),
JsonYamlGenerator: require('./generators/json-yaml-generator'),
LaTeXGenerator: require('./generators/latex-generator'),
HtmlPngGenerator: require('./generators/html-png-generator')
};
}).call(this);

View File

@ -0,0 +1,138 @@
/**
Employment gap analysis for HackMyResume.
@license MIT. See LICENSE.md for details.
@module inspectors/gap-inspector
*/
(function() {
var FluentDate, LO, _, gapInspector, moment;
_ = require('underscore');
FluentDate = require('../core/fluent-date');
moment = require('moment');
LO = require('lodash');
/**
Identify gaps in the candidate's employment history.
*/
gapInspector = module.exports = {
moniker: 'gap-inspector',
/**
Run the Gap Analyzer on a resume.
@method run
@return {Array} An array of object representing gaps in the candidate's
employment history. Each object provides the start, end, and duration of the
gap:
{ <-- gap
start: // A Moment.js date
end: // A Moment.js date
duration: // Gap length
}
*/
run: function(rez) {
var coverage, dur, g, gap_start, hist, new_e, num_gaps, o, ref_count, tdur, total_gap_days;
coverage = {
gaps: [],
overlaps: [],
pct: '0%',
duration: {
total: 0,
work: 0,
gaps: 0
}
};
hist = LO.get(rez, 'employment.history');
if (!hist || !hist.length) {
return coverage;
}
new_e = hist.map(function(job) {
var obj;
obj = _.pick(job, ['start', 'end']);
if (obj && (obj.start || obj.end)) {
obj = _.pairs(obj);
obj[0][1] = FluentDate.fmt(obj[0][1]);
if (obj.length > 1) {
obj[1][1] = FluentDate.fmt(obj[1][1]);
}
}
return obj;
});
new_e = _.filter(_.flatten(new_e, true), function(v) {
return v && v.length && v[0] && v[0].length;
});
if (!new_e || !new_e.length) {
return coverage;
}
new_e = _.sortBy(new_e, function(elem) {
return elem[1].unix();
});
num_gaps = 0;
ref_count = 0;
total_gap_days = 0;
gap_start = null;
new_e.forEach(function(point) {
var inc, lastGap, lastOver;
inc = point[0] === 'start' ? 1 : -1;
ref_count += inc;
if (ref_count === 0) {
return coverage.gaps.push({
start: point[1],
end: null
});
} else if (ref_count === 1 && inc === 1) {
lastGap = _.last(coverage.gaps);
if (lastGap) {
lastGap.end = point[1];
lastGap.duration = lastGap.end.diff(lastGap.start, 'days');
return total_gap_days += lastGap.duration;
}
} else if (ref_count === 2 && inc === 1) {
return coverage.overlaps.push({
start: point[1],
end: null
});
} else if (ref_count === 1 && inc === -1) {
lastOver = _.last(coverage.overlaps);
if (lastOver) {
lastOver.end = point[1];
lastOver.duration = lastOver.end.diff(lastOver.start, 'days');
if (lastOver.duration === 0) {
return coverage.overlaps.pop();
}
}
}
});
if (coverage.overlaps.length) {
o = _.last(coverage.overlaps);
if (o && !o.end) {
o.end = moment();
o.duration = o.end.diff(o.start, 'days');
}
}
if (coverage.gaps.length) {
g = _.last(coverage.gaps);
if (g && !g.end) {
g.end = moment();
g.duration = g.end.diff(g.start, 'days');
}
}
tdur = rez.duration('days');
dur = {
total: tdur,
work: tdur - total_gap_days,
gaps: total_gap_days
};
coverage.pct = dur.total > 0 && dur.work > 0 ? (((dur.total - dur.gaps) / dur.total) * 100).toFixed(1) + '%' : '???';
coverage.duration = dur;
return coverage;
}
};
}).call(this);

View File

@ -0,0 +1,61 @@
/**
Keyword analysis for HackMyResume.
@license MIT. See LICENSE.md for details.
@module inspectors/keyword-inspector
*/
(function() {
var FluentDate, _, keywordInspector;
_ = require('underscore');
FluentDate = require('../core/fluent-date');
/**
Analyze the resume's use of keywords.
TODO: BUG: Keyword search regex is inaccurate, especially for one or two
letter keywords like "C" or "CLI".
@class keywordInspector
*/
keywordInspector = module.exports = {
/** A unique name for this inspector. */
moniker: 'keyword-inspector',
/**
Run the Keyword Inspector on a resume.
@method run
@return An collection of statistical keyword data.
*/
run: function(rez) {
var prefix, regex_quote, searchable, suffix;
regex_quote = function(str) {
return (str + '').replace(/[.?*+^$[\]\\(){}|-]/ig, "\\$&");
};
searchable = '';
rez.transformStrings(['imp', 'computed', 'safe'], function(key, val) {
return searchable += ' ' + val;
});
prefix = '(?:' + ['^', '\\s+', '[\\.,]+'].join('|') + ')';
suffix = '(?:' + ['$', '\\s+', '[\\.,]+'].join('|') + ')';
return rez.keywords().map(function(kw) {
var count, myArray, regex, regex_str;
regex_str = prefix + regex_quote(kw) + suffix;
regex = new RegExp(regex_str, 'ig');
myArray = null;
count = 0;
while ((myArray = regex.exec(searchable)) !== null) {
count++;
}
return {
name: kw,
count: count
};
});
}
};
}).call(this);

View File

@ -0,0 +1,49 @@
/**
Section analysis for HackMyResume.
@license MIT. See LICENSE.md for details.
@module inspectors/totals-inspector
*/
(function() {
var FluentDate, _, totalsInspector;
_ = require('underscore');
FluentDate = require('../core/fluent-date');
/**
Retrieve sectional overview and summary information.
@class totalsInspector
*/
totalsInspector = module.exports = {
moniker: 'totals-inspector',
/**
Run the Totals Inspector on a resume.
@method run
@return An object containing summary information for each section on the
resume.
*/
run: function(rez) {
var sectionTotals;
sectionTotals = {};
_.each(rez, function(val, key) {
if (_.isArray(val) && !_.isString(val)) {
return sectionTotals[key] = val.length;
} else if (val.history && _.isArray(val.history)) {
return sectionTotals[key] = val.history.length;
} else if (val.sets && _.isArray(val.sets)) {
return sectionTotals[key] = val.sets.length;
}
});
return {
totals: sectionTotals,
numSections: Object.keys(sectionTotals).length
};
}
};
}).call(this);

View File

@ -0,0 +1,100 @@
/**
Definition of the HandlebarsGenerator class.
@license MIT. See LICENSE.md for details.
@module renderers/handlebars-generator
*/
(function() {
var FS, HANDLEBARS, HMSTATUS, HandlebarsGenerator, PATH, READFILES, SLASH, _, parsePath, registerHelpers, registerPartials;
_ = require('underscore');
HANDLEBARS = require('handlebars');
FS = require('fs');
registerHelpers = require('../helpers/handlebars-helpers');
PATH = require('path');
parsePath = require('parse-filepath');
READFILES = require('recursive-readdir-sync');
HMSTATUS = require('../core/status-codes');
SLASH = require('slash');
/**
Perform template-based resume generation using Handlebars.js.
@class HandlebarsGenerator
*/
HandlebarsGenerator = module.exports = {
generateSimple: function(data, tpl) {
var template;
try {
template = HANDLEBARS.compile(tpl, {
strict: false,
assumeObjects: false
});
return template(data);
} catch (_error) {
throw {
fluenterror: template ? HMSTATUS.invokeTemplate : HMSTATUS.compileTemplate,
inner: _error
};
}
},
generate: function(json, jst, format, curFmt, opts, theme) {
var ctx, encData;
registerPartials(format, theme);
registerHelpers(theme, opts);
encData = json;
if (format === 'html' || format === 'pdf') {
encData = json.markdownify();
}
if (format === 'doc') {
encData = json.xmlify();
}
ctx = {
r: encData,
RAW: json,
filt: opts.filters,
format: format,
opts: opts,
engine: this,
results: curFmt.files,
headFragment: opts.headFragment || ''
};
return this.generateSimple(ctx, jst);
}
};
registerPartials = function(format, theme) {
var partialsFolder;
if (_.contains(['html', 'doc', 'md', 'txt', 'pdf'], format)) {
partialsFolder = PATH.join(parsePath(require.resolve('fresh-themes')).dirname, '/partials/', format === 'pdf' ? 'html' : format);
_.each(READFILES(partialsFolder, function(error) {
return {};
}), function(el) {
var compiledTemplate, name, pathInfo, tplData;
pathInfo = parsePath(el);
name = SLASH(PATH.relative(partialsFolder, el).replace(/\.(?:html|xml|hbs|md|txt)$/i, ''));
tplData = FS.readFileSync(el, 'utf8');
compiledTemplate = HANDLEBARS.compile(tplData);
HANDLEBARS.registerPartial(name, compiledTemplate);
return theme.partialsInitialized = true;
});
}
return _.each(theme.partials, function(el) {
var compiledTemplate, tplData;
tplData = FS.readFileSync(el.path, 'utf8');
compiledTemplate = HANDLEBARS.compile(tplData);
return HANDLEBARS.registerPartial(el.name, compiledTemplate);
});
};
}).call(this);

View File

@ -0,0 +1,58 @@
/**
Definition of the JRSGenerator class.
@license MIT. See LICENSE.md for details.
@module renderers/jrs-generator
*/
(function() {
var FS, HANDLEBARS, JRSGenerator, MD, MDIN, PATH, READFILES, SLASH, _, parsePath, registerHelpers;
_ = require('underscore');
HANDLEBARS = require('handlebars');
FS = require('fs');
registerHelpers = require('../helpers/handlebars-helpers');
PATH = require('path');
parsePath = require('parse-filepath');
READFILES = require('recursive-readdir-sync');
SLASH = require('slash');
MD = require('marked');
/**
Perform template-based resume generation for JSON Resume themes.
@class JRSGenerator
*/
JRSGenerator = module.exports = {
generate: function(json, jst, format, cssInfo, opts, theme) {
var org, rezHtml, turnoff;
turnoff = ['log', 'error', 'dir'];
org = turnoff.map(c)(function() {
var ret;
ret = console[c];
return console[c] = function() {};
});
rezHtml = theme.render(json.harden());
turnoff.forEach(function(c, idx) {
return console[c] = org[idx];
});
return rezHtml = rezHtml.replace(/@@@@~.*?~@@@@/gm, function(val) {
return MDIN(val.replace(/~@@@@/gm, '').replace(/@@@@~/gm, ''));
});
}
};
MDIN = function(txt) {
return MD(txt || '').replace(/^\s*<p>|<\/p>\s*$/gi, '');
};
}).call(this);

View File

@ -0,0 +1,60 @@
/**
Definition of the UnderscoreGenerator class.
@license MIT. See LICENSE.md for details.
@module underscore-generator.js
*/
(function() {
var HMSTATUS, UnderscoreGenerator, _, registerHelpers;
_ = require('underscore');
registerHelpers = require('../helpers/underscore-helpers');
HMSTATUS = require('../core/status-codes');
/**
Perform template-based resume generation using Underscore.js.
@class UnderscoreGenerator
*/
UnderscoreGenerator = module.exports = {
generateSimple: function(data, tpl) {
var template;
try {
template = _.template(tpl);
return template(data);
} catch (_error) {
throw {
fluenterror: template ? HMSTATUS.invokeTemplate : HMSTATUS.compileTemplate,
inner: _error
};
}
},
generate: function(json, jst, format, cssInfo, opts, theme) {
var ctx, delims;
delims = (opts.themeObj && opts.themeObj.delimeters) || opts.template;
if (opts.themeObj && opts.themeObj.delimeters) {
delims = _.mapObject(delims, function(val, key) {
return new RegExp(val, "ig");
});
}
_.templateSettings = delims;
jst = jst.replace(delims.comment, '');
ctx = {
r: format === 'html' || format === 'pdf' || format === 'png' ? json.markdownify() : json,
filt: opts.filters,
XML: require('xml-escape'),
RAW: json,
cssInfo: cssInfo,
headFragment: opts.headFragment || '',
opts: opts
};
registerHelpers(theme, opts, cssInfo, ctx, this);
return this.generateSimple(ctx, jst);
}
};
}).call(this);

72
dist/hmc/dist/utils/class.js vendored Normal file
View File

@ -0,0 +1,72 @@
/**
Definition of John Resig's `Class` class.
@module class.js
*/
/* Simple JavaScript Inheritance
* By John Resig http://ejohn.org/
* MIT Licensed.
* http://ejohn.org/blog/simple-javascript-inheritance/
*/
// Inspired by base2 and Prototype
(function(){
var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
// The base Class implementation (does nothing)
this.Class = function(){};
module.exports = Class;
// Create a new Class that inherits from this class
Class.extend = function(prop) {
var _super = this.prototype;
// Instantiate a base class (but only create the instance,
// don't run the init constructor)
initializing = true;
var prototype = new this();
initializing = false;
// Copy the properties over onto the new prototype
for (var name in prop) {
// Check if we're overwriting an existing function
prototype[name] = typeof prop[name] == "function" &&
typeof _super[name] == "function" && fnTest.test(prop[name]) ?
(function(name, fn){
return function() {
var tmp = this._super;
// Add a new ._super() method that is the same method
// but on the super-class
this._super = _super[name];
// The method only need to be bound temporarily, so we
// remove it when we're done executing
var ret = fn.apply(this, arguments);
this._super = tmp;
return ret;
};
})(name, prop[name]) : // jshint ignore:line
prop[name];
}
// The dummy class constructor
function Class() {
// All construction is actually done in the init method
if ( !initializing && this.init )
this.init.apply(this, arguments);
}
// Populate our constructed prototype object
Class.prototype = prototype;
// Enforce the constructor to be what we expect
Class.prototype.constructor = Class;
// And make this class extendable
Class.extend = arguments.callee;
return Class;
};
})();

12
dist/hmc/dist/utils/file-contains.js vendored Normal file
View File

@ -0,0 +1,12 @@
/**
Definition of the SyntaxErrorEx class.
@module file-contains.js
*/
(function() {
module.exports = function(file, needle) {
return require('fs').readFileSync(file, 'utf-8').indexOf(needle) > -1;
};
}).call(this);

61
dist/hmc/dist/utils/html-to-wpml.js vendored Normal file
View File

@ -0,0 +1,61 @@
/**
Definition of the Markdown to WordProcessingML conversion routine.
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
@module utils/html-to-wpml
*/
(function() {
var HTML5Tokenizer, _;
_ = require('underscore');
HTML5Tokenizer = require('simple-html-tokenizer');
module.exports = function(html) {
var final, is_bold, is_italic, is_link, link_url, tokens;
tokens = HTML5Tokenizer.tokenize(html);
final = is_bold = is_italic = is_link = link_url = '';
_.each(tokens, function(tok) {
var style;
switch (tok.type) {
case 'StartTag':
switch (tok.tagName) {
case 'p':
return final += '<w:p>';
case 'strong':
return is_bold = true;
case 'em':
return is_italic = true;
case 'a':
is_link = true;
return link_url = tok.attributes.filter(function(attr) {
return attr[0] === 'href';
})[0][1];
}
break;
case 'EndTag':
switch (tok.tagName) {
case 'p':
return final += '</w:p>';
case 'strong':
return is_bold = false;
case 'em':
return is_italic = false;
case 'a':
return is_link = false;
}
break;
case 'Chars':
if ((tok.chars.trim().length)) {
style = is_bold ? '<w:b/>' : '';
style += is_italic ? '<w:i/>' : '';
style += is_link ? '<w:rStyle w:val="Hyperlink"/>' : '';
return final += (is_link ? '<w:hlink w:dest="' + link_url + '">' : '') + '<w:r><w:rPr>' + style + '</w:rPr><w:t>' + tok.chars + '</w:t></w:r>' + (is_link ? '</w:hlink>' : '');
}
}
});
return final;
};
}).call(this);

28
dist/hmc/dist/utils/md2chalk.js vendored Normal file
View File

@ -0,0 +1,28 @@
/**
Inline Markdown-to-Chalk conversion routines.
@license MIT. See LICENSE.md for details.
@module utils/md2chalk
*/
(function() {
var CHALK, LO, MD;
MD = require('marked');
CHALK = require('chalk');
LO = require('lodash');
module.exports = function(v, style, boldStyle) {
var temp;
boldStyle = boldStyle || 'bold';
temp = v.replace(/\*\*(.*?)\*\*/g, LO.get(CHALK, boldStyle)('$1'));
if (style) {
return LO.get(CHALK, style)(temp);
} else {
return temp;
}
};
}).call(this);

77
dist/hmc/dist/utils/rasterize.js vendored Normal file
View File

@ -0,0 +1,77 @@
(function() {
"use strict";
var address, output, page, pageHeight, pageWidth, size, system;
page = require('webpage').create();
system = require('system');
address = output = size = null;
if (system.args.length < 3 || system.args.length > 5) {
console.log('Usage: rasterize.js URL filename [paperwidth*paperheight|paperformat] [zoom]');
console.log(' paper (pdf output) examples: "5in*7.5in", "10cm*20cm", "A4", "Letter"');
console.log(' image (png/jpg output) examples: "1920px" entire page, window width 1920px');
console.log(' "800px*600px" window, clipped to 800x600');
phantom.exit(1);
} else {
address = system.args[1];
output = system.args[2];
page.viewportSize = {
width: 600,
height: 600
};
if (system.args.length > 3 && system.args[2].substr(-4) === ".pdf") {
size = system.args[3].split('*');
page.paperSize = size.length === 2 ? {
width: size[0],
height: size[1],
margin: '0px'
} : {
format: system.args[3],
orientation: 'portrait',
margin: '1cm'
};
} else if (system.args.length > 3 && system.args[3].substr(-2) === "px") {
size = system.args[3].split('*');
if (size.length === 2) {
pageWidth = parseInt(size[0], 10);
pageHeight = parseInt(size[1], 10);
page.viewportSize = {
width: pageWidth,
height: pageHeight
};
page.clipRect = {
top: 0,
left: 0,
width: pageWidth,
height: pageHeight
};
} else {
console.log("size:", system.args[3]);
pageWidth = parseInt(system.args[3], 10);
pageHeight = parseInt(pageWidth * 3 / 4, 10);
console.log("pageHeight:", pageHeight);
page.viewportSize = {
width: pageWidth,
height: pageHeight
};
}
}
if (system.args.length > 4) {
page.zoomFactor = system.args[4];
}
page.open(address, function(status) {
if (status !== 'success') {
console.log('Unable to load the address!');
phantom.exit(1);
} else {
return window.setTimeout(function() {
page.render(output);
phantom.exit();
}, 200);
}
});
}
}).call(this);

32
dist/hmc/dist/utils/safe-json-loader.js vendored Normal file
View File

@ -0,0 +1,32 @@
/**
Definition of the SafeJsonLoader class.
@module utils/safe-json-loader
@license MIT. See LICENSE.md for details.
*/
(function() {
var FS, SyntaxErrorEx;
FS = require('fs');
SyntaxErrorEx = require('./syntax-error-ex');
module.exports = function(file) {
var ret, retRaw;
ret = {};
try {
ret.raw = FS.readFileSync(file, 'utf8');
ret.json = JSON.parse(ret.raw);
} catch (_error) {
retRaw = ret.raw && ret.raw.trim();
ret.ex = {
operation: retRaw ? 'parse' : 'read',
inner: SyntaxErrorEx.is(_error) ? new SyntaxErrorEx(_error, retRaw) : _error,
file: file
};
}
return ret;
};
}).call(this);

34
dist/hmc/dist/utils/safe-spawn.js vendored Normal file
View File

@ -0,0 +1,34 @@
/**
Safe spawn utility for HackMyResume / FluentCV.
@module utils/safe-spawn
@license MIT. See LICENSE.md for details.
*/
(function() {
module.exports = function(cmd, args, isSync) {
var info, spawn;
try {
spawn = require('child_process')[isSync ? 'spawnSync' : 'spawn'];
info = spawn(cmd, args);
if (!isSync) {
return info.on('error', function(err) {
throw {
cmd: 'wkhtmltopdf',
inner: err
};
});
} else {
if (info.error) {
throw {
cmd: 'wkhtmltopdf',
inner: info.error
};
}
}
} catch (_error) {
throw _error;
}
};
}).call(this);

View File

@ -0,0 +1,62 @@
/**
Object string transformation.
@module utils/string-transformer
@license MIT. See LICENSE.md for details.
*/
(function() {
var _, moment;
_ = require('underscore');
moment = require('moment');
/**
Create a copy of this object in which all string fields have been run through
a transformation function (such as a Markdown filter or XML encoder).
*/
module.exports = function(ret, filt, transformer) {
var that, transformStringsInObject;
that = this;
transformStringsInObject = function(obj, filters) {
if (!obj) {
return;
}
if (moment.isMoment(obj)) {
return;
}
if (_.isArray(obj)) {
return obj.forEach(function(elem, idx, ar) {
if (typeof elem === 'string' || elem instanceof String) {
return ar[idx] = transformer(null, elem);
} else if (_.isObject(elem)) {
return transformStringsInObject(elem, filters);
}
});
} else if (_.isObject(obj)) {
return Object.keys(obj).forEach(function(k) {
var sub;
if (filters.length && _.contains(filters, k)) {
return;
}
sub = obj[k];
if (typeof sub === 'string' || sub instanceof String) {
return obj[k] = transformer(k, sub);
} else if (_.isObject(sub)) {
return transformStringsInObject(sub, filters);
}
});
}
};
Object.keys(ret).forEach(function(member) {
if (!filt || !filt.length || !_.contains(filt, member)) {
return transformStringsInObject(ret[member], filt || []);
}
});
return ret;
};
}).call(this);

27
dist/hmc/dist/utils/string.js vendored Normal file
View File

@ -0,0 +1,27 @@
/**
Definitions of string utility functions.
@module utils/string
*/
/**
Determine if the string is null, empty, or whitespace.
See: http://stackoverflow.com/a/32800728/4942583
@method isNullOrWhitespace
*/
(function() {
String.isNullOrWhitespace = function(input) {
return !input || !input.trim();
};
String.prototype.endsWith = function(suffix) {
return this.indexOf(suffix, this.length - suffix.length) !== -1;
};
String.is = function(val) {
return typeof val === 'string' || val instanceof String;
};
}).call(this);

39
dist/hmc/dist/utils/syntax-error-ex.js vendored Normal file
View File

@ -0,0 +1,39 @@
/**
Definition of the SyntaxErrorEx class.
@module utils/syntax-error-ex
@license MIT. See LICENSE.md for details.
*/
/**
Represents a SyntaxError exception with line and column info.
Collect syntax error information from the provided exception object. The
JavaScript `SyntaxError` exception isn't interpreted uniformly across environ-
ments, so we reparse on error to grab the line and column.
See: http://stackoverflow.com/q/13323356
@class SyntaxErrorEx
*/
(function() {
var SyntaxErrorEx;
SyntaxErrorEx = function(ex, rawData) {
var JSONLint, colNum, lineNum, lint;
lineNum = null;
colNum = null;
JSONLint = require('json-lint');
lint = JSONLint(rawData, {
comments: false
});
this.line = lint.error ? lint.line : '???';
return this.col = lint.error ? lint.character : '???';
};
SyntaxErrorEx.is = function(ex) {
return ex instanceof SyntaxError;
};
module.exports = SyntaxErrorEx;
}).call(this);

102
dist/hmc/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/hmc/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/hmc/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/hmc/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);

79
dist/hmc/dist/verbs/peek.js vendored Normal file
View File

@ -0,0 +1,79 @@
/**
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);

110
dist/hmc/dist/verbs/validate.js vendored Normal file
View File

@ -0,0 +1,110 @@
/**
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, 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');
/** An invokable resume validation command. */
ValidateVerb = module.exports = Verb.extend({
init: function() {
return this._super('validate');
},
invoke: function() {
this.stat(HMEVENT.begin, {
cmd: 'validate'
});
validate.apply(this, arguments);
this.stat(HMEVENT.end);
}
});
/** Validate 1 to N resumes in FRESH or JSON Resume format. */
validate = function(sources, unused, opts) {
var resumes, 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')
};
resumes = ResumeFactory.load(sources, {
format: null,
objectify: false
}, this);
return resumes.map(function(src) {
var errors, exc, fmt, json, ret;
ret = {
file: src,
isValid: false
};
if (src.fluenterror) {
if (opts.assert) {
throw src;
}
this.setError(src.fluenterror, src);
return ret;
}
json = src.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) {
exc = _error;
return ret;
}
this.stat(HMEVENT.afterValidate, {
file: src.file,
isValid: ret.isValid,
fmt: fmt.replace('jars', 'JSON Resume'),
errors: errors
});
if (opts.assert && !ret.isValid) {
throw {
fluenterror: HMSTATUS.invalid({
shouldExit: true
})
};
}
return ret;
}, this);
};
}).call(this);

69
dist/hmc/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);

92
dist/hmc/package.json vendored Normal file
View File

@ -0,0 +1,92 @@
{
"name": "hackmycore",
"version": "0.4.0",
"description": "The open core library for HackMyResume and FluentCV.",
"repository": {
"type": "git",
"url": "https://github.com/hacksalot/HackMyCore.git"
},
"scripts": {
"test": "grunt clean:test && mocha",
"grunt": "grunt"
},
"author": "hacksalot <hacksalot@indevious.com> (https://github.com/hacksalot)",
"contributors": [],
"license": "MIT",
"bugs": {
"url": "https://github.com/hacksalot/HackMyCore/issues"
},
"main": "dist/index.js",
"homepage": "https://github.com/hacksalot/HackMyCore",
"dependencies": {
"copy": "^0.1.3",
"extend": "^3.0.0",
"fresca": "~0.6.0",
"fresh-jrs-converter": "^0.2.0",
"fresh-resume-starter": "^0.2.2",
"fresh-themes": "~0.14.0-beta",
"fs-extra": "^0.24.0",
"handlebars": "^4.0.5",
"html": "0.0.10",
"is-my-json-valid": "^2.12.2",
"json-lint": "^0.1.0",
"jst": "0.0.13",
"lodash": "^3.10.1",
"marked": "^0.3.5",
"mkdirp": "^0.5.1",
"moment": "^2.10.6",
"parse-filepath": "^0.6.3",
"path-exists": "^2.1.0",
"printf": "^0.2.3",
"recursive-readdir-sync": "^1.0.6",
"simple-html-tokenizer": "^0.2.0",
"slash": "^1.0.0",
"string-padding": "^1.0.2",
"string.prototype.endswith": "^0.2.0",
"string.prototype.startswith": "^0.2.0",
"traverse": "^0.6.6",
"underscore": "^1.8.3",
"word-wrap": "^1.1.0",
"xml-escape": "^1.0.0",
"yamljs": "^0.2.4"
},
"devDependencies": {
"chai": "*",
"chalk": "^1.1.1",
"fresh-test-resumes": "^0.6.0",
"grunt": "*",
"grunt-cli": "^0.1.13",
"grunt-contrib-clean": "^0.7.0",
"grunt-contrib-coffee": "^0.13.0",
"grunt-contrib-copy": "^0.8.2",
"grunt-contrib-jshint": "^0.11.3",
"grunt-contrib-yuidoc": "^0.10.0",
"grunt-jsdoc": "^1.1.0",
"grunt-simple-mocha": "*",
"jsonresume-theme-boilerplate": "^0.1.2",
"jsonresume-theme-classy": "^1.0.9",
"jsonresume-theme-modern": "0.0.18",
"jsonresume-theme-sceptile": "^1.0.5",
"mocha": "*",
"resample": "fluentdesk/resample"
},
"keywords": [
"resume",
"CV",
"portfolio",
"employment",
"career",
"HackMyResume",
"Markdown",
"JSON",
"Word",
"PDF",
"YAML",
"HTML",
"LaTeX",
"CLI",
"Handlebars",
"Underscore",
"template"
]
}

View File

@ -52,14 +52,31 @@
"copy": "^0.1.3",
"extend": "^3.0.0",
"fresca": "~0.6.0",
"hackmycore": "^0.4.0",
"fresh-jrs-converter": "^0.2.0",
"fresh-resume-starter": "^0.2.2",
"fresh-themes": "^0.14.0-beta",
"fs-extra": "^0.26.4",
"handlebars": "^4.0.5",
"html": "0.0.10",
"is-my-json-valid": "^2.12.4",
"json-lint": "^0.1.0",
"lodash": "^3.10.1",
"marked": "^0.3.5",
"mkdirp": "^0.5.1",
"moment": "^2.11.1",
"parse-filepath": "^0.6.3",
"path-exists": "^2.1.0",
"printf": "^0.2.3",
"recursive-readdir-sync": "^1.0.6",
"simple-html-tokenizer": "^0.2.1",
"slash": "^1.0.0",
"string-padding": "^1.0.2",
"string.prototype.endswith": "^0.2.0",
"string.prototype.startswith": "^0.2.0",
"traverse": "^0.6.6",
"underscore": "^1.8.3",
"word-wrap": "^1.1.0",
"xml-escape": "^1.0.0",
"yamljs": "^0.2.4"
},
"devDependencies": {

View File

@ -6,18 +6,18 @@ Error-handling routines for HackMyResume.
HMSTATUS = require('hackmycore/dist/core/status-codes')
HMSTATUS = require('../hmc/dist/core/status-codes')
PKG = require('../../package.json')
FS = require('fs')
FCMD = require('hackmycore')
FCMD = require('../hmc')
PATH = require('path')
WRAP = require('word-wrap')
M2C = require('hackmycore/dist/utils/md2chalk.js')
M2C = require('../hmc/dist/utils/md2chalk.js')
chalk = require('chalk')
extend = require('extend')
YAML = require('yamljs')
printf = require('printf')
SyntaxErrorEx = require('hackmycore/dist/utils/syntax-error-ex')
SyntaxErrorEx = require('../hmc/dist/utils/syntax-error-ex')
require('string.prototype.startswith')

View File

@ -6,16 +6,16 @@ Definition of the `main` function.
HMR = require 'hackmycore'
HMR = require '../hmc'
PKG = require '../../package.json'
FS = require 'fs'
EXTEND = require 'extend'
chalk = require 'chalk'
PATH = require 'path'
HMSTATUS = require 'hackmycore/dist/core/status-codes'
HME = require 'hackmycore/dist/core/event-codes'
safeLoadJSON = require 'hackmycore/dist/utils/safe-json-loader'
StringUtils = require 'hackmycore/dist/utils/string.js'
HMSTATUS = require '../hmc/dist/core/status-codes'
HME = require '../hmc/dist/core/event-codes'
safeLoadJSON = require '../hmc/dist/utils/safe-json-loader'
StringUtils = require '../hmc/dist/utils/string.js'
_ = require 'underscore'
OUTPUT = require './out'
PAD = require 'string-padding'

View File

@ -7,10 +7,10 @@ Output routines for HackMyResume.
chalk = require('chalk')
HME = require('hackmycore/dist/core/event-codes')
HME = require('../hmc/dist/core/event-codes')
_ = require('underscore')
Class = require('hackmycore/dist/utils/class.js')
M2C = require('hackmycore/dist/utils/md2chalk.js')
Class = require('../hmc/dist/utils/class.js')
M2C = require('../hmc/dist/utils/md2chalk.js')
PATH = require('path')
LO = require('lodash')
FS = require('fs')
@ -28,6 +28,7 @@ OutputHandler = module.exports = Class.extend
init: ( opts ) ->
@opts = EXTEND( true, this.opts || { }, opts )
@msgs = YAML.load(PATH.join( __dirname, 'msg.yml' )).events
return
@ -108,7 +109,7 @@ OutputHandler = module.exports = Class.extend
when HME.afterAnalyze
info = evt.info
rawTpl = FS.readFileSync( PATH.join( __dirname, 'analyze.hbs' ), 'utf8')
HANDLEBARS.registerHelper( require('hackmycore/dist/helpers/console-helpers') )
HANDLEBARS.registerHelper( require('../hmc/dist/helpers/console-helpers') )
template = HANDLEBARS.compile(rawTpl, { strict: false, assumeObjects: false })
tot = 0
info.keywords.forEach (g) -> tot += g.count