1
0
mirror of https://github.com/JuanCanham/HackMyResume.git synced 2025-05-15 10:07:07 +01:00

Compare commits

...

14 Commits

Author SHA1 Message Date
bd278268f6 Merge branch 'master' of https://github.com/hacksalot/HackMyResume 2016-01-31 12:21:44 -05:00
abe31e30e0 Update license year range to 2016 2016-01-31 12:21:29 -05:00
314d8d8763 Introduce build instructions. 2016-01-31 12:17:17 -05:00
ed0792e8f8 Fix YML/JSON/PNG invalid output format warning.
Fixes #97 but we still need to support standalone PNG (ie, a PNG not
generated as part of a .all output target).
2016-01-31 09:41:00 -05:00
90765bf90b Refactor verb invocations to base. 2016-01-31 08:37:12 -05:00
f1ba7765ee Include date tests. 2016-01-30 20:20:32 -05:00
27c7a0264a Improve date handling. 2016-01-30 20:06:04 -05:00
8e806dc04f Improve duration calcs, intro base resume class. 2016-01-30 16:40:22 -05:00
8ec6b5ed6a Bump version to 1.7.4. 2016-01-30 12:08:02 -05:00
4ef4ec5d42 Remove Node. 4.5.
Travis support 4.1 and 5.0 but not 4.5.
2016-01-30 11:49:27 -05:00
2f523b845b Travis: Add Node 4.5. 2016-01-30 11:40:36 -05:00
1c416f39d3 Fix JSON Resume theme breakage.
Fixes #128.
2016-01-30 11:31:39 -05:00
1de0eff7b3 Merge pull request #114 from pra85/patch-1
Update license year range to 2016
2016-01-29 22:32:45 -05:00
f8a39b0908 Update license year range to 2016 2016-01-30 07:41:15 +05:30
32 changed files with 499 additions and 373 deletions

58
BUILDING.md Normal file
View File

@ -0,0 +1,58 @@
Building
========
*See [CONTRIBUTING.md][contrib] for more information on contributing to the
HackMyResume or FluentCV projects.*
HackMyResume is a standard Node.js command line app implemented in a mix of
CoffeeScript and JavaScript. Setting up a build environment is easy:
## Prerequisites ##
1. OS: Linux, OS X, or Windows
2. Install [Node.js][node] and [Grunt][grunt].
## Set up a build environment ###
1. Fork [hacksalot/HackMyResume][hmr] to your GitHub account.
2. Clone your fork locally.
3. From within the top-level HackMyResume folder, run `npm install` to install
project dependencies.
4. Create a new branch, based on the latest HackMyResume `dev` branch, to
contain your work.
5. Run `npm link` in the HackMyResume folder so that the `hackmyresume` command
will reference your local installation (you may need to
`npm uninstall -g hackmyresume` first).
## Making changes
1. HackMyResume sources live in the [`/src`][src] folder. Always make your edits
there, never in the generated `/dist` folder.
2. After making your changes, run `grunt build` to package the HackMyResume
sources to the `/dist` folder. This will transform CoffeeScript files to
JavaScript and perform other build steps as necessary. In the future, a watch
task or guardfile will be added to automate this step.
3. Do local spot testing with `hackmyresume` as normal.
4. When you're ready to submit your changes, run `grunt test` to run the HMR
test suite. Fix any errors that occur.
5. Commit and push your changes.
6. Submit a pull request targeting the HackMyResume `dev` branch.
[node]: https://nodejs.org/en/
[grunt]: http://gruntjs.com/
[hmr]: https://github.com/hacksalot/HackMyResume
[src]: https://github.com/hacksalot/HackMyResume/tree/master/src
[contrib]: https://github.com/hacksalot/HackMyResume/blob/master/CONTRIBUTING.md

View File

@ -4,17 +4,11 @@ Contributing
*Note: HackMyResume is also available as [FluentCV][fcv]. Contributors are *Note: HackMyResume is also available as [FluentCV][fcv]. Contributors are
credited in both.* credited in both.*
HackMyResume needs your help! Our contribution workflow is based on [GitHub
Flow][flow] and we respond to all pull requests and issues, usually within 24
hours. HackMyResume has no corporate affiliation and no commercial basis, which
allows the project to maintain a strict user-first policy, rapid development
velocity, and a liberal stance on contributions and exotic functionality in
keeping with the spirit (and name) of the tool.
In short, your code is welcome here.
## How To Contribute ## How To Contribute
*See [BUILDING.md][building] for instructions on setting up a HackMyResume
development environment.*
1. Optional: [**open an issue**][iss] identifying the feature or bug you'd like 1. Optional: [**open an issue**][iss] identifying the feature or bug you'd like
to implement or fix. This step isn't required — you can start hacking away on to implement or fix. This step isn't required — you can start hacking away on
HackMyResume without clearing it with us — but helps avoid duplication of work HackMyResume without clearing it with us — but helps avoid duplication of work
@ -25,7 +19,7 @@ similar; call it whatever you like) to perform your work in.
4. **Install dependencies** by running `npm install` in the top-level 4. **Install dependencies** by running `npm install` in the top-level
HackMyResume folder. HackMyResume folder.
5. Make your **commits** as usual. 5. Make your **commits** as usual.
6. **Verify** your changes locally with `npm test`. 6. **Verify** your changes locally with `grunt test`.
7. **Push** your commits. 7. **Push** your commits.
7. **Submit a pull request** from your feature branch to the HackMyResume `dev` 7. **Submit a pull request** from your feature branch to the HackMyResume `dev`
branch. branch.
@ -48,7 +42,7 @@ You can reach hacksalot directly at:
hacksalot@indevious.com hacksalot@indevious.com
``` ```
Thanks! See you out there in the trenches. Thanks for your interest in the HackMyResume project.
[fcv]: https://github.com/fluentdesk/fluentcv [fcv]: https://github.com/fluentdesk/fluentcv
[flow]: https://guides.github.com/introduction/flow/ [flow]: https://guides.github.com/introduction/flow/
@ -56,3 +50,4 @@ Thanks! See you out there in the trenches.
[ha]: https://github.com/hacksalot [ha]: https://github.com/hacksalot
[th]: https://github.com/tomheon [th]: https://github.com/tomheon
[awesome]: https://github.com/hacksalot/HackMyResume/graphs/contributors [awesome]: https://github.com/hacksalot/HackMyResume/graphs/contributors
[building]: https://github.com/hacksalot/HackMyResume/blob/master/BUILDING.md

View File

@ -1,7 +1,7 @@
The MIT License The MIT License
=============== ===============
Copyright (c) 2016 hacksalot (https://github.com/hacksalot) Copyright (c) 2015-2016 hacksalot (https://github.com/hacksalot)
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

71
dist/core/abstract-resume.js vendored Normal file
View File

@ -0,0 +1,71 @@
/**
Definition of the AbstractResume class.
@license MIT. See LICENSE.md for details.
@module core/abstract-resume
*/
(function() {
var AbstractResume, FluentDate, _, __;
_ = require('underscore');
__ = require('lodash');
FluentDate = require('./fluent-date');
AbstractResume = (function() {
function AbstractResume() {}
/**
Compute the total duration of the work history.
@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.
*/
AbstractResume.prototype.duration = function(collKey, startKey, endKey, unit) {
var firstDate, hist, lastDate, new_e;
unit = unit || 'years';
hist = __.get(this, collKey);
if (!hist || !hist.length) {
return 0;
}
new_e = hist.map(function(job) {
var obj;
obj = _.pick(job, [startKey, endKey]);
if (!_.has(obj, endKey)) {
obj[endKey] = 'current';
}
if (obj && (obj[startKey] || obj[endKey])) {
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 0;
}
new_e = _.sortBy(new_e, function(elem) {
return elem[1].unix();
});
firstDate = _.first(new_e)[1];
lastDate = _.last(new_e)[1];
return lastDate.diff(firstDate, unit);
};
return AbstractResume;
})();
module.exports = AbstractResume;
}).call(this);

View File

@ -10,6 +10,8 @@ The HackMyResume date representation.
moment = require('moment'); moment = require('moment');
require('../utils/string');
/** /**
Create a FluentDate from a string or Moment date object. There are a few date Create a FluentDate from a string or Moment date object. There are a few date
@ -33,6 +35,10 @@ The HackMyResume date representation.
this.rep = this.fmt(dt); this.rep = this.fmt(dt);
} }
FluentDate.isCurrent = function(dt) {
return !dt || (String.is(dt) && /^(present|now|current)$/.test(dt));
};
return FluentDate; return FluentDate;
})(); })();
@ -54,7 +60,7 @@ The HackMyResume date representation.
module.exports = FluentDate; module.exports = FluentDate;
FluentDate.fmt = function(dt, throws) { FluentDate.fmt = function(dt, throws) {
var defTime, month, mt, parts, ref, temp; var month, mt, parts, ref, temp;
throws = (throws === void 0 || throws === null) || throws; throws = (throws === void 0 || throws === null) || throws;
if (typeof dt === 'string' || dt instanceof String) { if (typeof dt === 'string' || dt instanceof String) {
dt = dt.toLowerCase().trim(); dt = dt.toLowerCase().trim();
@ -72,33 +78,7 @@ The HackMyResume date representation.
} else if (/^\s*\d{4}\s*$/.test(dt)) { } else if (/^\s*\d{4}\s*$/.test(dt)) {
return moment(dt, 'YYYY'); return moment(dt, 'YYYY');
} else if (/^\s*$/.test(dt)) { } else if (/^\s*$/.test(dt)) {
defTime = { return moment();
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 { } else {
mt = moment(dt); mt = moment(dt);
if (mt.isValid()) { if (mt.isValid()) {

View File

@ -6,7 +6,9 @@ Definition of the FRESHResume class.
*/ */
(function() { (function() {
var CONVERTER, FS, FreshResume, JRSResume, MD, PATH, XML, _, __, _parseDates, extend, moment, validator; var AbstractResume, CONVERTER, FS, FluentDate, FreshResume, JRSResume, MD, PATH, XML, _, __, _parseDates, extend, moment, validator,
extend1 = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
hasProp = {}.hasOwnProperty;
FS = require('fs'); FS = require('fs');
@ -30,6 +32,10 @@ Definition of the FRESHResume class.
JRSResume = require('./jrs-resume'); JRSResume = require('./jrs-resume');
FluentDate = require('./fluent-date');
AbstractResume = require('./abstract-resume');
/** /**
A FRESH resume or CV. FRESH resumes are backed by JSON, and each FreshResume A FRESH resume or CV. FRESH resumes are backed by JSON, and each FreshResume
@ -37,8 +43,12 @@ Definition of the FRESHResume class.
@constructor @constructor
*/ */
FreshResume = (function() { FreshResume = (function(superClass) {
function FreshResume() {} extend1(FreshResume, superClass);
function FreshResume() {
return FreshResume.__super__.constructor.apply(this, arguments);
}
/** Initialize the FreshResume from file. */ /** Initialize the FreshResume from file. */
@ -376,36 +386,8 @@ Definition of the FRESHResume class.
return ret; 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) { FreshResume.prototype.duration = function(unit) {
var careerLast, careerStart, empHist, firstJob; return FreshResume.__super__.duration.call(this, 'employment.history', 'start', 'end', unit);
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;
}; };
@ -420,7 +402,11 @@ Definition of the FRESHResume class.
if (a.safe.start.isBefore(b.safe.start)) { if (a.safe.start.isBefore(b.safe.start)) {
return 1; return 1;
} else { } else {
return (a.safe.start.isAfter(b.safe.start) && -1) || 0; if (a.safe.start.isAfter(b.safe.start)) {
return -1;
} else {
return 0;
}
} }
}; };
sortSection = function(key) { sortSection = function(key) {
@ -448,7 +434,7 @@ Definition of the FRESHResume class.
return FreshResume; return FreshResume;
})(); })(AbstractResume);
/** /**
@ -499,7 +485,7 @@ Definition of the FRESHResume class.
return; return;
} }
if (Object.prototype.toString.call(obj) === '[object Array]') { if (Object.prototype.toString.call(obj) === '[object Array]') {
return obj.forEach(function(elem) { obj.forEach(function(elem) {
return replaceDatesInObject(elem); return replaceDatesInObject(elem);
}); });
} else if (typeof obj === 'object') { } else if (typeof obj === 'object') {
@ -509,19 +495,19 @@ Definition of the FRESHResume class.
Object.keys(obj).forEach(function(key) { Object.keys(obj).forEach(function(key) {
return replaceDatesInObject(obj[key]); return replaceDatesInObject(obj[key]);
}); });
return ['start', 'end', 'date'].forEach(function(val) { ['start', 'end', 'date'].forEach(function(val) {
if ((obj[val] !== void 0) && (!obj.safe || !obj.safe[val])) { if ((obj[val] !== void 0) && (!obj.safe || !obj.safe[val])) {
obj.safe = obj.safe || {}; obj.safe = obj.safe || {};
obj.safe[val] = _fmt(obj[val]); obj.safe[val] = _fmt(obj[val]);
if (obj[val] && (val === 'start') && !obj.end) { if (obj[val] && (val === 'start') && !obj.end) {
return obj.safe.end = _fmt('current'); obj.safe.end = _fmt('current');
} }
} }
}); });
} }
}; };
return Object.keys(this).forEach(function(member) { Object.keys(this).forEach(function(member) {
return replaceDatesInObject(that[member]); replaceDatesInObject(that[member]);
}); });
}; };

View File

@ -6,7 +6,9 @@ Definition of the JRSResume class.
*/ */
(function() { (function() {
var CONVERTER, FS, JRSResume, MD, PATH, _, _parseDates, extend, moment, validator; var AbstractResume, CONVERTER, FS, JRSResume, MD, PATH, _, _parseDates, extend, moment, validator,
extend1 = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
hasProp = {}.hasOwnProperty;
FS = require('fs'); FS = require('fs');
@ -24,6 +26,8 @@ Definition of the JRSResume class.
moment = require('moment'); moment = require('moment');
AbstractResume = require('./abstract-resume');
/** /**
A JRS resume or CV. JRS resumes are backed by JSON, and each JRSResume object A JRS resume or CV. JRS resumes are backed by JSON, and each JRSResume object
@ -31,10 +35,14 @@ Definition of the JRSResume class.
@class JRSResume @class JRSResume
*/ */
JRSResume = (function() { JRSResume = (function(superClass) {
var clear, format; var clear, format;
function JRSResume() {} extend1(JRSResume, superClass);
function JRSResume() {
return JRSResume.__super__.constructor.apply(this, arguments);
}
/** Initialize the JSResume from file. */ /** Initialize the JSResume from file. */
@ -249,30 +257,8 @@ Definition of the JRSResume class.
return ret; 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) { JRSResume.prototype.duration = function(unit) {
var careerLast, careerStart; return JRSResume.__super__.duration.call(this, 'work', 'startDate', 'endDate', unit);
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;
}; };
@ -372,7 +358,7 @@ Definition of the JRSResume class.
return JRSResume; return JRSResume;
})(); })(AbstractResume);
/** Get the default (empty) sheet. */ /** Get the default (empty) sheet. */

View File

@ -6,7 +6,7 @@ Definition of the JRSTheme class.
*/ */
(function() { (function() {
var JRSTheme, PATH, _, getFormat, parsePath, pathExists; var JRSTheme, PATH, _, parsePath, pathExists;
_ = require('underscore'); _ = require('underscore');
@ -25,17 +25,13 @@ Definition of the JRSTheme class.
JRSTheme = (function() { JRSTheme = (function() {
function JRSTheme() {} function JRSTheme() {}
return JRSTheme;
})();
({
/** /**
Open and parse the specified theme. Open and parse the specified theme.
@method open @method open
*/ */
open: function(thFolder) {
JRSTheme.prototype.open = function(thFolder) {
var pathInfo, pkgJsonPath, thApi, thPkg; var pathInfo, pkgJsonPath, thApi, thPkg;
this.folder = thFolder; this.folder = thFolder;
pathInfo = parsePath(thFolder); pathInfo = parsePath(thFolder);
@ -78,25 +74,31 @@ Definition of the JRSTheme class.
}; };
} }
return this; return this;
}, };
/** /**
Determine if the theme supports the output format. Determine if the theme supports the output format.
@method hasFormat @method hasFormat
*/ */
hasFormat: function(fmt) {
JRSTheme.prototype.hasFormat = function(fmt) {
return _.has(this.formats, fmt); return _.has(this.formats, fmt);
} };
/** /**
Return the requested output format. Return the requested output format.
@method getFormat @method getFormat
*/ */
});
getFormat = function(fmt) { JRSTheme.prototype.getFormat = function(fmt) {
return this.formats[fmt]; return this.formats[fmt];
}; };
return JRSTheme;
})();
module.exports = JRSTheme; module.exports = JRSTheme;

View File

@ -36,10 +36,11 @@ Definition of the JRSGenerator class.
generate: function(json, jst, format, cssInfo, opts, theme) { generate: function(json, jst, format, cssInfo, opts, theme) {
var org, rezHtml, turnoff; var org, rezHtml, turnoff;
turnoff = ['log', 'error', 'dir']; turnoff = ['log', 'error', 'dir'];
org = turnoff.map(c)(function() { org = turnoff.map(function(c) {
var ret; var ret;
ret = console[c]; ret = console[c];
return console[c] = function() {}; console[c] = function() {};
return ret;
}); });
rezHtml = theme.render(json.harden()); rezHtml = theme.render(json.harden());
turnoff.forEach(function(c, idx) { turnoff.forEach(function(c, idx) {

View File

@ -26,14 +26,7 @@ Implementation of the 'analyze' verb for HackMyResume.
AnalyzeVerb = module.exports = Verb.extend({ AnalyzeVerb = module.exports = Verb.extend({
init: function() { init: function() {
return this._super('analyze'); return this._super('analyze', analyze);
},
invoke: function() {
this.stat(HMEVENT.begin, {
cmd: 'analyze'
});
analyze.apply(this, arguments);
return this.stat(HMEVENT.end);
} }
}); });

41
dist/verbs/build.js vendored
View File

@ -74,18 +74,7 @@ Implementation of the 'build' verb for HackMyResume.
/** Create a new build verb. */ /** Create a new build verb. */
init: function() { init: function() {
return this._super('build'); return this._super('build', 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;
} }
}); });
@ -95,16 +84,16 @@ Implementation of the 'build' verb for HackMyResume.
theme file, generate 0..N resumes in the desired formats. theme file, generate 0..N resumes in the desired formats.
@param src Path to the source JSON resume file: "rez/resume.json". @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 dst An array of paths to the target resume file(s).
@param theme Friendly name of the resume theme. Defaults to "modern". @param opts Generation options.
@param logger Optional logging override.
*/ */
build = function(src, dst, opts) { build = function(src, dst, opts) {
var ex, inv, isFRESH, mixed, newEx, orgFormat, rez, sheetObjects, sheets, tFolder, targets, theme, toFormat; var ex, inv, isFRESH, mixed, newEx, orgFormat, problemSheets, rez, sheetObjects, sheets, tFolder, targets, theme, toFormat;
if (!src || !src.length) { if (!src || !src.length) {
this.err(HMSTATUS.resumeNotFound, { this.err(HMSTATUS.resumeNotFound, {
quit: true quit: true
}); });
return null;
} }
prep(src, dst, opts); prep(src, dst, opts);
sheetObjects = ResumeFactory.load(src, { sheetObjects = ResumeFactory.load(src, {
@ -115,9 +104,12 @@ Implementation of the 'build' verb for HackMyResume.
sort: _opts.sort sort: _opts.sort
} }
}, this); }, this);
if (!sheetObjects || _.some(sheetObjects, function(so) { problemSheets = _.filter(sheetObjects, function(so) {
return so.fluenterror; return so.fluenterror;
})) { });
if (problemSheets && problemSheets.length) {
problemSheets[0].quit = true;
this.err(problemSheets[0].fluenterror, problemSheets[0]);
return null; return null;
} }
sheets = sheetObjects.map(function(r) { sheets = sheetObjects.map(function(r) {
@ -130,12 +122,14 @@ Implementation of the 'build' verb for HackMyResume.
try { try {
tFolder = verifyTheme.call(this, _opts.theme); tFolder = verifyTheme.call(this, _opts.theme);
theme = _opts.themeObj = loadTheme(tFolder); theme = _opts.themeObj = loadTheme(tFolder);
addFreebieFormats(theme);
} catch (_error) { } catch (_error) {
ex = _error; ex = _error;
newEx = { newEx = {
fluenterror: HMSTATUS.themeLoad, fluenterror: HMSTATUS.themeLoad,
inner: ex, inner: ex,
attempted: _opts.theme attempted: _opts.theme,
quit: true
}; };
this.err(HMSTATUS.themeLoad, newEx); this.err(HMSTATUS.themeLoad, newEx);
return null; return null;
@ -147,8 +141,10 @@ Implementation of the 'build' verb for HackMyResume.
if (inv && inv.length) { if (inv && inv.length) {
this.err(HMSTATUS.invalidFormat, { this.err(HMSTATUS.invalidFormat, {
data: inv, data: inv,
theme: theme theme: theme,
quit: true
}); });
return null;
} }
rez = null; rez = null;
if (sheets.length > 1) { if (sheets.length > 1) {
@ -186,7 +182,6 @@ Implementation of the 'build' verb for HackMyResume.
fmt: toFormat fmt: toFormat
}); });
} }
addFreebieFormats(theme);
this.stat(HMEVENT.applyTheme, { this.stat(HMEVENT.applyTheme, {
r: rez, r: rez,
theme: theme theme: theme
@ -247,12 +242,12 @@ Implementation of the 'build' verb for HackMyResume.
fmt: targInfo.fmt.outFormat, fmt: targInfo.fmt.outFormat,
file: PATH.relative(process.cwd(), f) file: PATH.relative(process.cwd(), f)
}); });
_opts.targets = finished;
if (targInfo.fmt.files && targInfo.fmt.files.length) { if (targInfo.fmt.files && targInfo.fmt.files.length) {
theFormat = _fmts.filter(function(fmt) { theFormat = _fmts.filter(function(fmt) {
return fmt.name === targInfo.fmt.outFormat; return fmt.name === targInfo.fmt.outFormat;
})[0]; })[0];
MKDIRP.sync(PATH.dirname(f)); MKDIRP.sync(PATH.dirname(f));
_opts.targets = finished;
ret = theFormat.gen.generate(_rezObj, f, _opts); ret = theFormat.gen.generate(_rezObj, f, _opts);
} else { } else {
theFormat = _fmts.filter(function(fmt) { theFormat = _fmts.filter(function(fmt) {
@ -284,9 +279,7 @@ Implementation of the 'build' verb for HackMyResume.
}; };
/** /** Ensure that user-specified outputs/targets are valid. */
Ensure that user-specified outputs/targets are valid.
*/
verifyOutputs = function(targets, theme) { verifyOutputs = function(targets, theme) {
this.stat(HMEVENT.verifyOutputs, { this.stat(HMEVENT.verifyOutputs, {

View File

@ -22,14 +22,7 @@ Implementation of the 'convert' verb for HackMyResume.
ConvertVerb = module.exports = Verb.extend({ ConvertVerb = module.exports = Verb.extend({
init: function() { init: function() {
return this._super('convert'); return this._super('convert', convert);
},
invoke: function() {
this.stat(HMEVENT.begin, {
cmd: 'convert'
});
convert.apply(this, arguments);
return this.stat(HMEVENT.end);
} }
}); });

11
dist/verbs/create.js vendored
View File

@ -24,16 +24,7 @@ Implementation of the 'create' verb for HackMyResume.
CreateVerb = module.exports = Verb.extend({ CreateVerb = module.exports = Verb.extend({
init: function() { init: function() {
return this._super('new'); return this._super('new', create);
},
invoke: function() {
this.stat(HMEVENT.begin, {
cmd: 'create'
});
create.apply(this, arguments);
this.stat(HMEVENT.begin, {
cmd: 'convert'
});
} }
}); });

9
dist/verbs/peek.js vendored
View File

@ -22,14 +22,7 @@ Implementation of the 'peek' verb for HackMyResume.
PeekVerb = module.exports = Verb.extend({ PeekVerb = module.exports = Verb.extend({
init: function() { init: function() {
return this._super('peek'); return this._super('peek', peek);
},
invoke: function() {
this.stat(HMEVENT.begin, {
cmd: 'peek'
});
peek.apply(this, arguments);
return this.stat(HMEVENT.end);
} }
}); });

View File

@ -31,16 +31,7 @@ Implementation of the 'validate' verb for HackMyResume.
ValidateVerb = module.exports = Verb.extend({ ValidateVerb = module.exports = Verb.extend({
init: function() { init: function() {
return this._super('validate'); return this._super('validate', validate);
},
invoke: function() {
var ret;
this.stat(HMEVENT.begin, {
cmd: 'validate'
});
ret = validate.apply(this, arguments);
this.stat(HMEVENT.end);
return ret;
} }
}); });
@ -104,7 +95,6 @@ Implementation of the 'validate' verb for HackMyResume.
shouldExit: true shouldExit: true
}; };
} }
console.log('1111');
return ret; return ret;
}, this); }, this);
}; };

18
dist/verbs/verb.js vendored
View File

@ -6,12 +6,14 @@ Definition of the Verb class.
*/ */
(function() { (function() {
var Class, EVENTS, Verb; var Class, EVENTS, HMEVENT, Verb;
Class = require('../utils/class'); Class = require('../utils/class');
EVENTS = require('events'); EVENTS = require('events');
HMEVENT = require('../core/event-codes');
/** /**
An instantiation of a HackMyResume command. An instantiation of a HackMyResume command.
@ -21,9 +23,21 @@ Definition of the Verb class.
Verb = module.exports = Class.extend({ Verb = module.exports = Class.extend({
/** Constructor. Automatically called at creation. */ /** Constructor. Automatically called at creation. */
init: function(moniker) { init: function(moniker, workhorse) {
this.moniker = moniker; this.moniker = moniker;
this.emitter = new EVENTS.EventEmitter(); this.emitter = new EVENTS.EventEmitter();
this.workhorse = workhorse;
},
/** Invoke the command. */
invoke: function() {
var ret;
this.stat(HMEVENT.begin, {
cmd: this.moniker
});
ret = this.workhorse.apply(this, arguments);
this.stat(HMEVENT.end);
return ret;
}, },
/** Forward subscriptions to the event emitter. */ /** Forward subscriptions to the event emitter. */

View File

@ -1,6 +1,6 @@
{ {
"name": "hackmyresume", "name": "hackmyresume",
"version": "1.7.3", "version": "1.7.4",
"description": "Generate polished résumés and CVs in HTML, Markdown, LaTeX, MS Word, PDF, plain text, JSON, XML, YAML, smoke signal, and carrier pigeon.", "description": "Generate polished résumés and CVs in HTML, Markdown, LaTeX, MS Word, PDF, plain text, JSON, XML, YAML, smoke signal, and carrier pigeon.",
"repository": { "repository": {
"type": "git", "type": "git",

View File

@ -0,0 +1,53 @@
###*
Definition of the AbstractResume class.
@license MIT. See LICENSE.md for details.
@module core/abstract-resume
###
_ = require 'underscore'
__ = require 'lodash'
FluentDate = require('./fluent-date')
class AbstractResume
###*
Compute the total duration of the work history.
@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.
###
duration: (collKey, startKey, endKey, unit) ->
unit = unit || 'years'
hist = __.get @, collKey
return 0 if !hist or !hist.length
# BEGIN CODE DUPLICATION --> src/inspectors/gap-inspector.coffee (TODO)
# Convert the candidate's employment history to an array of dates,
# where each element in the array is a start date or an end date of a
# job -- it doesn't matter which.
new_e = hist.map ( job ) ->
obj = _.pick( job, [startKey, endKey] )
# Synthesize an end date if this is a "current" gig
obj[endKey] = 'current' if !_.has obj, endKey
if obj && (obj[startKey] || obj[endKey])
obj = _.pairs obj
obj[0][1] = FluentDate.fmt( obj[0][1] )
if obj.length > 1
obj[1][1] = FluentDate.fmt( obj[1][1] )
obj
# Flatten the array, remove empties, and sort
new_e = _.filter _.flatten( new_e, true ), (v) ->
return v && v.length && v[0] && v[0].length
return 0 if !new_e or !new_e.length
new_e = _.sortBy new_e, ( elem ) -> return elem[1].unix()
# END CODE DUPLICATION
firstDate = _.first( new_e )[1];
lastDate = _.last( new_e )[1];
lastDate.diff firstDate, unit
module.exports = AbstractResume

View File

@ -7,6 +7,7 @@ The HackMyResume date representation.
moment = require 'moment' moment = require 'moment'
require('../utils/string')
###* ###*
Create a FluentDate from a string or Moment date object. There are a few date Create a FluentDate from a string or Moment date object. There are a few date
@ -30,6 +31,8 @@ class FluentDate
constructor: (dt) -> constructor: (dt) ->
@rep = this.fmt dt @rep = this.fmt dt
@isCurrent: (dt) ->
!dt || (String.is(dt) and /^(present|now|current)$/.test(dt))
months = {} months = {}
abbr = {} abbr = {}
@ -56,16 +59,7 @@ FluentDate.fmt = ( dt, throws ) ->
else if /^\s*\d{4}\s*$/.test(dt) # "2015" else if /^\s*\d{4}\s*$/.test(dt) # "2015"
return moment dt, 'YYYY' return moment dt, 'YYYY'
else if /^\s*$/.test(dt) # "", " " else if /^\s*$/.test(dt) # "", " "
defTime = return moment()
isNull: true
isBefore: ( other ) ->
if other and !other.isNull then true else false
isAfter: ( other ) ->
if other and !other.isNull then false else false
unix: () -> 0
format: () -> ''
diff: () -> 0
return defTime
else else
mt = moment dt mt = moment dt
if mt.isValid() if mt.isValid()

View File

@ -17,6 +17,8 @@ XML = require 'xml-escape'
MD = require 'marked' MD = require 'marked'
CONVERTER = require 'fresh-jrs-converter' CONVERTER = require 'fresh-jrs-converter'
JRSResume = require './jrs-resume' JRSResume = require './jrs-resume'
FluentDate = require './fluent-date'
AbstractResume = require './abstract-resume'
@ -25,7 +27,7 @@ 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. object is an instantiation of that JSON decorated with utility methods.
@constructor @constructor
### ###
class FreshResume class FreshResume extends AbstractResume
###* Initialize the FreshResume from file. ### ###* Initialize the FreshResume from file. ###
open: ( file, opts ) -> open: ( file, opts ) ->
@ -306,28 +308,8 @@ class FreshResume
ret 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.
###
duration: (unit) -> duration: (unit) ->
unit = unit || 'years' super('employment.history', 'start', 'end', unit)
empHist = __.get(this, 'employment.history')
if empHist && empHist.length
firstJob = _.last( empHist )
careerStart = if firstJob.start then firstJob.safe.start else ''
if ((typeof careerStart == 'string' || careerStart instanceof String) && !careerStart.trim())
return 0
careerLast = _.max empHist, ( w ) ->
return if w.safe && w.safe.end then w.safe.end.unix() else moment().unix()
return careerLast.safe.end.diff careerStart, unit
0
###* ###*
@ -337,9 +319,9 @@ class FreshResume
sort: () -> sort: () ->
byDateDesc = (a,b) -> byDateDesc = (a,b) ->
if ( a.safe.start.isBefore(b.safe.start) ) if a.safe.start.isBefore(b.safe.start)
then 1 then 1
else ( a.safe.start.isAfter(b.safe.start) && -1 ) || 0 else ( if a.safe.start.isAfter(b.safe.start) then -1 else 0 )
sortSection = ( key ) -> sortSection = ( key ) ->
ar = __.get this, key ar = __.get this, key
@ -352,10 +334,6 @@ class FreshResume
sortSection 'service.history' sortSection 'service.history'
sortSection 'projects' sortSection 'projects'
# this.awards && this.awards.sort( function(a, b) {
# return( a.safeDate.isBefore(b.safeDate) ) ? 1
# : ( a.safeDate.isAfter(b.safeDate) && -1 ) || 0;
# });
@writing && @writing.sort (a, b) -> @writing && @writing.sort (a, b) ->
if a.safe.date.isBefore b.safe.date if a.safe.date.isBefore b.safe.date
then 1 then 1
@ -400,6 +378,7 @@ _parseDates = () ->
return if !obj return if !obj
if Object.prototype.toString.call( obj ) == '[object Array]' if Object.prototype.toString.call( obj ) == '[object Array]'
obj.forEach (elem) -> replaceDatesInObject( elem ) obj.forEach (elem) -> replaceDatesInObject( elem )
return
else if typeof obj == 'object' else if typeof obj == 'object'
if obj._isAMomentObject || obj.safe if obj._isAMomentObject || obj.safe
return return
@ -410,8 +389,12 @@ _parseDates = () ->
obj.safe[ val ] = _fmt obj[val] obj.safe[ val ] = _fmt obj[val]
if obj[val] && (val == 'start') && !obj.end if obj[val] && (val == 'start') && !obj.end
obj.safe.end = _fmt 'current' obj.safe.end = _fmt 'current'
return
Object.keys( this ).forEach (member) -> replaceDatesInObject(that[member]) return
Object.keys( this ).forEach (member) ->
replaceDatesInObject(that[member])
return
return

View File

@ -14,7 +14,7 @@ PATH = require('path')
MD = require('marked') MD = require('marked')
CONVERTER = require('fresh-jrs-converter') CONVERTER = require('fresh-jrs-converter')
moment = require('moment') moment = require('moment')
AbstractResume = require('./abstract-resume')
###* ###*
@ -22,7 +22,7 @@ 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. is an instantiation of that JSON decorated with utility methods.
@class JRSResume @class JRSResume
### ###
class JRSResume class JRSResume extends AbstractResume
@ -203,25 +203,8 @@ class JRSResume
ret ret
duration: (unit) ->
###* super('work', 'startDate', 'endDate', unit)
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.
###
duration: ( unit ) ->
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, ( w ) -> w.safeEndDate.unix() ).safeEndDate;
return careerLast.diff careerStart, unit
0
###* ###*

View File

@ -20,68 +20,69 @@ The JRSTheme class is a representation of a JSON Resume theme asset.
class JRSTheme class JRSTheme
###*
Open and parse the specified theme.
@method open
###
open: ( thFolder ) ->
@folder = thFolder ###*
Open and parse the specified theme.
@method open
###
open: ( thFolder ) ->
# Open the [theme-name].json file; should have the same @folder = thFolder
# name as folder
pathInfo = parsePath thFolder
# Open and parse the theme's package.json file. # Open the [theme-name].json file; should have the same
pkgJsonPath = PATH.join thFolder, 'package.json' # name as folder
if pathExists pkgJsonPath pathInfo = parsePath thFolder
thApi = require thFolder
thPkg = require pkgJsonPath
this.name = thPkg.name
this.render = (thApi && thApi.render) || undefined
this.engine = 'jrs'
# Create theme formats (HTML and PDF). Just add the bare minimum mix of # Open and parse the theme's package.json file.
# properties necessary to allow JSON Resume themes to share a rendering pkgJsonPath = PATH.join thFolder, 'package.json'
# path with FRESH themes. if pathExists pkgJsonPath
this.formats = thApi = require thFolder
html: thPkg = require pkgJsonPath
outFormat: 'html' this.name = thPkg.name
files: [{ this.render = (thApi && thApi.render) || undefined
action: 'transform', this.engine = 'jrs'
render: this.render,
major: true, # Create theme formats (HTML and PDF). Just add the bare minimum mix of
ext: 'html', # properties necessary to allow JSON Resume themes to share a rendering
css: null # path with FRESH themes.
}] this.formats =
pdf: html:
outFormat: 'pdf' outFormat: 'html'
files: [{ files: [{
action: 'transform', action: 'transform',
render: this.render, render: this.render,
major: true, major: true,
ext: 'pdf', ext: 'html',
css: null css: null
}] }]
else pdf:
throw { fluenterror: HACKMYSTATUS.missingPackageJSON }; outFormat: 'pdf'
@ files: [{
action: 'transform',
render: this.render,
major: true,
ext: 'pdf',
css: null
}]
else
throw { fluenterror: HACKMYSTATUS.missingPackageJSON };
@
###* ###*
Determine if the theme supports the output format. Determine if the theme supports the output format.
@method hasFormat @method hasFormat
### ###
hasFormat: ( fmt ) -> _.has this.formats, fmt hasFormat: ( fmt ) -> _.has this.formats, fmt
###* ###*
Return the requested output format. Return the requested output format.
@method getFormat @method getFormat
### ###
getFormat = ( fmt ) -> @formats[ fmt ] getFormat: ( fmt ) -> @formats[ fmt ]
module.exports = JRSTheme; module.exports = JRSTheme;

View File

@ -25,9 +25,10 @@ JRSGenerator = module.exports =
# Disable JRS theme chatter (console.log, console.error, etc.) # Disable JRS theme chatter (console.log, console.error, etc.)
turnoff = ['log', 'error', 'dir']; turnoff = ['log', 'error', 'dir'];
org = turnoff.map(c) -> org = turnoff.map (c) ->
ret = console[c] ret = console[c]
console[c] = () -> console[c] = () ->
ret
# Freeze and render # Freeze and render
rezHtml = theme.render json.harden() rezHtml = theme.render json.harden()

View File

@ -19,12 +19,7 @@ chalk = require('chalk')
AnalyzeVerb = module.exports = Verb.extend AnalyzeVerb = module.exports = Verb.extend
init: -> @._super 'analyze' init: -> @_super 'analyze', analyze
invoke: ->
@.stat HMEVENT.begin, { cmd: 'analyze' }
analyze.apply @, arguments
@.stat HMEVENT.end

View File

@ -42,14 +42,7 @@ loadTheme = null
BuildVerb = module.exports = Verb.extend BuildVerb = module.exports = Verb.extend
###* Create a new build verb. ### ###* Create a new build verb. ###
init: () -> @._super 'build' init: () -> @_super 'build', build
###* Invoke the Build command. ###
invoke: ->
@stat HMEVENT.begin, { cmd: 'build' }
ret = build.apply @, arguments
@stat HMEVENT.end
return ret
@ -58,13 +51,13 @@ 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. theme file, generate 0..N resumes in the desired formats.
@param src Path to the source JSON resume file: "rez/resume.json". @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 dst An array of paths to the target resume file(s).
@param theme Friendly name of the resume theme. Defaults to "modern". @param opts Generation options.
@param logger Optional logging override.
### ###
build = ( src, dst, opts ) -> build = ( src, dst, opts ) ->
if !src || !src.length if !src || !src.length
@err HMSTATUS.resumeNotFound, { quit: true } @err HMSTATUS.resumeNotFound, quit: true
return null
prep src, dst, opts prep src, dst, opts
@ -74,10 +67,14 @@ build = ( src, dst, opts ) ->
}, @); }, @);
# Explicit check for any resume loading errors... # Explicit check for any resume loading errors...
if !sheetObjects || _.some( sheetObjects, (so) -> return so.fluenterror ) problemSheets = _.filter( sheetObjects, (so) -> return so.fluenterror )
if( problemSheets && problemSheets.length )
problemSheets[0].quit = true
@err problemSheets[0].fluenterror, problemSheets[0]
return null return null
sheets = sheetObjects.map((r) -> return r.json ) # Get the collection of raw JSON sheets
sheets = sheetObjects.map (r) -> r.json
# Load the theme... # Load the theme...
theme = null theme = null
@ -85,12 +82,14 @@ build = ( src, dst, opts ) ->
try try
tFolder = verifyTheme.call @, _opts.theme tFolder = verifyTheme.call @, _opts.theme
theme = _opts.themeObj = loadTheme tFolder theme = _opts.themeObj = loadTheme tFolder
addFreebieFormats theme
catch ex catch ex
newEx = newEx =
fluenterror: HMSTATUS.themeLoad fluenterror: HMSTATUS.themeLoad
inner: ex inner: ex
attempted: _opts.theme attempted: _opts.theme
this.err HMSTATUS.themeLoad, newEx quit: true
@err HMSTATUS.themeLoad, newEx
return null return null
@stat HMEVENT.afterTheme, { theme: theme } @stat HMEVENT.afterTheme, { theme: theme }
@ -98,7 +97,8 @@ build = ( src, dst, opts ) ->
# Check for invalid outputs... # Check for invalid outputs...
inv = verifyOutputs.call @, dst, theme inv = verifyOutputs.call @, dst, theme
if inv && inv.length if inv && inv.length
@err HMSTATUS.invalidFormat, { data: inv, theme: theme } @err HMSTATUS.invalidFormat, data: inv, theme: theme, quit: true
return null
## Merge input resumes, yielding a single source resume. ## Merge input resumes, yielding a single source resume.
rez = null rez = null
@ -107,7 +107,7 @@ build = ( src, dst, opts ) ->
mixed = _.any sheets, (s) -> return if isFRESH then s.basics else !s.basics mixed = _.any sheets, (s) -> return if isFRESH then s.basics else !s.basics
@stat HMEVENT.beforeMerge, { f: _.clone(sheetObjects), mixed: mixed } @stat HMEVENT.beforeMerge, { f: _.clone(sheetObjects), mixed: mixed }
if mixed if mixed
this.err HMSTATUS.mixedMerge @err HMSTATUS.mixedMerge
rez = _.reduceRight sheets, ( a, b, idx ) -> rez = _.reduceRight sheets, ( a, b, idx ) ->
extend( true, b, a ) extend( true, b, a )
@ -124,8 +124,7 @@ build = ( src, dst, opts ) ->
rez = RConverter[ 'to' + toFormat ]( rez ); rez = RConverter[ 'to' + toFormat ]( rez );
@stat HMEVENT.afterInlineConvert, { file: sheetObjects[0].file, fmt: toFormat } @stat HMEVENT.afterInlineConvert, { file: sheetObjects[0].file, fmt: toFormat }
# Add freebie formats to the theme # Announce the theme
addFreebieFormats theme
@stat HMEVENT.applyTheme, { r: rez, theme: theme } @stat HMEVENT.applyTheme, { r: rez, theme: theme }
# Load the resume into a FRESHResume or JRSResume object # Load the resume into a FRESHResume or JRSResume object
@ -195,13 +194,14 @@ single = ( targInfo, theme, finished ) ->
fmt: targInfo.fmt.outFormat fmt: targInfo.fmt.outFormat
file: PATH.relative(process.cwd(), f) file: PATH.relative(process.cwd(), f)
_opts.targets = finished;
# If targInfo.fmt.files exists, this format is backed by a document. # If targInfo.fmt.files exists, this format is backed by a document.
# Fluent/FRESH themes are handled here. # Fluent/FRESH themes are handled here.
if targInfo.fmt.files && targInfo.fmt.files.length if targInfo.fmt.files && targInfo.fmt.files.length
theFormat = _fmts.filter( theFormat = _fmts.filter(
(fmt) -> return fmt.name == targInfo.fmt.outFormat )[0]; (fmt) -> return fmt.name == targInfo.fmt.outFormat )[0];
MKDIRP.sync( PATH.dirname( f ) ); # Ensure dest folder exists; MKDIRP.sync( PATH.dirname( f ) ); # Ensure dest folder exists;
_opts.targets = finished;
ret = theFormat.gen.generate( _rezObj, f, _opts ); ret = theFormat.gen.generate( _rezObj, f, _opts );
# Otherwise this is an ad-hoc format (JSON, YML, or PNG) that every theme # Otherwise this is an ad-hoc format (JSON, YML, or PNG) that every theme
@ -235,11 +235,9 @@ single = ( targInfo, theme, finished ) ->
###* ###* Ensure that user-specified outputs/targets are valid. ###
Ensure that user-specified outputs/targets are valid.
###
verifyOutputs = ( targets, theme ) -> verifyOutputs = ( targets, theme ) ->
@.stat HMEVENT.verifyOutputs, { targets: targets, theme: theme } @stat HMEVENT.verifyOutputs, { targets: targets, theme: theme }
_.reject targets.map( ( t ) -> _.reject targets.map( ( t ) ->
pathInfo = parsePath t pathInfo = parsePath t
{ {
@ -247,6 +245,8 @@ verifyOutputs = ( targets, theme ) ->
}), }),
(t) -> t.format == 'all' || theme.hasFormat( t.format ) (t) -> t.format == 'all' || theme.hasFormat( t.format )
###* ###*
Reinforce the chosen theme with "freebie" formats provided by HackMyResume. 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 A "freebie" format is an output format such as JSON, YML, or PNG that can be

View File

@ -17,12 +17,7 @@ HMEVENT = require('../core/event-codes');
ConvertVerb = module.exports = Verb.extend ConvertVerb = module.exports = Verb.extend
init: -> @._super 'convert' init: -> @_super 'convert', convert
invoke: ->
@.stat HMEVENT.begin, { cmd: 'convert' }
convert.apply @, arguments
@.stat HMEVENT.end

View File

@ -17,13 +17,8 @@ HMEVENT = require('../core/event-codes')
CreateVerb = module.exports = Verb.extend CreateVerb = module.exports = Verb.extend
init: -> @._super('new') init: -> @_super 'new', create
invoke: ->
@.stat HMEVENT.begin, { cmd: 'create' }
create.apply @, arguments
@.stat HMEVENT.begin, { cmd: 'convert' }
return
###* ###*

View File

@ -17,12 +17,7 @@ HMEVENT = require('../core/event-codes')
PeekVerb = module.exports = Verb.extend PeekVerb = module.exports = Verb.extend
init: -> @._super('peek') init: -> @_super 'peek', peek
invoke: ->
@.stat HMEVENT.begin, { cmd: 'peek' }
peek.apply @, arguments
@.stat HMEVENT.end

View File

@ -21,13 +21,7 @@ safeLoadJSON = require '../utils/safe-json-loader'
###* An invokable resume validation command. ### ###* An invokable resume validation command. ###
ValidateVerb = module.exports = Verb.extend ValidateVerb = module.exports = Verb.extend
init: -> @_super 'validate' init: -> @_super 'validate', validate
invoke: ->
@stat HMEVENT.begin, { cmd: 'validate' }
ret = validate.apply @, arguments
@stat HMEVENT.end
return ret
@ -85,8 +79,6 @@ validate = (sources, unused, opts) ->
if opts.assert and !ret.isValid if opts.assert and !ret.isValid
throw fluenterror: HMSTATUS.invalid, shouldExit: true throw fluenterror: HMSTATUS.invalid, shouldExit: true
console.log '1111' ret
return ret
, @ , @

View File

@ -6,10 +6,10 @@ Definition of the Verb class.
# Use J. Resig's nifty class implementation # Use J. Resig's nifty class implementation
Class = require '../utils/class' Class = require '../utils/class'
EVENTS = require 'events' EVENTS = require 'events'
HMEVENT = require '../core/event-codes'
@ -22,24 +22,34 @@ Verb = module.exports = Class.extend
###* Constructor. Automatically called at creation. ### ###* Constructor. Automatically called at creation. ###
init: ( moniker ) -> init: ( moniker, workhorse ) ->
@.moniker = moniker @moniker = moniker
@.emitter = new EVENTS.EventEmitter() @emitter = new EVENTS.EventEmitter()
@workhorse = workhorse
return return
###* Invoke the command. ###
invoke: ->
@stat HMEVENT.begin, cmd: @moniker
ret = @workhorse.apply @, arguments
@stat HMEVENT.end
ret
###* Forward subscriptions to the event emitter. ### ###* Forward subscriptions to the event emitter. ###
on: -> on: ->
this.emitter.on.apply @.emitter, arguments @emitter.on.apply @emitter, arguments
###* Fire an arbitrary event, scoped to "hmr:". ### ###* Fire an arbitrary event, scoped to "hmr:". ###
fire: (evtName, payload) -> fire: (evtName, payload) ->
payload = payload || { } payload = payload || { }
payload.cmd = this.moniker payload.cmd = @moniker
this.emitter.emit 'hmr:' + evtName, payload @emitter.emit 'hmr:' + evtName, payload
true true
@ -60,10 +70,11 @@ Verb = module.exports = Class.extend
stat: ( subEvent, payload ) -> stat: ( subEvent, payload ) ->
payload = payload || { } payload = payload || { }
payload.sub = subEvent payload.sub = subEvent
this.fire 'status', payload @fire 'status', payload
true true
###* Associate error info with the invocation. ### ###* Associate error info with the invocation. ###
setError: ( code, obj ) -> setError: ( code, obj ) ->
@errorCode = code @errorCode = code

View File

@ -9,3 +9,4 @@ require('./scripts/test-jrs-sheet');
require('./scripts/test-themes'); require('./scripts/test-themes');
require('./scripts/test-api'); require('./scripts/test-api');
require('./scripts/test-output'); require('./scripts/test-output');
require('./scripts/test-dates');

View File

@ -0,0 +1,81 @@
/**
@module test-dates.js
*/
var chai = require('chai')
, expect = chai.expect
, should = chai.should()
, path = require('path')
, _ = require('underscore')
, FRESHResume = require('../../dist/core/fresh-resume')
, FCMD = require( '../../dist/index')
, validator = require('is-my-json-valid')
, EXTEND = require('extend');
chai.config.includeStack = true;
var gig = {
employer: 'E1'
};
var r = {
name: 'John Doe',
meta: {
format: 'FRESH@0.6.0'
},
employment: {
history: [ null ]
}
};
var tests = [
// single job, concrete start, no end
[ { start: '2010-01-01' } , { val: 6, unit: 'year' } ],
[ { start: '2010-01' } , { val: 6, unit: 'year' } ],
[ { start: '2010' } , { val: 6, unit: 'year' } ],
// single job, concrete start, concrete end
[ { start: '2010-01-01', end: '2015-01-01' } , { val: 5, unit: 'year' } ],
[ { start: '2010-01', end: '2015-01' } , { val: 5, unit: 'year' } ],
[ { start: '2010', end: '2015' } , { val: 5, unit: 'year' } ],
// single job, falsy start, falsy end
[ { } , { val: 0, unit: 'year' } ],
[ { start: null } , { val: 0, unit: 'year' } ],
[ { end: null } , { val: 0, unit: 'year' } ],
[ { start: undefined } , { val: 0, unit: 'year' } ],
[ { end: undefined } , { val: 0, unit: 'year' } ],
[ { start: null, end: null } , { val: 0, unit: 'year' } ],
[ { start: '', end: '' } , { val: 0, unit: 'year' } ],
[ { start: ' ', end: ' ' } , { val: 0, unit: 'year' } ],
[ { start: undefined, end: undefined } , { val: 0, unit: 'year' } ],
// two jobs (concrete start + end) -> ( concrete start )
[ { start: '2000-01', end: '2013-01' }, { start: '2013-01' }, { val: 16, unit: 'year' } ],
[ { start: '2000-01', end: '2013-01' }, { start: '2013-01', end: '' }, { val: 16, unit: 'year' } ],
[ { start: '2000-01', end: '2013-01' }, { start: '2013-01', end: null }, { val: 16, unit: 'year' } ],
[ { start: '2000-01', end: '2013-01' }, { start: '2013-01', end: 'current' }, { val: 16, unit: 'year' } ]
];
tests.forEach(function(t){
_.initial( t ).forEach(function(t){ t.employer = 'E1' });
})
describe('Testing DATES', function () {
tests.forEach( function(t) {
it( JSON.stringify( _.initial(t) ), function () {
r.employment.history = _.initial( t );
var rObj = new FRESHResume();
rObj.parseJSON( r );
var dur = rObj.duration();
expect( dur ).to.equal( _.last(t).val );
});
});
});