mirror of
https://github.com/JuanCanham/HackMyResume.git
synced 2025-05-12 00:27:08 +01:00
Compare commits
14 Commits
Author | SHA1 | Date | |
---|---|---|---|
bd278268f6 | |||
abe31e30e0 | |||
314d8d8763 | |||
ed0792e8f8 | |||
90765bf90b | |||
f1ba7765ee | |||
27c7a0264a | |||
8e806dc04f | |||
8ec6b5ed6a | |||
4ef4ec5d42 | |||
2f523b845b | |||
1c416f39d3 | |||
1de0eff7b3 | |||
f8a39b0908 |
58
BUILDING.md
Normal file
58
BUILDING.md
Normal 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
|
@ -4,17 +4,11 @@ Contributing
|
||||
*Note: HackMyResume is also available as [FluentCV][fcv]. Contributors are
|
||||
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
|
||||
|
||||
*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
|
||||
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
|
||||
@ -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
|
||||
HackMyResume folder.
|
||||
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. **Submit a pull request** from your feature branch to the HackMyResume `dev`
|
||||
branch.
|
||||
@ -48,7 +42,7 @@ You can reach hacksalot directly at:
|
||||
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
|
||||
[flow]: https://guides.github.com/introduction/flow/
|
||||
@ -56,3 +50,4 @@ Thanks! See you out there in the trenches.
|
||||
[ha]: https://github.com/hacksalot
|
||||
[th]: https://github.com/tomheon
|
||||
[awesome]: https://github.com/hacksalot/HackMyResume/graphs/contributors
|
||||
[building]: https://github.com/hacksalot/HackMyResume/blob/master/BUILDING.md
|
||||
|
@ -1,7 +1,7 @@
|
||||
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
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
71
dist/core/abstract-resume.js
vendored
Normal file
71
dist/core/abstract-resume.js
vendored
Normal 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);
|
36
dist/core/fluent-date.js
vendored
36
dist/core/fluent-date.js
vendored
@ -10,6 +10,8 @@ The HackMyResume date representation.
|
||||
|
||||
moment = require('moment');
|
||||
|
||||
require('../utils/string');
|
||||
|
||||
|
||||
/**
|
||||
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);
|
||||
}
|
||||
|
||||
FluentDate.isCurrent = function(dt) {
|
||||
return !dt || (String.is(dt) && /^(present|now|current)$/.test(dt));
|
||||
};
|
||||
|
||||
return FluentDate;
|
||||
|
||||
})();
|
||||
@ -54,7 +60,7 @@ The HackMyResume date representation.
|
||||
module.exports = FluentDate;
|
||||
|
||||
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;
|
||||
if (typeof dt === 'string' || dt instanceof String) {
|
||||
dt = dt.toLowerCase().trim();
|
||||
@ -72,33 +78,7 @@ The HackMyResume date representation.
|
||||
} 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;
|
||||
return moment();
|
||||
} else {
|
||||
mt = moment(dt);
|
||||
if (mt.isValid()) {
|
||||
|
64
dist/core/fresh-resume.js
vendored
64
dist/core/fresh-resume.js
vendored
@ -6,7 +6,9 @@ Definition of the FRESHResume class.
|
||||
*/
|
||||
|
||||
(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');
|
||||
|
||||
@ -30,6 +32,10 @@ Definition of the FRESHResume class.
|
||||
|
||||
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
|
||||
@ -37,8 +43,12 @@ Definition of the FRESHResume class.
|
||||
@constructor
|
||||
*/
|
||||
|
||||
FreshResume = (function() {
|
||||
function FreshResume() {}
|
||||
FreshResume = (function(superClass) {
|
||||
extend1(FreshResume, superClass);
|
||||
|
||||
function FreshResume() {
|
||||
return FreshResume.__super__.constructor.apply(this, arguments);
|
||||
}
|
||||
|
||||
|
||||
/** Initialize the FreshResume from file. */
|
||||
@ -376,36 +386,8 @@ Definition of the FRESHResume class.
|
||||
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;
|
||||
return FreshResume.__super__.duration.call(this, 'employment.history', 'start', 'end', unit);
|
||||
};
|
||||
|
||||
|
||||
@ -420,7 +402,11 @@ Definition of the FRESHResume class.
|
||||
if (a.safe.start.isBefore(b.safe.start)) {
|
||||
return 1;
|
||||
} 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) {
|
||||
@ -448,7 +434,7 @@ Definition of the FRESHResume class.
|
||||
|
||||
return FreshResume;
|
||||
|
||||
})();
|
||||
})(AbstractResume);
|
||||
|
||||
|
||||
/**
|
||||
@ -499,7 +485,7 @@ Definition of the FRESHResume class.
|
||||
return;
|
||||
}
|
||||
if (Object.prototype.toString.call(obj) === '[object Array]') {
|
||||
return obj.forEach(function(elem) {
|
||||
obj.forEach(function(elem) {
|
||||
return replaceDatesInObject(elem);
|
||||
});
|
||||
} else if (typeof obj === 'object') {
|
||||
@ -509,19 +495,19 @@ Definition of the FRESHResume class.
|
||||
Object.keys(obj).forEach(function(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])) {
|
||||
obj.safe = obj.safe || {};
|
||||
obj.safe[val] = _fmt(obj[val]);
|
||||
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) {
|
||||
return replaceDatesInObject(that[member]);
|
||||
Object.keys(this).forEach(function(member) {
|
||||
replaceDatesInObject(that[member]);
|
||||
});
|
||||
};
|
||||
|
||||
|
40
dist/core/jrs-resume.js
vendored
40
dist/core/jrs-resume.js
vendored
@ -6,7 +6,9 @@ Definition of the JRSResume class.
|
||||
*/
|
||||
|
||||
(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');
|
||||
|
||||
@ -24,6 +26,8 @@ Definition of the JRSResume class.
|
||||
|
||||
moment = require('moment');
|
||||
|
||||
AbstractResume = require('./abstract-resume');
|
||||
|
||||
|
||||
/**
|
||||
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
|
||||
*/
|
||||
|
||||
JRSResume = (function() {
|
||||
JRSResume = (function(superClass) {
|
||||
var clear, format;
|
||||
|
||||
function JRSResume() {}
|
||||
extend1(JRSResume, superClass);
|
||||
|
||||
function JRSResume() {
|
||||
return JRSResume.__super__.constructor.apply(this, arguments);
|
||||
}
|
||||
|
||||
|
||||
/** Initialize the JSResume from file. */
|
||||
@ -249,30 +257,8 @@ Definition of the JRSResume class.
|
||||
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;
|
||||
return JRSResume.__super__.duration.call(this, 'work', 'startDate', 'endDate', unit);
|
||||
};
|
||||
|
||||
|
||||
@ -372,7 +358,7 @@ Definition of the JRSResume class.
|
||||
|
||||
return JRSResume;
|
||||
|
||||
})();
|
||||
})(AbstractResume);
|
||||
|
||||
|
||||
/** Get the default (empty) sheet. */
|
||||
|
30
dist/core/jrs-theme.js
vendored
30
dist/core/jrs-theme.js
vendored
@ -6,7 +6,7 @@ Definition of the JRSTheme class.
|
||||
*/
|
||||
|
||||
(function() {
|
||||
var JRSTheme, PATH, _, getFormat, parsePath, pathExists;
|
||||
var JRSTheme, PATH, _, parsePath, pathExists;
|
||||
|
||||
_ = require('underscore');
|
||||
|
||||
@ -25,17 +25,13 @@ Definition of the JRSTheme class.
|
||||
JRSTheme = (function() {
|
||||
function JRSTheme() {}
|
||||
|
||||
return JRSTheme;
|
||||
|
||||
})();
|
||||
|
||||
({
|
||||
|
||||
/**
|
||||
Open and parse the specified theme.
|
||||
@method open
|
||||
*/
|
||||
open: function(thFolder) {
|
||||
|
||||
JRSTheme.prototype.open = function(thFolder) {
|
||||
var pathInfo, pkgJsonPath, thApi, thPkg;
|
||||
this.folder = thFolder;
|
||||
pathInfo = parsePath(thFolder);
|
||||
@ -78,25 +74,31 @@ Definition of the JRSTheme class.
|
||||
};
|
||||
}
|
||||
return this;
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
Determine if the theme supports the output format.
|
||||
@method hasFormat
|
||||
*/
|
||||
hasFormat: function(fmt) {
|
||||
|
||||
JRSTheme.prototype.hasFormat = function(fmt) {
|
||||
return _.has(this.formats, fmt);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
Return the requested output format.
|
||||
@method getFormat
|
||||
*/
|
||||
});
|
||||
|
||||
getFormat = function(fmt) {
|
||||
return this.formats[fmt];
|
||||
};
|
||||
JRSTheme.prototype.getFormat = function(fmt) {
|
||||
return this.formats[fmt];
|
||||
};
|
||||
|
||||
return JRSTheme;
|
||||
|
||||
})();
|
||||
|
||||
module.exports = JRSTheme;
|
||||
|
||||
|
5
dist/renderers/jrs-generator.js
vendored
5
dist/renderers/jrs-generator.js
vendored
@ -36,10 +36,11 @@ Definition of the JRSGenerator class.
|
||||
generate: function(json, jst, format, cssInfo, opts, theme) {
|
||||
var org, rezHtml, turnoff;
|
||||
turnoff = ['log', 'error', 'dir'];
|
||||
org = turnoff.map(c)(function() {
|
||||
org = turnoff.map(function(c) {
|
||||
var ret;
|
||||
ret = console[c];
|
||||
return console[c] = function() {};
|
||||
console[c] = function() {};
|
||||
return ret;
|
||||
});
|
||||
rezHtml = theme.render(json.harden());
|
||||
turnoff.forEach(function(c, idx) {
|
||||
|
9
dist/verbs/analyze.js
vendored
9
dist/verbs/analyze.js
vendored
@ -26,14 +26,7 @@ Implementation of the 'analyze' verb for HackMyResume.
|
||||
|
||||
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);
|
||||
return this._super('analyze', analyze);
|
||||
}
|
||||
});
|
||||
|
||||
|
41
dist/verbs/build.js
vendored
41
dist/verbs/build.js
vendored
@ -74,18 +74,7 @@ Implementation of the 'build' verb for HackMyResume.
|
||||
|
||||
/** 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;
|
||||
return this._super('build', build);
|
||||
}
|
||||
});
|
||||
|
||||
@ -95,16 +84,16 @@ Implementation of the 'build' verb for HackMyResume.
|
||||
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.
|
||||
@param opts Generation options.
|
||||
*/
|
||||
|
||||
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) {
|
||||
this.err(HMSTATUS.resumeNotFound, {
|
||||
quit: true
|
||||
});
|
||||
return null;
|
||||
}
|
||||
prep(src, dst, opts);
|
||||
sheetObjects = ResumeFactory.load(src, {
|
||||
@ -115,9 +104,12 @@ Implementation of the 'build' verb for HackMyResume.
|
||||
sort: _opts.sort
|
||||
}
|
||||
}, this);
|
||||
if (!sheetObjects || _.some(sheetObjects, function(so) {
|
||||
problemSheets = _.filter(sheetObjects, function(so) {
|
||||
return so.fluenterror;
|
||||
})) {
|
||||
});
|
||||
if (problemSheets && problemSheets.length) {
|
||||
problemSheets[0].quit = true;
|
||||
this.err(problemSheets[0].fluenterror, problemSheets[0]);
|
||||
return null;
|
||||
}
|
||||
sheets = sheetObjects.map(function(r) {
|
||||
@ -130,12 +122,14 @@ Implementation of the 'build' verb for HackMyResume.
|
||||
try {
|
||||
tFolder = verifyTheme.call(this, _opts.theme);
|
||||
theme = _opts.themeObj = loadTheme(tFolder);
|
||||
addFreebieFormats(theme);
|
||||
} catch (_error) {
|
||||
ex = _error;
|
||||
newEx = {
|
||||
fluenterror: HMSTATUS.themeLoad,
|
||||
inner: ex,
|
||||
attempted: _opts.theme
|
||||
attempted: _opts.theme,
|
||||
quit: true
|
||||
};
|
||||
this.err(HMSTATUS.themeLoad, newEx);
|
||||
return null;
|
||||
@ -147,8 +141,10 @@ Implementation of the 'build' verb for HackMyResume.
|
||||
if (inv && inv.length) {
|
||||
this.err(HMSTATUS.invalidFormat, {
|
||||
data: inv,
|
||||
theme: theme
|
||||
theme: theme,
|
||||
quit: true
|
||||
});
|
||||
return null;
|
||||
}
|
||||
rez = null;
|
||||
if (sheets.length > 1) {
|
||||
@ -186,7 +182,6 @@ Implementation of the 'build' verb for HackMyResume.
|
||||
fmt: toFormat
|
||||
});
|
||||
}
|
||||
addFreebieFormats(theme);
|
||||
this.stat(HMEVENT.applyTheme, {
|
||||
r: rez,
|
||||
theme: theme
|
||||
@ -247,12 +242,12 @@ Implementation of the 'build' verb for HackMyResume.
|
||||
fmt: targInfo.fmt.outFormat,
|
||||
file: PATH.relative(process.cwd(), f)
|
||||
});
|
||||
_opts.targets = finished;
|
||||
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) {
|
||||
@ -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) {
|
||||
this.stat(HMEVENT.verifyOutputs, {
|
||||
|
9
dist/verbs/convert.js
vendored
9
dist/verbs/convert.js
vendored
@ -22,14 +22,7 @@ Implementation of the 'convert' verb for HackMyResume.
|
||||
|
||||
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);
|
||||
return this._super('convert', convert);
|
||||
}
|
||||
});
|
||||
|
||||
|
11
dist/verbs/create.js
vendored
11
dist/verbs/create.js
vendored
@ -24,16 +24,7 @@ Implementation of the 'create' verb for HackMyResume.
|
||||
|
||||
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'
|
||||
});
|
||||
return this._super('new', create);
|
||||
}
|
||||
});
|
||||
|
||||
|
9
dist/verbs/peek.js
vendored
9
dist/verbs/peek.js
vendored
@ -22,14 +22,7 @@ Implementation of the 'peek' verb for HackMyResume.
|
||||
|
||||
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);
|
||||
return this._super('peek', peek);
|
||||
}
|
||||
});
|
||||
|
||||
|
12
dist/verbs/validate.js
vendored
12
dist/verbs/validate.js
vendored
@ -31,16 +31,7 @@ Implementation of the 'validate' verb for HackMyResume.
|
||||
|
||||
ValidateVerb = module.exports = Verb.extend({
|
||||
init: function() {
|
||||
return this._super('validate');
|
||||
},
|
||||
invoke: function() {
|
||||
var ret;
|
||||
this.stat(HMEVENT.begin, {
|
||||
cmd: 'validate'
|
||||
});
|
||||
ret = validate.apply(this, arguments);
|
||||
this.stat(HMEVENT.end);
|
||||
return ret;
|
||||
return this._super('validate', validate);
|
||||
}
|
||||
});
|
||||
|
||||
@ -104,7 +95,6 @@ Implementation of the 'validate' verb for HackMyResume.
|
||||
shouldExit: true
|
||||
};
|
||||
}
|
||||
console.log('1111');
|
||||
return ret;
|
||||
}, this);
|
||||
};
|
||||
|
18
dist/verbs/verb.js
vendored
18
dist/verbs/verb.js
vendored
@ -6,12 +6,14 @@ Definition of the Verb class.
|
||||
*/
|
||||
|
||||
(function() {
|
||||
var Class, EVENTS, Verb;
|
||||
var Class, EVENTS, HMEVENT, Verb;
|
||||
|
||||
Class = require('../utils/class');
|
||||
|
||||
EVENTS = require('events');
|
||||
|
||||
HMEVENT = require('../core/event-codes');
|
||||
|
||||
|
||||
/**
|
||||
An instantiation of a HackMyResume command.
|
||||
@ -21,9 +23,21 @@ Definition of the Verb class.
|
||||
Verb = module.exports = Class.extend({
|
||||
|
||||
/** Constructor. Automatically called at creation. */
|
||||
init: function(moniker) {
|
||||
init: function(moniker, workhorse) {
|
||||
this.moniker = moniker;
|
||||
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. */
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"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.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
53
src/core/abstract-resume.coffee
Normal file
53
src/core/abstract-resume.coffee
Normal 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
|
@ -7,6 +7,7 @@ The HackMyResume date representation.
|
||||
|
||||
|
||||
moment = require 'moment'
|
||||
require('../utils/string')
|
||||
|
||||
###*
|
||||
Create a FluentDate from a string or Moment date object. There are a few date
|
||||
@ -30,6 +31,8 @@ class FluentDate
|
||||
constructor: (dt) ->
|
||||
@rep = this.fmt dt
|
||||
|
||||
@isCurrent: (dt) ->
|
||||
!dt || (String.is(dt) and /^(present|now|current)$/.test(dt))
|
||||
|
||||
months = {}
|
||||
abbr = {}
|
||||
@ -56,16 +59,7 @@ FluentDate.fmt = ( dt, throws ) ->
|
||||
else if /^\s*\d{4}\s*$/.test(dt) # "2015"
|
||||
return moment dt, 'YYYY'
|
||||
else if /^\s*$/.test(dt) # "", " "
|
||||
defTime =
|
||||
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
|
||||
return moment()
|
||||
else
|
||||
mt = moment dt
|
||||
if mt.isValid()
|
||||
|
@ -17,6 +17,8 @@ XML = require 'xml-escape'
|
||||
MD = require 'marked'
|
||||
CONVERTER = require 'fresh-jrs-converter'
|
||||
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.
|
||||
@constructor
|
||||
###
|
||||
class FreshResume
|
||||
class FreshResume extends AbstractResume
|
||||
|
||||
###* Initialize the FreshResume from file. ###
|
||||
open: ( file, opts ) ->
|
||||
@ -306,28 +308,8 @@ class FreshResume
|
||||
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) ->
|
||||
unit = unit || 'years'
|
||||
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
|
||||
|
||||
super('employment.history', 'start', 'end', unit)
|
||||
|
||||
|
||||
###*
|
||||
@ -337,9 +319,9 @@ class FreshResume
|
||||
sort: () ->
|
||||
|
||||
byDateDesc = (a,b) ->
|
||||
if ( a.safe.start.isBefore(b.safe.start) )
|
||||
if a.safe.start.isBefore(b.safe.start)
|
||||
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 ) ->
|
||||
ar = __.get this, key
|
||||
@ -352,10 +334,6 @@ class FreshResume
|
||||
sortSection 'service.history'
|
||||
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) ->
|
||||
if a.safe.date.isBefore b.safe.date
|
||||
then 1
|
||||
@ -400,6 +378,7 @@ _parseDates = () ->
|
||||
return if !obj
|
||||
if Object.prototype.toString.call( obj ) == '[object Array]'
|
||||
obj.forEach (elem) -> replaceDatesInObject( elem )
|
||||
return
|
||||
else if typeof obj == 'object'
|
||||
if obj._isAMomentObject || obj.safe
|
||||
return
|
||||
@ -410,8 +389,12 @@ _parseDates = () ->
|
||||
obj.safe[ val ] = _fmt obj[val]
|
||||
if obj[val] && (val == 'start') && !obj.end
|
||||
obj.safe.end = _fmt 'current'
|
||||
|
||||
Object.keys( this ).forEach (member) -> replaceDatesInObject(that[member])
|
||||
return
|
||||
return
|
||||
Object.keys( this ).forEach (member) ->
|
||||
replaceDatesInObject(that[member])
|
||||
return
|
||||
return
|
||||
|
||||
|
||||
|
||||
|
@ -14,7 +14,7 @@ PATH = require('path')
|
||||
MD = require('marked')
|
||||
CONVERTER = require('fresh-jrs-converter')
|
||||
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.
|
||||
@class JRSResume
|
||||
###
|
||||
class JRSResume
|
||||
class JRSResume extends AbstractResume
|
||||
|
||||
|
||||
|
||||
@ -203,25 +203,8 @@ class JRSResume
|
||||
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 ) ->
|
||||
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
|
||||
|
||||
duration: (unit) ->
|
||||
super('work', 'startDate', 'endDate', unit)
|
||||
|
||||
|
||||
###*
|
||||
|
@ -20,68 +20,69 @@ The JRSTheme class is a representation of a JSON Resume theme asset.
|
||||
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
|
||||
# name as folder
|
||||
pathInfo = parsePath thFolder
|
||||
@folder = thFolder
|
||||
|
||||
# Open and parse the theme's package.json file.
|
||||
pkgJsonPath = PATH.join thFolder, 'package.json'
|
||||
if pathExists pkgJsonPath
|
||||
thApi = require thFolder
|
||||
thPkg = require pkgJsonPath
|
||||
this.name = thPkg.name
|
||||
this.render = (thApi && thApi.render) || undefined
|
||||
this.engine = 'jrs'
|
||||
# Open the [theme-name].json file; should have the same
|
||||
# name as folder
|
||||
pathInfo = parsePath thFolder
|
||||
|
||||
# Create theme formats (HTML and PDF). Just add the bare minimum mix of
|
||||
# properties necessary to allow JSON Resume themes to share a rendering
|
||||
# path with FRESH themes.
|
||||
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 };
|
||||
@
|
||||
# Open and parse the theme's package.json file.
|
||||
pkgJsonPath = PATH.join thFolder, 'package.json'
|
||||
if pathExists pkgJsonPath
|
||||
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
|
||||
# properties necessary to allow JSON Resume themes to share a rendering
|
||||
# path with FRESH themes.
|
||||
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 };
|
||||
@
|
||||
|
||||
|
||||
|
||||
###*
|
||||
Determine if the theme supports the output format.
|
||||
@method hasFormat
|
||||
###
|
||||
hasFormat: ( fmt ) -> _.has this.formats, fmt
|
||||
###*
|
||||
Determine if the theme supports the output format.
|
||||
@method hasFormat
|
||||
###
|
||||
hasFormat: ( fmt ) -> _.has this.formats, fmt
|
||||
|
||||
|
||||
|
||||
###*
|
||||
Return the requested output format.
|
||||
@method getFormat
|
||||
###
|
||||
getFormat = ( fmt ) -> @formats[ fmt ]
|
||||
###*
|
||||
Return the requested output format.
|
||||
@method getFormat
|
||||
###
|
||||
getFormat: ( fmt ) -> @formats[ fmt ]
|
||||
|
||||
|
||||
module.exports = JRSTheme;
|
||||
|
@ -25,9 +25,10 @@ JRSGenerator = module.exports =
|
||||
|
||||
# Disable JRS theme chatter (console.log, console.error, etc.)
|
||||
turnoff = ['log', 'error', 'dir'];
|
||||
org = turnoff.map(c) ->
|
||||
org = turnoff.map (c) ->
|
||||
ret = console[c]
|
||||
console[c] = () ->
|
||||
ret
|
||||
|
||||
# Freeze and render
|
||||
rezHtml = theme.render json.harden()
|
||||
|
@ -19,12 +19,7 @@ chalk = require('chalk')
|
||||
|
||||
AnalyzeVerb = module.exports = Verb.extend
|
||||
|
||||
init: -> @._super 'analyze'
|
||||
|
||||
invoke: ->
|
||||
@.stat HMEVENT.begin, { cmd: 'analyze' }
|
||||
analyze.apply @, arguments
|
||||
@.stat HMEVENT.end
|
||||
init: -> @_super 'analyze', analyze
|
||||
|
||||
|
||||
|
||||
|
@ -42,14 +42,7 @@ loadTheme = null
|
||||
BuildVerb = module.exports = Verb.extend
|
||||
|
||||
###* Create a new build verb. ###
|
||||
init: () -> @._super 'build'
|
||||
|
||||
###* Invoke the Build command. ###
|
||||
invoke: ->
|
||||
@stat HMEVENT.begin, { cmd: 'build' }
|
||||
ret = build.apply @, arguments
|
||||
@stat HMEVENT.end
|
||||
return ret
|
||||
init: () -> @_super 'build', build
|
||||
|
||||
|
||||
|
||||
@ -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.
|
||||
@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.
|
||||
@param opts Generation options.
|
||||
###
|
||||
build = ( src, dst, opts ) ->
|
||||
|
||||
if !src || !src.length
|
||||
@err HMSTATUS.resumeNotFound, { quit: true }
|
||||
@err HMSTATUS.resumeNotFound, quit: true
|
||||
return null
|
||||
|
||||
prep src, dst, opts
|
||||
|
||||
@ -74,10 +67,14 @@ build = ( src, dst, opts ) ->
|
||||
}, @);
|
||||
|
||||
# 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
|
||||
|
||||
sheets = sheetObjects.map((r) -> return r.json )
|
||||
# Get the collection of raw JSON sheets
|
||||
sheets = sheetObjects.map (r) -> r.json
|
||||
|
||||
# Load the theme...
|
||||
theme = null
|
||||
@ -85,12 +82,14 @@ build = ( src, dst, opts ) ->
|
||||
try
|
||||
tFolder = verifyTheme.call @, _opts.theme
|
||||
theme = _opts.themeObj = loadTheme tFolder
|
||||
addFreebieFormats theme
|
||||
catch ex
|
||||
newEx =
|
||||
fluenterror: HMSTATUS.themeLoad
|
||||
inner: ex
|
||||
attempted: _opts.theme
|
||||
this.err HMSTATUS.themeLoad, newEx
|
||||
quit: true
|
||||
@err HMSTATUS.themeLoad, newEx
|
||||
return null
|
||||
|
||||
@stat HMEVENT.afterTheme, { theme: theme }
|
||||
@ -98,7 +97,8 @@ build = ( src, dst, opts ) ->
|
||||
# Check for invalid outputs...
|
||||
inv = verifyOutputs.call @, dst, theme
|
||||
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.
|
||||
rez = null
|
||||
@ -107,7 +107,7 @@ build = ( src, dst, opts ) ->
|
||||
mixed = _.any sheets, (s) -> return if isFRESH then s.basics else !s.basics
|
||||
@stat HMEVENT.beforeMerge, { f: _.clone(sheetObjects), mixed: mixed }
|
||||
if mixed
|
||||
this.err HMSTATUS.mixedMerge
|
||||
@err HMSTATUS.mixedMerge
|
||||
|
||||
rez = _.reduceRight sheets, ( a, b, idx ) ->
|
||||
extend( true, b, a )
|
||||
@ -124,8 +124,7 @@ build = ( src, dst, opts ) ->
|
||||
rez = RConverter[ 'to' + toFormat ]( rez );
|
||||
@stat HMEVENT.afterInlineConvert, { file: sheetObjects[0].file, fmt: toFormat }
|
||||
|
||||
# Add freebie formats to the theme
|
||||
addFreebieFormats theme
|
||||
# Announce the theme
|
||||
@stat HMEVENT.applyTheme, { r: rez, theme: theme }
|
||||
|
||||
# Load the resume into a FRESHResume or JRSResume object
|
||||
@ -195,13 +194,14 @@ single = ( targInfo, theme, finished ) ->
|
||||
fmt: targInfo.fmt.outFormat
|
||||
file: PATH.relative(process.cwd(), f)
|
||||
|
||||
_opts.targets = finished;
|
||||
|
||||
# If targInfo.fmt.files exists, this format is backed by a document.
|
||||
# Fluent/FRESH themes are handled here.
|
||||
if targInfo.fmt.files && targInfo.fmt.files.length
|
||||
theFormat = _fmts.filter(
|
||||
(fmt) -> return fmt.name == targInfo.fmt.outFormat )[0];
|
||||
MKDIRP.sync( PATH.dirname( f ) ); # Ensure dest folder exists;
|
||||
_opts.targets = finished;
|
||||
ret = theFormat.gen.generate( _rezObj, f, _opts );
|
||||
|
||||
# Otherwise this is an ad-hoc format (JSON, YML, or PNG) that every theme
|
||||
@ -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 ) ->
|
||||
@.stat HMEVENT.verifyOutputs, { targets: targets, theme: theme }
|
||||
@stat HMEVENT.verifyOutputs, { targets: targets, theme: theme }
|
||||
_.reject targets.map( ( t ) ->
|
||||
pathInfo = parsePath t
|
||||
{
|
||||
@ -247,6 +245,8 @@ verifyOutputs = ( targets, theme ) ->
|
||||
}),
|
||||
(t) -> t.format == 'all' || theme.hasFormat( t.format )
|
||||
|
||||
|
||||
|
||||
###*
|
||||
Reinforce the chosen theme with "freebie" formats provided by HackMyResume.
|
||||
A "freebie" format is an output format such as JSON, YML, or PNG that can be
|
||||
|
@ -17,12 +17,7 @@ HMEVENT = require('../core/event-codes');
|
||||
|
||||
ConvertVerb = module.exports = Verb.extend
|
||||
|
||||
init: -> @._super 'convert'
|
||||
|
||||
invoke: ->
|
||||
@.stat HMEVENT.begin, { cmd: 'convert' }
|
||||
convert.apply @, arguments
|
||||
@.stat HMEVENT.end
|
||||
init: -> @_super 'convert', convert
|
||||
|
||||
|
||||
|
||||
|
@ -17,13 +17,8 @@ HMEVENT = require('../core/event-codes')
|
||||
|
||||
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
|
||||
|
||||
|
||||
###*
|
||||
|
@ -17,12 +17,7 @@ HMEVENT = require('../core/event-codes')
|
||||
|
||||
PeekVerb = module.exports = Verb.extend
|
||||
|
||||
init: -> @._super('peek')
|
||||
|
||||
invoke: ->
|
||||
@.stat HMEVENT.begin, { cmd: 'peek' }
|
||||
peek.apply @, arguments
|
||||
@.stat HMEVENT.end
|
||||
init: -> @_super 'peek', peek
|
||||
|
||||
|
||||
|
||||
|
@ -21,13 +21,7 @@ safeLoadJSON = require '../utils/safe-json-loader'
|
||||
###* An invokable resume validation command. ###
|
||||
ValidateVerb = module.exports = Verb.extend
|
||||
|
||||
init: -> @_super 'validate'
|
||||
|
||||
invoke: ->
|
||||
@stat HMEVENT.begin, { cmd: 'validate' }
|
||||
ret = validate.apply @, arguments
|
||||
@stat HMEVENT.end
|
||||
return ret
|
||||
init: -> @_super 'validate', validate
|
||||
|
||||
|
||||
|
||||
@ -85,8 +79,6 @@ validate = (sources, unused, opts) ->
|
||||
if opts.assert and !ret.isValid
|
||||
throw fluenterror: HMSTATUS.invalid, shouldExit: true
|
||||
|
||||
console.log '1111'
|
||||
|
||||
return ret
|
||||
ret
|
||||
|
||||
, @
|
||||
|
@ -6,10 +6,10 @@ Definition of the Verb class.
|
||||
|
||||
|
||||
|
||||
|
||||
# Use J. Resig's nifty class implementation
|
||||
Class = require '../utils/class'
|
||||
EVENTS = require 'events'
|
||||
HMEVENT = require '../core/event-codes'
|
||||
|
||||
|
||||
|
||||
@ -22,24 +22,34 @@ Verb = module.exports = Class.extend
|
||||
|
||||
|
||||
###* Constructor. Automatically called at creation. ###
|
||||
init: ( moniker ) ->
|
||||
@.moniker = moniker
|
||||
@.emitter = new EVENTS.EventEmitter()
|
||||
init: ( moniker, workhorse ) ->
|
||||
@moniker = moniker
|
||||
@emitter = new EVENTS.EventEmitter()
|
||||
@workhorse = workhorse
|
||||
return
|
||||
|
||||
|
||||
|
||||
###* Invoke the command. ###
|
||||
invoke: ->
|
||||
@stat HMEVENT.begin, cmd: @moniker
|
||||
ret = @workhorse.apply @, arguments
|
||||
@stat HMEVENT.end
|
||||
ret
|
||||
|
||||
|
||||
|
||||
###* Forward subscriptions to the event emitter. ###
|
||||
on: ->
|
||||
this.emitter.on.apply @.emitter, arguments
|
||||
@emitter.on.apply @emitter, arguments
|
||||
|
||||
|
||||
|
||||
###* Fire an arbitrary event, scoped to "hmr:". ###
|
||||
fire: (evtName, payload) ->
|
||||
payload = payload || { }
|
||||
payload.cmd = this.moniker
|
||||
this.emitter.emit 'hmr:' + evtName, payload
|
||||
payload.cmd = @moniker
|
||||
@emitter.emit 'hmr:' + evtName, payload
|
||||
true
|
||||
|
||||
|
||||
@ -60,10 +70,11 @@ Verb = module.exports = Class.extend
|
||||
stat: ( subEvent, payload ) ->
|
||||
payload = payload || { }
|
||||
payload.sub = subEvent
|
||||
this.fire 'status', payload
|
||||
@fire 'status', payload
|
||||
true
|
||||
|
||||
|
||||
|
||||
###* Associate error info with the invocation. ###
|
||||
setError: ( code, obj ) ->
|
||||
@errorCode = code
|
||||
|
@ -9,3 +9,4 @@ require('./scripts/test-jrs-sheet');
|
||||
require('./scripts/test-themes');
|
||||
require('./scripts/test-api');
|
||||
require('./scripts/test-output');
|
||||
require('./scripts/test-dates');
|
||||
|
81
test/scripts/test-dates.js
Normal file
81
test/scripts/test-dates.js
Normal 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 );
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
});
|
Reference in New Issue
Block a user