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

Compare commits

...

48 Commits

Author SHA1 Message Date
901659cc4f make package depend on my upstream theme 2020-03-17 04:20:38 +00:00
3cf850ea0e Update test exemplars. 2016-02-14 05:32:33 -05:00
1b0bc87b60 Update changelog and version. 2016-02-14 04:54:44 -05:00
5d3b993737 Bump fresh-themes to 0.15.1-beta.
Not 100% necessary given "^" but support naive stripping of the "^"
decorator.
2016-02-14 04:32:39 -05:00
917fd8e3f3 Refactor helpers.
Rebind Handlebars helpers to drop the pesky options hash for standalone
helpers that don't need it. Move block helpers (which do need the
Handlebars options/context) to a separate file for special handling.
2016-02-14 04:10:23 -05:00
6ac2cd490b Bump fresh-test-resumes to 0.7.0. 2016-02-13 23:45:53 -05:00
8100190978 Bump fresh-themes to 0.15.0-beta. 2016-02-13 22:54:43 -05:00
7c36ff8331 Introduce "date" helper. 2016-02-13 22:54:07 -05:00
255a518565 Set test timeout to 30 seconds.
Most themes should generate in < 1s but allow up to 30 seconds for
network latency when opening a remote file, or for fetching remote
resources (CSS, JS, etc) during a local build.
2016-02-13 20:42:03 -05:00
2d595350c6 Escape LaTeX during generation. 2016-02-13 20:40:17 -05:00
ca92d41d9e Numerous fixes. 2016-02-13 16:08:45 -05:00
3f8e795c61 Fix generation glitches.
Fix output file name glitch, writing CSS files to destination folder,
and an issue where the process would evaporate before PDF/PNG generation
could complete.
2016-02-13 03:27:11 -05:00
9927e79900 Clean up CoffeeScript. 2016-02-13 00:40:10 -05:00
dbef9f0a35 Improve VALIDATE error handling. 2016-02-13 00:11:52 -05:00
c889664c31 More VALIDATE fixups. 2016-02-12 23:47:08 -05:00
7a60cd0bab Fixup VALIDATE command.
Introduce MISSING and UNKNOWN states alongside BROKEN, VALID, and
INVALID and fix regressions introduced in previous refactorings.
2016-02-12 22:49:56 -05:00
964350d3c7 Bump fresh-jrs-converter to 0.2.2.
Technically the "^0.2.1" implies v0.2.2 but eventually we'll drop the
"^" and use shrinkwrapped versions in dev, so explicitly bump for now.
2016-02-12 21:42:50 -05:00
b57d9f05af jsHint: Allow == null.
Relax jsHint's barbaric and antiquated default behavior on triple-equals
null comparisons. Allow expressive, precise, and subtle expressions such
as CoffeeScript's use of "== null" for testing against null OR undefined
under the existential operator.
2016-02-12 17:42:48 -05:00
b26799f9fc Improve JSON error handling.
Add support for detection of invalid line breaks in JSON string values.
Fixes #137. Could be improved to fetch the column number and drop the
messy grabbing of the line number from the exception message via regex,
but currently the "jsonlint" library (not to be confused with
"json-lint") only emits an error string. Since this is also the library
that drives http://jsonlint.com, we'll accept the messy regex in return
for more robust error checking when our default json-lint path fails.

All of the above only necessary because standard JSON.parse error
handling is broken in all environments. : )
2016-02-12 17:11:11 -05:00
daeffd27b5 Remove HB reference from generic helpers. 2016-02-11 22:06:43 -05:00
f87eb46549 Fix theme generation error. 2016-02-11 22:04:11 -05:00
da7cd28734 Remove unused var. 2016-02-11 22:03:49 -05:00
31e0bb69cc Introduce "pad()" helper.
Introduce a helper to emit padded strings / arrays of strings.
2016-02-11 22:02:50 -05:00
5c248cca2a Remove output folder. 2016-02-11 12:09:47 -05:00
f83eb018e8 Scrub tests. 2016-02-11 12:08:11 -05:00
317a250917 Gather. 2016-02-11 11:48:44 -05:00
aaa5e1fc1f Refactor generation.
Merge implicit and explicit generation paths, start emitting file
transform & copy signals, fix various bugs, introduce new bugs, support
better --debug outputs in the future.
2016-02-09 15:27:34 -05:00
1bc4263a46 Aerate. 2016-02-09 10:50:10 -05:00
e191af1fb0 Fix glitch in converted CoffeeScript.
Replace naked ternary with if then else.
2016-02-09 10:41:48 -05:00
7c0a9bcc02 Aerate. 2016-02-09 10:37:33 -05:00
d894f62607 Add ResumeFactory to facade.
Until facade is decommissioned and mothballed
2016-02-09 08:55:00 -05:00
2758038858 Cleanup and bug fixes.
Remove file-based open methods from resume classes; force clients to use
clean string-based or JSON overloads; fix processing glitch in
validate(); tweak outputs; adjust tests; update CHANGELOG; etc.
2016-02-04 18:49:16 -05:00
661fb91861 Aerate. 2016-02-04 15:23:47 -05:00
3c551eb923 Point package.json "main" at "dist" folder. 2016-02-04 14:38:11 -05:00
5bf4bda6de Fix PEEK command. 2016-02-03 20:08:17 -05:00
49ae016f08 Deglitch. 2016-02-02 19:02:56 -05:00
89957aed76 Scrub.
Adding slightly heavier function-level comments as a start for API docs.
2016-02-02 17:47:32 -05:00
233025ddcc Fix indentation. 2016-02-02 17:46:38 -05:00
11dd8952d8 Improve PEEK behavior. 2016-02-02 17:34:10 -05:00
d7c83613df Make CLI tests asynchronous. 2016-02-02 16:18:38 -05:00
a456093f13 Clean up a couple regressions. 2016-02-02 14:13:38 -05:00
dd4851498a Remove Resig's class implementation.
Fun while it lasted.
2016-02-02 13:49:02 -05:00
f72b02a0f4 Refactor generators to CoffeeScript classes. 2016-02-02 13:38:12 -05:00
63a0c78fc5 Refactor verbs to CoffeeScript classes.
Retire Resig's class implementation.
2016-02-01 23:16:49 -05:00
fd39cc9fd9 Adjust error handling / tests. 2016-02-01 22:56:08 -05:00
70f45d468d Asynchrony. 2016-02-01 22:52:13 -05:00
212b01092c Improve proc spawn behavior.
Interim until async / promises support is in.
2016-02-01 09:25:22 -05:00
36d641801b Add Gitter chat badge. 2016-01-31 20:02:27 -05:00
125 changed files with 9195 additions and 1750 deletions

1
.gitignore vendored
View File

@ -4,6 +4,7 @@ doc/
docs/ docs/
local/ local/
npm-debug.log npm-debug.log
*.map
# Emacs detritus # Emacs detritus
# -*- mode: gitignore; -*- # -*- mode: gitignore; -*-

View File

@ -1,5 +1,108 @@
CHANGELOG CHANGELOG
========= =========
## v1.8.0
### Added
- Updated `Awesome` theme to latest version of [Awesome-CV][acv].
- Introduced new theme helpers: `pad`, `date`.
### Fixed
- Fixed an issue where the `Awesome` theme wouldn't correctly generate LaTeX
outputs (#138).
- Emit a line number for syntax errors around embedded newlines in JSON strings
(#137).
- Fix several PDF / PNG generation errors (#132, others).
- Display a more helpful error message when attempting to generate a PDF or PNG
on a machine where PhantomJS and/or wkhtmltopdf are either not installed or
not path-accessible.
- Fixed an issue that would cause long-running PDF/PNG generation to fail in
certain environments.
- Fixed an issue involving an unhelpful spawn-related exception (#136).
### Internal
- JSHint will no longer gripe at the use of `== null` and `!= null` in
CoffeeScript transpilation.
- Introduced [template-friendly Awesome-CV fork][awefork] to isolate template
expansion logic & provide better durability for HackMyResume's `awesome` theme.
- Fixed a couple temporary regressions (#139, #140) on the dev branch.
- Additional tests.
- Minor breaking HackMyResume API changes.
## v1.7.4
### Added
- [Build instructions](https://github.com/hacksalot/HackMyResume/blob/master/BUILDING.md).
### Changed
- More precise date handling.
### Fixed
- Issue with incomplete PDF generation (#127).
- Issue with building JSON Resume themes (#128).
- Issue with generating `.json` output format by itself (#97).
## v1.7.3
### Fixed
- Issue with generated PDFs being chopped off and displaying a mysterious sequence of numbers of unknown and possibly alien origin (#127).
- Unsightly border on Modern:PDF.
- Modern|Positive:PDF formats now correctly reference their PDF-specific CSS files.
- `Incorrect helper use` warning in Positive:DOC.
## v1.7.2
### Changed
- Interim release supporting FluentCV Desktop.
### Internal
- Moved [HackMyCore](https://github.com/hacksalot/HackMyCore) dependency to
submodule.
## v1.7.1
### Changed
- Caffeinate. CoffeeScript now used throughout
[HackMyResume](https://github.com/hacksalot/HackMyResume) and
[HackMyCore](https://github.com/hacksalot/HackMyCore); generated JavaScript
lives in `/dist`.
### Fixed
- Issue with generating a single PDF with the `.pdf` extension (#99).
## v1.7.0
### Changed
- [Internal] Relocated HMR processing code to the
[HackMyCore](https://github.com/hacksalot/HackMyCore) project. Shouldn't affect
normal use.
## v1.6.0 ## v1.6.0
### Major Improvements ### Major Improvements
@ -304,3 +407,5 @@ theme.
[i111]: https://github.com/hacksalot/HackMyResume/issues/111 [i111]: https://github.com/hacksalot/HackMyResume/issues/111
[fresca]: https://github.com/fluentdesk/FRESCA [fresca]: https://github.com/fluentdesk/FRESCA
[themes]: https://github.com/fluentdesk/fresh-themes [themes]: https://github.com/fluentdesk/fresh-themes
[awefork]: https://github.com/fluentdesk/Awesome-CV
[acv]: https://github.com/posquit0/Awesome-CV

View File

@ -17,6 +17,9 @@ module.exports = function (grunt) {
coffee: { coffee: {
main: { main: {
options: {
sourceMap: true
},
expand: true, expand: true,
cwd: 'src', cwd: 'src',
src: ['**/*.coffee'], src: ['**/*.coffee'],
@ -67,7 +70,8 @@ module.exports = function (grunt) {
jshint: { jshint: {
options: { options: {
laxcomma: true, laxcomma: true,
expr: true expr: true,
eqnull: true
}, },
all: ['Gruntfile.js', 'dist/cli/**/*.js', 'test/*.js'] all: ['Gruntfile.js', 'dist/cli/**/*.js', 'test/*.js']
} }

View File

@ -4,6 +4,7 @@ HackMyResume
[![Latest release][img-release]][latest-release] [![Latest release][img-release]][latest-release]
[![Build status (MASTER)][img-master]][travis-url-master] [![Build status (MASTER)][img-master]][travis-url-master]
[![Build status (DEV)][img-dev]][travis-url-dev] [![Build status (DEV)][img-dev]][travis-url-dev]
[![Join the chat at https://gitter.im/hacksalot/HackMyResume](https://badges.gitter.im/hacksalot/HackMyResume.svg)](https://gitter.im/hacksalot/HackMyResume?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
*Create polished résumés and CVs in multiple formats from your command line or *Create polished résumés and CVs in multiple formats from your command line or
shell. Author in clean Markdown and JSON, export to Word, HTML, PDF, LaTeX, shell. Author in clean Markdown and JSON, export to Word, HTML, PDF, LaTeX,

28
dist/cli/error.js vendored
View File

@ -35,8 +35,7 @@ Error-handling routines for HackMyResume.
require('string.prototype.startswith'); require('string.prototype.startswith');
/** /** Error handler for HackMyResume. All errors are handled here.
Error handler for HackMyResume. All errors are handled here.
@class ErrorHandler @class ErrorHandler
*/ */
@ -62,7 +61,7 @@ Error-handling routines for HackMyResume.
stack = ex.stack || (ex.inner && ex.inner.stack); stack = ex.stack || (ex.inner && ex.inner.stack);
stack && o(chalk.gray(stack)); stack && o(chalk.gray(stack));
} }
if (ex.quit || objError.quit) { if (shouldExit) {
if (this.debug) { if (this.debug) {
o(chalk.cyan('Exiting with error code ' + ex.fluenterror.toString())); o(chalk.cyan('Exiting with error code ' + ex.fluenterror.toString()));
} }
@ -140,7 +139,6 @@ Error-handling routines for HackMyResume.
if (ex.inner) { if (ex.inner) {
msg += chalk.red('\n' + ex.inner); msg += chalk.red('\n' + ex.inner);
} }
withStack = true;
quit = false; quit = false;
etype = 'error'; etype = 'error';
break; break;
@ -215,13 +213,27 @@ Error-handling routines for HackMyResume.
if (SyntaxErrorEx.is(ex.inner)) { if (SyntaxErrorEx.is(ex.inner)) {
console.error(printf(M2C(this.msgs.readError.msg, 'red'), ex.file)); console.error(printf(M2C(this.msgs.readError.msg, 'red'), ex.file));
se = new SyntaxErrorEx(ex, ex.raw); se = new SyntaxErrorEx(ex, ex.raw);
msg = printf(M2C(this.msgs.parseError.msg, 'red'), se.line, se.col); if ((se.line != null) && (se.col != null)) {
} else if (ex.inner && ex.inner.line !== void 0 && ex.inner.col !== void 0) { msg = printf(M2C(this.msgs.parseError.msg[0], 'red'), se.line, se.col);
msg = printf(M2C(this.msgs.parseError.msg, 'red'), ex.inner.line, ex.inner.col); } else if (se.line != null) {
msg = printf(M2C(this.msgs.parseError.msg[1], 'red'), se.line);
} else {
msg = M2C(this.msgs.parseError.msg[2], 'red');
}
} else if (ex.inner && (ex.inner.line != null) && (ex.inner.col != null)) {
msg = printf(M2C(this.msgs.parseError.msg[0], 'red'), ex.inner.line, ex.inner.col);
} else { } else {
msg = ex; msg = ex;
} }
etype = 'error'; etype = 'error';
break;
case HMSTATUS.createError:
msg = printf(M2C(this.msgs.createError.msg), ex.inner.path);
etype = 'error';
break;
case HMSTATUS.validateError:
msg = printf(M2C(this.msgs.validateError.msg), ex.inner.toString());
etype = 'error';
} }
return { return {
msg: msg, msg: msg,
@ -232,3 +244,5 @@ Error-handling routines for HackMyResume.
}; };
}).call(this); }).call(this);
//# sourceMappingURL=error.js.map

76
dist/cli/main.js vendored
View File

@ -6,7 +6,7 @@ Definition of the `main` function.
*/ */
(function() { (function() {
var Command, EXTEND, FS, HME, HMR, HMSTATUS, OUTPUT, PAD, PATH, PKG, StringUtils, _, _opts, _out, _title, chalk, execute, initOptions, initialize, loadOptions, logMsg, main, safeLoadJSON, splitSrcDest; var Command, EXTEND, FS, HME, HMR, HMSTATUS, M2C, OUTPUT, PAD, PATH, PKG, StringUtils, _, _err, _exitCallback, _opts, _out, _title, chalk, execute, executeFail, executeSuccess, initOptions, initialize, loadOptions, logMsg, main, printf, safeLoadJSON, splitSrcDest;
HMR = require('../index'); HMR = require('../index');
@ -36,12 +36,20 @@ Definition of the `main` function.
Command = require('commander').Command; Command = require('commander').Command;
M2C = require('../utils/md2chalk');
printf = require('printf');
_opts = {}; _opts = {};
_title = chalk.white.bold('\n*** HackMyResume v' + PKG.version + ' ***'); _title = chalk.white.bold('\n*** HackMyResume v' + PKG.version + ' ***');
_out = new OUTPUT(_opts); _out = new OUTPUT(_opts);
_err = require('./error');
_exitCallback = null;
/* /*
A callable implementation of the HackMyResume CLI. Encapsulates the command A callable implementation of the HackMyResume CLI. Encapsulates the command
@ -51,9 +59,9 @@ Definition of the `main` function.
process.argv (in production) or custom parameters (in test). process.argv (in production) or custom parameters (in test).
*/ */
main = module.exports = function(rawArgs) { main = module.exports = function(rawArgs, exitCallback) {
var args, initInfo, program; var args, initInfo, program;
initInfo = initialize(rawArgs); initInfo = initialize(rawArgs, exitCallback);
args = initInfo.args; args = initInfo.args;
program = new Command('hackmyresume').version(PKG.version).description(chalk.yellow.bold('*** HackMyResume ***')).option('-s --silent', 'Run in silent mode').option('--no-color', 'Disable colors').option('--color', 'Enable colors').option('-d --debug', 'Enable diagnostics', false).option('-a --assert', 'Treat warnings as errors', false).option('-v --version', 'Show the version').allowUnknownOption(); program = new Command('hackmyresume').version(PKG.version).description(chalk.yellow.bold('*** HackMyResume ***')).option('-s --silent', 'Run in silent mode').option('--no-color', 'Disable colors').option('--color', 'Enable colors').option('-d --debug', 'Enable diagnostics', false).option('-a --assert', 'Treat warnings as errors', false).option('-v --version', 'Show the version').allowUnknownOption();
program.jsonArgs = initInfo.options; program.jsonArgs = initInfo.options;
@ -92,8 +100,9 @@ Definition of the `main` function.
/* Massage command-line args and setup Commander.js. */ /* Massage command-line args and setup Commander.js. */
initialize = function(ar) { initialize = function(ar, exitCallback) {
var o; var o;
_exitCallback = exitCallback || process.exit;
o = initOptions(ar); o = initOptions(ar);
o.silent || logMsg(_title); o.silent || logMsg(_title);
if (o.debug) { if (o.debug) {
@ -105,20 +114,18 @@ Definition of the `main` function.
_out.log(chalk.cyan(PAD(' FRESCA:', 25, null, PAD.RIGHT)) + chalk.cyan.bold(PKG.dependencies.fresca)); _out.log(chalk.cyan(PAD(' FRESCA:', 25, null, PAD.RIGHT)) + chalk.cyan.bold(PKG.dependencies.fresca));
_out.log(''); _out.log('');
} }
_err.init(o.debug, o.assert, o.silent);
if (o.verb && !HMR.verbs[o.verb] && !HMR.alias[o.verb]) { if (o.verb && !HMR.verbs[o.verb] && !HMR.alias[o.verb]) {
throw { _err.err({
fluenterror: HMSTATUS.invalidCommand, fluenterror: HMSTATUS.invalidCommand,
quit: true, quit: true,
attempted: o.orgVerb attempted: o.orgVerb
}; }, true);
} }
Command.prototype.missingArgument = function(name) { Command.prototype.missingArgument = function(name) {
if (this.name() !== 'new') { _err.err({
throw { fluenterror: this.name() !== 'new' ? HMSTATUS.resumeNotFound : HMSTATUS.createNameMissing
fluenterror: HMSTATUS.resumeNotFound, }, true);
quit: true
};
}
}; };
Command.prototype.helpInformation = function() { Command.prototype.helpInformation = function() {
var manPage; var manPage;
@ -136,7 +143,7 @@ Definition of the `main` function.
initOptions = function(ar) { initOptions = function(ar) {
oVerb; oVerb;
var args, cleanArgs, inf, isDebug, isMono, isSilent, oJSON, oVerb, optStr, optsIdx, verb, vidx; var args, cleanArgs, inf, isAssert, isDebug, isMono, isSilent, oJSON, oVerb, optStr, optsIdx, verb, vidx;
verb = ''; verb = '';
args = ar.slice(); args = ar.slice();
cleanArgs = args.slice(2); cleanArgs = args.slice(2);
@ -177,6 +184,9 @@ Definition of the `main` function.
isSilent = _.some(args, function(v) { isSilent = _.some(args, function(v) {
return v === '-s' || v === '--silent'; return v === '-s' || v === '--silent';
}); });
isAssert = _.some(args, function(v) {
return v === '-a' || v === '--assert';
});
isMono = _.some(args, function(v) { isMono = _.some(args, function(v) {
return v === '--no-color'; return v === '--no-color';
}); });
@ -184,6 +194,7 @@ Definition of the `main` function.
color: !isMono, color: !isMono,
debug: isDebug, debug: isDebug,
silent: isSilent, silent: isSilent,
assert: isAssert,
orgVerb: oVerb, orgVerb: oVerb,
verb: verb, verb: verb,
json: oJSON, json: oJSON,
@ -195,24 +206,43 @@ Definition of the `main` function.
/* Invoke a HackMyResume verb. */ /* Invoke a HackMyResume verb. */
execute = function(src, dst, opts, log) { execute = function(src, dst, opts, log) {
var hand, v; var prom, v;
loadOptions.call(this, opts, this.parent.jsonArgs);
hand = require('./error');
hand.init(_opts.debug, _opts.assert, _opts.silent);
v = new HMR.verbs[this.name()](); v = new HMR.verbs[this.name()]();
loadOptions.call(this, opts, this.parent.jsonArgs);
_opts.errHandler = v; _opts.errHandler = v;
_out.init(_opts); _out.init(_opts);
v.on('hmr:status', function() { v.on('hmr:status', function() {
return _out["do"].apply(_out, arguments); return _out["do"].apply(_out, arguments);
}); });
v.on('hmr:error', function() { v.on('hmr:error', function() {
return hand.err.apply(hand, arguments); return _err.err.apply(_err, arguments);
}); });
v.invoke.call(v, src, dst, _opts, log); prom = v.invoke.call(v, src, dst, _opts, log);
if (v.errorCode) { prom.then(executeSuccess, executeFail);
console.log('Exiting with error code ' + v.errorCode); };
return process.exit(v.errorCode);
/* Success handler for verb invocations. Calls process.exit by default */
executeSuccess = function(obj) {};
/* Failure handler for verb invocations. Calls process.exit by default */
executeFail = function(err) {
var finalErrorCode, msgs;
finalErrorCode = -1;
if (err) {
finalErrorCode = err.fluenterror ? err.fluenterror : err;
} }
if (_opts.debug) {
msgs = require('./msg').errors;
logMsg(printf(M2C(msgs.exiting.msg, 'cyan'), finalErrorCode));
if (err.stack) {
logMsg(err.stack);
}
}
_exitCallback(finalErrorCode);
}; };
@ -282,3 +312,5 @@ Definition of the `main` function.
}; };
}).call(this); }).call(this);
//# sourceMappingURL=main.js.map

2
dist/cli/msg.js vendored
View File

@ -15,3 +15,5 @@ Message-handling routines for HackMyResume.
module.exports = YAML.load(PATH.join(__dirname, 'msg.yml')); module.exports = YAML.load(PATH.join(__dirname, 'msg.yml'));
}).call(this); }).call(this);
//# sourceMappingURL=msg.js.map

15
dist/cli/msg.yml vendored
View File

@ -3,6 +3,8 @@ events:
msg: Invoking **%s** command. msg: Invoking **%s** command.
beforeCreate: beforeCreate:
msg: Creating new **%s** resume: **%s** msg: Creating new **%s** resume: **%s**
afterCreate:
msg: Creating new **%s** resume: **%s**
afterRead: afterRead:
msg: Reading **%s** resume: **%s** msg: Reading **%s** resume: **%s**
beforeTheme: beforeTheme:
@ -41,6 +43,8 @@ events:
- "VALID!" - "VALID!"
- "INVALID" - "INVALID"
- "BROKEN" - "BROKEN"
- "MISSING"
- "ERROR"
beforePeek: beforePeek:
msg: msg:
- Peeking at **%s** in **%s** - Peeking at **%s** in **%s**
@ -79,7 +83,10 @@ errors:
readError: readError:
msg: Reading **???** resume: **%s** msg: Reading **???** resume: **%s**
parseError: parseError:
msg: Invalid or corrupt JSON on line %s column %s. msg:
- Invalid or corrupt JSON on line %s column %s.
- Invalid or corrupt JSON on line %s.
- Invalid or corrupt JSON.
invalidHelperUse: invalidHelperUse:
msg: "**Warning**: Incorrect use of the **%s** theme helper." msg: "**Warning**: Incorrect use of the **%s** theme helper."
fileSaveError: fileSaveError:
@ -96,3 +103,9 @@ errors:
msg: "Invalid number of parameters. Expected: **%s**." msg: "Invalid number of parameters. Expected: **%s**."
missingParam: missingParam:
msg: The '**%s**' parameter was needed but not supplied. msg: The '**%s**' parameter was needed but not supplied.
createError:
msg: Failed to create **'%s'**.
exiting:
msg: Exiting with status code **%s**.
validateError:
msg: "An error occurred during validation:\n%s"

76
dist/cli/out.js vendored
View File

@ -6,7 +6,7 @@ Output routines for HackMyResume.
*/ */
(function() { (function() {
var Class, EXTEND, FS, HANDLEBARS, HME, LO, M2C, OutputHandler, PATH, YAML, _, chalk, dbgStyle, pad, printf; var EXTEND, FS, HANDLEBARS, HME, LO, M2C, OutputHandler, PATH, YAML, _, chalk, dbgStyle, pad, printf;
chalk = require('chalk'); chalk = require('chalk');
@ -14,8 +14,6 @@ Output routines for HackMyResume.
_ = require('underscore'); _ = require('underscore');
Class = require('../utils/class.js');
M2C = require('../utils/md2chalk.js'); M2C = require('../utils/md2chalk.js');
PATH = require('path'); PATH = require('path');
@ -39,20 +37,27 @@ Output routines for HackMyResume.
/** A stateful output module. All HMR console output handled here. */ /** A stateful output module. All HMR console output handled here. */
OutputHandler = module.exports = Class.extend({ module.exports = OutputHandler = (function() {
init: function(opts) { function OutputHandler(opts) {
this.init(opts);
return;
}
OutputHandler.prototype.init = function(opts) {
this.opts = EXTEND(true, this.opts || {}, opts); this.opts = EXTEND(true, this.opts || {}, opts);
this.msgs = YAML.load(PATH.join(__dirname, 'msg.yml')).events; this.msgs = YAML.load(PATH.join(__dirname, 'msg.yml')).events;
}, };
log: function(msg) {
OutputHandler.prototype.log = function(msg) {
var finished; var finished;
msg = msg || ''; msg = msg || '';
printf = require('printf'); printf = require('printf');
finished = printf.apply(printf, arguments); finished = printf.apply(printf, arguments);
return this.opts.silent || console.log(finished); return this.opts.silent || console.log(finished);
}, };
"do": function(evt) {
var L, WRAP, info, msg, numFormats, output, rawTpl, sty, style, suffix, template, that, themeName, tot; OutputHandler.prototype["do"] = function(evt) {
var L, WRAP, adj, info, msg, msgs, numFormats, output, rawTpl, sty, style, suffix, template, that, themeName, tot;
that = this; that = this;
L = function() { L = function() {
return that.log.apply(that, arguments); return that.log.apply(that, arguments);
@ -60,8 +65,8 @@ Output routines for HackMyResume.
switch (evt.sub) { switch (evt.sub) {
case HME.begin: case HME.begin:
return this.opts.debug && L(M2C(this.msgs.begin.msg, dbgStyle), evt.cmd.toUpperCase()); return this.opts.debug && L(M2C(this.msgs.begin.msg, dbgStyle), evt.cmd.toUpperCase());
case HME.beforeCreate: case HME.afterCreate:
L(M2C(this.msgs.beforeCreate.msg, 'green'), evt.fmt, evt.file); L(M2C(this.msgs.beforeCreate.msg, evt.isError ? 'red' : 'green'), evt.fmt, evt.file);
break; break;
case HME.beforeTheme: case HME.beforeTheme:
return this.opts.debug && L(M2C(this.msgs.beforeTheme.msg, dbgStyle), evt.theme.toUpperCase()); return this.opts.debug && L(M2C(this.msgs.beforeTheme.msg, dbgStyle), evt.theme.toUpperCase());
@ -127,11 +132,35 @@ Output routines for HackMyResume.
case HME.afterInlineConvert: case HME.afterInlineConvert:
return L(M2C(this.msgs.afterInlineConvert.msg, 'gray', 'white.dim'), evt.file, evt.fmt); return L(M2C(this.msgs.afterInlineConvert.msg, 'gray', 'white.dim'), evt.file, evt.fmt);
case HME.afterValidate: case HME.afterValidate:
style = evt.isValid ? 'green' : 'yellow'; style = 'red';
L(M2C(this.msgs.afterValidate.msg[0], 'white') + chalk[style].bold(evt.isValid ? this.msgs.afterValidate.msg[1] : this.msgs.afterValidate.msg[2]), evt.file, evt.fmt); adj = '';
if (evt.errors) { msgs = this.msgs.afterValidate.msg;
return _.each(evt.errors, function(err, idx) { switch (evt.status) {
return L(chalk.yellow.bold('--> ') + chalk.yellow(err.field.replace('data.', 'resume.').toUpperCase() + ' ' + err.message)); case 'valid':
style = 'green';
adj = msgs[1];
break;
case 'invalid':
style = 'yellow';
adj = msgs[2];
break;
case 'broken':
style = 'red';
adj = msgs[3];
break;
case 'missing':
style = 'red';
adj = msgs[4];
break;
case 'unknown':
style = 'red';
adj = msgs[5];
}
evt.schema = evt.schema.replace('jars', 'JSON Resume').toUpperCase();
L(M2C(msgs[0], 'white') + chalk[style].bold(adj), evt.file, evt.schema);
if (evt.violations) {
_.each(evt.violations, function(err, idx) {
L(chalk.yellow.bold('--> ') + chalk.yellow(err.field.replace('data.', 'resume.').toUpperCase() + ' ' + err.message));
}, this); }, this);
} }
break; break;
@ -142,16 +171,23 @@ Output routines for HackMyResume.
} else { } else {
L(M2C(this.msgs.beforePeek.msg[1], sty), evt.file); L(M2C(this.msgs.beforePeek.msg[1], sty), evt.file);
} }
if (evt.target !== void 0) { if (evt.target !== void 0 && !evt.error) {
return console.dir(evt.target, { return console.dir(evt.target, {
depth: null, depth: null,
colors: true colors: true
}); });
} else if (!evt.error) { } else if (!evt.error) {
return L(M2C(this.msgs.afterPeek.msg, 'yellow'), evt.requested, evt.file); return L(M2C(this.msgs.afterPeek.msg, 'yellow'), evt.requested, evt.file);
} else if (evt.error) {
return L(chalk.red(evt.error.inner.inner));
} }
} }
} };
});
return OutputHandler;
})();
}).call(this); }).call(this);
//# sourceMappingURL=out.js.map

View File

@ -69,3 +69,5 @@ Definition of the AbstractResume class.
module.exports = AbstractResume; module.exports = AbstractResume;
}).call(this); }).call(this);
//# sourceMappingURL=abstract-resume.js.map

View File

@ -58,3 +58,5 @@ Event code definitions.
]; ];
}).call(this); }).call(this);
//# sourceMappingURL=default-formats.js.map

View File

@ -16,3 +16,5 @@ Event code definitions.
}; };
}).call(this); }).call(this);
//# sourceMappingURL=default-options.js.map

View File

@ -33,7 +33,12 @@ Event code definitions.
beforeInlineConvert: 22, beforeInlineConvert: 22,
afterInlineConvert: 23, afterInlineConvert: 23,
beforeValidate: 24, beforeValidate: 24,
afterValidate: 25 afterValidate: 25,
beforeWrite: 26,
afterWrite: 27,
applyTheme: 28
}; };
}).call(this); }).call(this);
//# sourceMappingURL=event-codes.js.map

View File

@ -103,3 +103,5 @@ The HackMyResume date representation.
}; };
}).call(this); }).call(this);
//# sourceMappingURL=fluent-date.js.map

View File

@ -51,17 +51,6 @@ Definition of the FRESHResume class.
} }
/** Initialize the FreshResume from file. */
FreshResume.prototype.open = function(file, opts) {
var raw, ret;
raw = FS.readFileSync(file, 'utf8');
ret = this.parse(raw, opts);
this.imp.file = file;
return ret;
};
/** Initialize the the FreshResume from JSON string data. */ /** Initialize the the FreshResume from JSON string data. */
FreshResume.prototype.parse = function(stringData, opts) { FreshResume.prototype.parse = function(stringData, opts) {
@ -442,7 +431,7 @@ Definition of the FRESHResume class.
*/ */
FreshResume["default"] = function() { FreshResume["default"] = function() {
return new FreshResume().parseJSON(require('fresh-resume-starter')); return new FreshResume().parseJSON(require('fresh-resume-starter').fresh);
}; };
@ -517,3 +506,5 @@ Definition of the FRESHResume class.
module.exports = FreshResume; module.exports = FreshResume;
}).call(this); }).call(this);
//# sourceMappingURL=fresh-resume.js.map

View File

@ -6,7 +6,7 @@ Definition of the FRESHTheme class.
*/ */
(function() { (function() {
var EXTEND, FRESHTheme, FS, HMSTATUS, PATH, READFILES, _, friendlyName, loadExplicit, loadImplicit, loadSafeJson, moment, parsePath, pathExists, validator; var EXTEND, FRESHTheme, FS, HMSTATUS, PATH, READFILES, _, _load, _loadOne, friendlyName, loadSafeJson, moment, parsePath, pathExists, validator;
FS = require('fs'); FS = require('fs');
@ -31,9 +31,7 @@ Definition of the FRESHTheme class.
READFILES = require('recursive-readdir-sync'); READFILES = require('recursive-readdir-sync');
/* /* A representation of a FRESH theme asset.
The FRESHTheme class is a representation of a FRESH theme
asset. See also: JRSTheme.
@class FRESHTheme @class FRESHTheme
*/ */
@ -41,9 +39,7 @@ Definition of the FRESHTheme class.
function FRESHTheme() {} function FRESHTheme() {}
/* /* Open and parse the specified theme. */
Open and parse the specified theme.
*/
FRESHTheme.prototype.open = function(themeFolder) { FRESHTheme.prototype.open = function(themeFolder) {
var cached, formatsHash, pathInfo, that, themeFile, themeInfo; var cached, formatsHash, pathInfo, that, themeFile, themeInfo;
@ -71,12 +67,7 @@ Definition of the FRESHTheme class.
return formatsHash[key] = cached[th].getFormat(key); return formatsHash[key] = cached[th].getFormat(key);
}); });
} }
if (!!this.formats) { formatsHash = _load.call(this, formatsHash);
formatsHash = loadExplicit.call(this, formatsHash);
this.explicit = true;
} else {
formatsHash = loadImplicit.call(this, formatsHash);
}
this.formats = formatsHash; this.formats = formatsHash;
this.name = parsePath(this.folder).name; this.name = parsePath(this.folder).name;
return this; return this;
@ -101,63 +92,17 @@ Definition of the FRESHTheme class.
})(); })();
/* Load the theme implicitly, by scanning the theme folder for files. TODO: /* Load and parse theme source files. */
Refactor duplicated code with loadExplicit.
*/
loadImplicit = function(formatsHash) { _load = function(formatsHash) {
var fmts, major, that, tplFolder; var copyOnly, fmts, major, that, tplFolder;
that = this; that = this;
major = false; major = false;
tplFolder = PATH.join(this.folder, 'src'); tplFolder = PATH.join(this.folder, 'src');
copyOnly = ['.ttf', '.otf', '.png', '.jpg', '.jpeg', '.pdf'];
fmts = READFILES(tplFolder).map(function(absPath) { fmts = READFILES(tplFolder).map(function(absPath) {
var idx, isMajor, obj, outFmt, pathInfo, portion, reg, res; return _loadOne.call(this, absPath, formatsHash, tplFolder);
pathInfo = parsePath(absPath); }, this);
outFmt = '';
isMajor = false;
portion = pathInfo.dirname.replace(tplFolder, '');
if (portion && portion.trim()) {
if (portion[1] === '_') {
return;
}
reg = /^(?:\/|\\)(html|latex|doc|pdf|png|partials)(?:\/|\\)?/ig;
res = reg.exec(portion);
if (res) {
if (res[1] !== 'partials') {
outFmt = res[1];
} else {
that.partials = that.partials || [];
that.partials.push({
name: pathInfo.name,
path: absPath
});
return null;
}
}
}
if (!outFmt) {
idx = pathInfo.name.lastIndexOf('-');
outFmt = idx === -1 ? pathInfo.name : pathInfo.name.substr(idx + 1);
isMajor = true;
}
formatsHash[outFmt] = formatsHash[outFmt] || {
outFormat: outFmt,
files: []
};
obj = {
action: 'transform',
path: absPath,
major: isMajor,
orgPath: PATH.relative(tplFolder, absPath),
ext: pathInfo.extname.slice(1),
title: friendlyName(outFmt),
pre: outFmt,
data: FS.readFileSync(absPath, 'utf8'),
css: null
};
formatsHash[outFmt].files.push(obj);
return obj;
});
this.cssFiles = fmts.filter(function(fmt) { this.cssFiles = fmts.filter(function(fmt) {
return fmt && (fmt.ext === 'css'); return fmt && (fmt.ext === 'css');
}); });
@ -183,89 +128,93 @@ Definition of the FRESHTheme class.
}; };
/* /* Load a single theme file. */
Load the theme explicitly, by following the 'formats' hash
in the theme's JSON settings file.
*/
loadExplicit = function(formatsHash) { _loadOne = function(absPath, formatsHash, tplFolder) {
var act, fmts, that, tplFolder; var absPathSafe, act, defFormats, idx, isPrimary, obj, outFmt, pathInfo, portion, ref, ref1, reg, res;
tplFolder = PATH.join(this.folder, 'src'); pathInfo = parsePath(absPath);
act = null; absPathSafe = absPath.trim().toLowerCase();
that = this; outFmt = '';
fmts = READFILES(tplFolder).map(function(absPath) { act = 'copy';
var absPathSafe, idx, obj, outFmt, pathInfo, portion, reg, res; isPrimary = false;
act = null; if (this.explicit) {
pathInfo = parsePath(absPath); outFmt = _.find(Object.keys(this.formats), function(fmtKey) {
absPathSafe = absPath.trim().toLowerCase();
outFmt = _.find(Object.keys(that.formats), function(fmtKey) {
var fmtVal; var fmtVal;
fmtVal = that.formats[fmtKey]; fmtVal = this.formats[fmtKey];
return _.some(fmtVal.transform, function(fpath) { return _.some(fmtVal.transform, function(fpath) {
var absPathB; var absPathB;
absPathB = PATH.join(that.folder, fpath).trim().toLowerCase(); absPathB = PATH.join(this.folder, fpath).trim().toLowerCase();
return absPathB === absPathSafe; return absPathB === absPathSafe;
}); }, this);
}); }, this);
if (outFmt) { if (outFmt) {
act = 'transform'; act = 'transform';
} }
if (!outFmt) { }
portion = pathInfo.dirname.replace(tplFolder, ''); if (!outFmt) {
if (portion && portion.trim()) { portion = pathInfo.dirname.replace(tplFolder, '');
reg = /^(?:\/|\\)(html|latex|doc|pdf)(?:\/|\\)?/ig; if (portion && portion.trim()) {
res = reg.exec(portion); if (portion[1] === '_') {
res && (outFmt = res[1]); return;
}
reg = /^(?:\/|\\)(html|latex|doc|pdf|png|partials)(?:\/|\\)?/ig;
res = reg.exec(portion);
if (res) {
if (res[1] !== 'partials') {
outFmt = res[1];
if (!this.explicit) {
act = 'transform';
}
} else {
this.partials = this.partials || [];
this.partials.push({
name: pathInfo.name,
path: absPath
});
return null;
}
} }
} }
if (!outFmt) { }
idx = pathInfo.name.lastIndexOf('-'); if (!outFmt) {
outFmt = idx === -1 ? pathInfo.name : pathInfo.name.substr(idx + 1); idx = pathInfo.name.lastIndexOf('-');
outFmt = idx === -1 ? pathInfo.name : pathInfo.name.substr(idx + 1);
if (!this.explicit) {
act = 'transform';
} }
formatsHash[outFmt] = formatsHash[outFmt] || { defFormats = require('./default-formats');
outFormat: outFmt, isPrimary = _.some(defFormats, function(form) {
files: [], return form.name === outFmt && pathInfo.extname !== '.css';
symLinks: that.formats[outFmt].symLinks
};
obj = {
action: act,
orgPath: PATH.relative(that.folder, absPath),
path: absPath,
ext: pathInfo.extname.slice(1),
title: friendlyName(outFmt),
pre: outFmt,
data: FS.readFileSync(absPath, 'utf8'),
css: null
};
formatsHash[outFmt].files.push(obj);
return obj;
});
this.cssFiles = fmts.filter(function(fmt) {
return fmt.ext === 'css';
});
this.cssFiles.forEach(function(cssf) {
var idx;
idx = _.findIndex(fmts, function(fmt) {
return fmt.pre === cssf.pre && fmt.ext === 'html';
}); });
fmts[idx].css = cssf.data; }
return fmts[idx].cssPath = cssf.path; formatsHash[outFmt] = formatsHash[outFmt] || {
}); outFormat: outFmt,
fmts = fmts.filter(function(fmt) { files: []
return fmt.ext !== 'css'; };
}); if ((ref = this.formats) != null ? (ref1 = ref[outFmt]) != null ? ref1.symLinks : void 0 : void 0) {
return formatsHash; formatsHash[outFmt].symLinks = this.formats[outFmt].symLinks;
}
obj = {
action: act,
primary: isPrimary,
path: absPath,
orgPath: PATH.relative(tplFolder, absPath),
ext: pathInfo.extname.slice(1),
title: friendlyName(outFmt),
pre: outFmt,
data: FS.readFileSync(absPath, 'utf8'),
css: null
};
formatsHash[outFmt].files.push(obj);
return obj;
}; };
/* /* Return a more friendly name for certain formats. */
Return a more friendly name for certain formats.
TODO: Refactor
*/
friendlyName = function(val) { friendlyName = function(val) {
var friendly; var friendly;
val = val.trim().toLowerCase(); val = (val && val.trim().toLowerCase()) || '';
friendly = { friendly = {
yml: 'yaml', yml: 'yaml',
md: 'markdown', md: 'markdown',
@ -277,3 +226,5 @@ Definition of the FRESHTheme class.
module.exports = FRESHTheme; module.exports = FRESHTheme;
}).call(this); }).call(this);
//# sourceMappingURL=fresh-theme.js.map

View File

@ -36,7 +36,7 @@ Definition of the JRSResume class.
*/ */
JRSResume = (function(superClass) { JRSResume = (function(superClass) {
var clear, format; var clear;
extend1(JRSResume, superClass); extend1(JRSResume, superClass);
@ -45,17 +45,6 @@ Definition of the JRSResume class.
} }
/** Initialize the JSResume from file. */
JRSResume.prototype.open = function(file, opts) {
var raw, ret;
raw = FS.readFileSync(file, 'utf8');
ret = this.parse(raw, opts);
this.imp.file = file;
return ret;
};
/** Initialize the the JSResume from string. */ /** Initialize the the JSResume from string. */
JRSResume.prototype.parse = function(stringData, opts) { JRSResume.prototype.parse = function(stringData, opts) {
@ -146,7 +135,7 @@ Definition of the JRSResume class.
/** Return the resume format. */ /** Return the resume format. */
format = function() { JRSResume.prototype.format = function() {
return 'JRS'; return 'JRS';
}; };
@ -364,7 +353,7 @@ Definition of the JRSResume class.
/** Get the default (empty) sheet. */ /** Get the default (empty) sheet. */
JRSResume["default"] = function() { JRSResume["default"] = function() {
return new JRSResume().open(PATH.join(__dirname, 'empty-jrs.json'), 'Empty'); return new JRSResume().parseJSON(require('fresh-resume-starter').jrs);
}; };
@ -429,3 +418,5 @@ Definition of the JRSResume class.
module.exports = JRSResume; module.exports = JRSResume;
}).call(this); }).call(this);
//# sourceMappingURL=jrs-resume.js.map

View File

@ -49,7 +49,7 @@ Definition of the JRSTheme class.
{ {
action: 'transform', action: 'transform',
render: this.render, render: this.render,
major: true, primary: true,
ext: 'html', ext: 'html',
css: null css: null
} }
@ -61,7 +61,7 @@ Definition of the JRSTheme class.
{ {
action: 'transform', action: 'transform',
render: this.render, render: this.render,
major: true, primary: true,
ext: 'pdf', ext: 'pdf',
css: null css: null
} }
@ -103,3 +103,5 @@ Definition of the JRSTheme class.
module.exports = JRSTheme; module.exports = JRSTheme;
}).call(this); }).call(this);
//# sourceMappingURL=jrs-theme.js.map

View File

@ -83,7 +83,7 @@ Definition of the ResumeFactory class.
}; };
_parse = function(fileName, opts, eve) { _parse = function(fileName, opts, eve) {
var ex, orgFormat, rawData, ret; var orgFormat, rawData, ret;
rawData = null; rawData = null;
try { try {
eve && eve.stat(HME.beforeRead, { eve && eve.stat(HME.beforeRead, {
@ -108,20 +108,15 @@ Definition of the ResumeFactory class.
}); });
return ret; return ret;
} catch (_error) { } catch (_error) {
ex = { return {
fluenterror: rawData ? HACKMYSTATUS.parseError : HACKMYSTATUS.readError, fluenterror: rawData ? HACKMYSTATUS.parseError : HACKMYSTATUS.readError,
inner: _error, inner: _error,
raw: rawData, raw: rawData,
file: fileName, file: fileName
shouldExit: false
}; };
opts.quit && (ex.quit = true);
eve && eve.err(ex.fluenterror, ex);
if (opts["throw"]) {
throw ex;
}
return ex;
} }
}; };
}).call(this); }).call(this);
//# sourceMappingURL=resume-factory.js.map

View File

@ -16,7 +16,7 @@ Status codes for HackMyResume.
resumeNotFoundAlt: 6, resumeNotFoundAlt: 6,
inputOutputParity: 7, inputOutputParity: 7,
createNameMissing: 8, createNameMissing: 8,
pdfgeneration: 9, pdfGeneration: 9,
missingPackageJSON: 10, missingPackageJSON: 10,
invalid: 11, invalid: 11,
invalidFormat: 12, invalidFormat: 12,
@ -31,7 +31,11 @@ Status codes for HackMyResume.
compileTemplate: 21, compileTemplate: 21,
themeLoad: 22, themeLoad: 22,
invalidParamCount: 23, invalidParamCount: 23,
missingParam: 24 missingParam: 24,
createError: 25,
validateError: 26
}; };
}).call(this); }).call(this);
//# sourceMappingURL=status-codes.js.map

View File

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

View File

@ -1,12 +1,14 @@
/** /**
Definition of the HTMLGenerator class. Definition of the HTMLGenerator class.
@module generators/html-generator
@license MIT. See LICENSE.md for details. @license MIT. See LICENSE.md for details.
@module html-generator.js
*/ */
(function() { (function() {
var FS, HTML, HtmlGenerator, PATH, TemplateGenerator; var FS, HTML, HtmlGenerator, PATH, TemplateGenerator,
extend = 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;
TemplateGenerator = require('./template-generator'); TemplateGenerator = require('./template-generator');
@ -18,16 +20,20 @@ Definition of the HTMLGenerator class.
require('string.prototype.endswith'); require('string.prototype.endswith');
HtmlGenerator = module.exports = TemplateGenerator.extend({ module.exports = HtmlGenerator = (function(superClass) {
init: function() { extend(HtmlGenerator, superClass);
return this._super('html');
}, function HtmlGenerator() {
HtmlGenerator.__super__.constructor.call(this, 'html');
}
/** /**
Copy satellite CSS files to the destination and optionally pretty-print Copy satellite CSS files to the destination and optionally pretty-print
the HTML resume prior to saving. the HTML resume prior to saving.
*/ */
onBeforeSave: function(info) {
HtmlGenerator.prototype.onBeforeSave = function(info) {
if (info.outputFile.endsWith('.css')) { if (info.outputFile.endsWith('.css')) {
return info.mk; return info.mk;
} }
@ -36,7 +42,12 @@ Definition of the HTMLGenerator class.
} else { } else {
return info.mk; return info.mk;
} }
} };
});
return HtmlGenerator;
})(TemplateGenerator);
}).call(this); }).call(this);
//# sourceMappingURL=html-generator.js.map

View File

@ -1,25 +1,29 @@
/** /**
Definition of the HtmlPdfCLIGenerator class. Definition of the HtmlPdfCLIGenerator class.
@module html-pdf-generator.js @module generators/html-pdf-generator.js
@license MIT. See LICENSE.md for details. @license MIT. See LICENSE.md for details.
*/ */
(function() { (function() {
var FS, HTML, HtmlPdfCLIGenerator, PATH, SLASH, SPAWN, TemplateGenerator, engines; var FS, HMSTATUS, HtmlPdfCLIGenerator, PATH, SLASH, SPAWN, TemplateGenerator, _, engines,
extend = 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;
TemplateGenerator = require('./template-generator'); TemplateGenerator = require('./template-generator');
FS = require('fs-extra'); FS = require('fs-extra');
HTML = require('html');
PATH = require('path'); PATH = require('path');
SPAWN = require('../utils/safe-spawn');
SLASH = require('slash'); SLASH = require('slash');
_ = require('underscore');
HMSTATUS = require('../core/status-codes');
SPAWN = require('../utils/safe-spawn');
/** /**
An HTML-driven PDF resume generator for HackMyResume. Talks to Phantom, An HTML-driven PDF resume generator for HackMyResume. Talks to Phantom,
@ -27,39 +31,51 @@ Definition of the HtmlPdfCLIGenerator class.
If an engine isn't installed for a particular platform, error out gracefully. If an engine isn't installed for a particular platform, error out gracefully.
*/ */
HtmlPdfCLIGenerator = module.exports = TemplateGenerator.extend({ module.exports = HtmlPdfCLIGenerator = (function(superClass) {
init: function() { extend(HtmlPdfCLIGenerator, superClass);
return this._super('pdf', 'html');
}, function HtmlPdfCLIGenerator() {
HtmlPdfCLIGenerator.__super__.constructor.call(this, 'pdf', 'html');
}
/** Generate the binary PDF. */ /** Generate the binary PDF. */
onBeforeSave: function(info) {
var ex, safe_eng; HtmlPdfCLIGenerator.prototype.onBeforeSave = function(info) {
try { var safe_eng;
safe_eng = info.opts.pdf || 'wkhtmltopdf'; if (info.ext !== 'html' && info.ext !== 'pdf') {
if (safe_eng !== 'none') { return info.mk;
engines[safe_eng].call(this, info.mk, info.outputFile); }
return null; safe_eng = info.opts.pdf || 'wkhtmltopdf';
} if (safe_eng === 'phantom') {
} catch (_error) { safe_eng = 'phantomjs';
ex = _error; }
if (ex.inner && ex.inner.code === 'ENOENT') { if (_.has(engines, safe_eng)) {
throw { this.errHandler = info.opts.errHandler;
fluenterror: this.codes.notOnPath, engines[safe_eng].call(this, info.mk, info.outputFile, this.onError);
inner: ex.inner, return null;
engine: ex.cmd, }
stack: ex.inner && ex.inner.stack };
};
} else {
throw { /* Low-level error callback for spawn(). May be called after HMR process
fluenterror: this.codes.pdfGeneration, termination, so object references may not be valid here. That's okay; if
inner: ex, the references are invalid, the error was already logged. We could use
stack: ex.stack spawn-watch here but that causes issues on legacy Node.js.
}; */
HtmlPdfCLIGenerator.prototype.onError = function(ex, param) {
var ref;
if ((ref = param.errHandler) != null) {
if (typeof ref.err === "function") {
ref.err(HMSTATUS.pdfGeneration, ex);
} }
} }
} };
});
return HtmlPdfCLIGenerator;
})(TemplateGenerator);
engines = { engines = {
@ -70,11 +86,11 @@ Definition of the HtmlPdfCLIGenerator class.
TODO: If HTML generation has run, reuse that output TODO: If HTML generation has run, reuse that output
TODO: Local web server to ease wkhtmltopdf rendering TODO: Local web server to ease wkhtmltopdf rendering
*/ */
wkhtmltopdf: function(markup, fOut) { wkhtmltopdf: function(markup, fOut, on_error) {
var info, tempFile; var tempFile;
tempFile = fOut.replace(/\.pdf$/i, '.pdf.html'); tempFile = fOut.replace(/\.pdf$/i, '.pdf.html');
FS.writeFileSync(tempFile, markup, 'utf8'); FS.writeFileSync(tempFile, markup, 'utf8');
return info = SPAWN('wkhtmltopdf', [tempFile, fOut]); SPAWN('wkhtmltopdf', [tempFile, fOut], false, on_error, this);
}, },
/** /**
@ -84,15 +100,18 @@ Definition of the HtmlPdfCLIGenerator class.
TODO: If HTML generation has run, reuse that output TODO: If HTML generation has run, reuse that output
TODO: Local web server to ease Phantom rendering TODO: Local web server to ease Phantom rendering
*/ */
phantom: function(markup, fOut) { phantomjs: function(markup, fOut, on_error) {
var destPath, info, scriptPath, sourcePath, tempFile; var destPath, scriptPath, sourcePath, tempFile;
tempFile = fOut.replace(/\.pdf$/i, '.pdf.html'); tempFile = fOut.replace(/\.pdf$/i, '.pdf.html');
FS.writeFileSync(tempFile, markup, 'utf8'); FS.writeFileSync(tempFile, markup, 'utf8');
scriptPath = SLASH(PATH.relative(process.cwd(), PATH.resolve(__dirname, '../utils/rasterize.js'))); scriptPath = PATH.relative(process.cwd(), PATH.resolve(__dirname, '../utils/rasterize.js'));
scriptPath = SLASH(scriptPath);
sourcePath = SLASH(PATH.relative(process.cwd(), tempFile)); sourcePath = SLASH(PATH.relative(process.cwd(), tempFile));
destPath = SLASH(PATH.relative(process.cwd(), fOut)); destPath = SLASH(PATH.relative(process.cwd(), fOut));
return info = SPAWN('phantomjs', [scriptPath, sourcePath, destPath]); SPAWN('phantomjs', [scriptPath, sourcePath, destPath], false, on_error, this);
} }
}; };
}).call(this); }).call(this);
//# sourceMappingURL=html-pdf-cli-generator.js.map

View File

@ -1,12 +1,14 @@
/** /**
Definition of the HtmlPngGenerator class. Definition of the HtmlPngGenerator class.
@module generators/html-png-generator
@license MIT. See LICENSE.MD for details. @license MIT. See LICENSE.MD for details.
@module html-png-generator.js
*/ */
(function() { (function() {
var FS, HTML, HtmlPngGenerator, PATH, SLASH, SPAWN, TemplateGenerator, phantom; var FS, HTML, HtmlPngGenerator, PATH, SLASH, SPAWN, TemplateGenerator, phantom,
extend = 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;
TemplateGenerator = require('./template-generator'); TemplateGenerator = require('./template-generator');
@ -25,12 +27,16 @@ Definition of the HtmlPngGenerator class.
An HTML-based PNG resume generator for HackMyResume. An HTML-based PNG resume generator for HackMyResume.
*/ */
HtmlPngGenerator = module.exports = TemplateGenerator.extend({ module.exports = HtmlPngGenerator = (function(superClass) {
init: function() { extend(HtmlPngGenerator, superClass);
return this._super('png', 'html');
}, function HtmlPngGenerator() {
invoke: function(rez, themeMarkup, cssInfo, opts) {}, HtmlPngGenerator.__super__.constructor.call(this, 'png', 'html');
generate: function(rez, f, opts) { }
HtmlPngGenerator.prototype.invoke = function(rez, themeMarkup, cssInfo, opts) {};
HtmlPngGenerator.prototype.generate = function(rez, f, opts) {
var htmlFile, htmlResults; var htmlFile, htmlResults;
htmlResults = opts.targets.filter(function(t) { htmlResults = opts.targets.filter(function(t) {
return t.fmt.outFormat === 'html'; return t.fmt.outFormat === 'html';
@ -39,8 +45,11 @@ Definition of the HtmlPngGenerator class.
return fl.info.ext === 'html'; return fl.info.ext === 'html';
}); });
phantom(htmlFile[0].data, f); phantom(htmlFile[0].data, f);
} };
});
return HtmlPngGenerator;
})(TemplateGenerator);
/** /**
@ -62,3 +71,5 @@ Definition of the HtmlPngGenerator class.
}; };
}).call(this); }).call(this);
//# sourceMappingURL=html-png-generator.js.map

View File

@ -1,12 +1,14 @@
/** /**
Definition of the JsonGenerator class. Definition of the JsonGenerator class.
@license MIT. See LICENSE.md for details.
@module generators/json-generator @module generators/json-generator
@license MIT. See LICENSE.md for details.
*/ */
(function() { (function() {
var BaseGenerator, FS, JsonGenerator, _; var BaseGenerator, FJCV, FS, JsonGenerator, _,
extend = 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;
BaseGenerator = require('./base-generator'); BaseGenerator = require('./base-generator');
@ -14,32 +16,32 @@ Definition of the JsonGenerator class.
_ = require('underscore'); _ = require('underscore');
FJCV = require('fresh-jrs-converter');
/**
The JsonGenerator generates a JSON resume directly.
*/
JsonGenerator = module.exports = BaseGenerator.extend({ /** The JsonGenerator generates a FRESH or JRS resume as an output. */
init: function() {
return this._super('json'); module.exports = JsonGenerator = (function(superClass) {
}, extend(JsonGenerator, superClass);
keys: ['imp', 'warnings', 'computed', 'filt', 'ctrl', 'index', 'safeStartDate', 'safeEndDate', 'safeDate', 'safeReleaseDate', 'result', 'isModified', 'htmlPreview', 'safe'],
invoke: function(rez) { function JsonGenerator() {
var replacer; JsonGenerator.__super__.constructor.call(this, 'json');
replacer = function(key, value) {
if (_.some(this.keys, function(val) {
return key.trim() === val;
})) {
return void 0;
} else {
return value;
}
};
return JSON.stringify(rez, replacer, 2);
},
generate: function(rez, f) {
FS.writeFileSync(f, this.invoke(rez), 'utf8');
} }
});
JsonGenerator.prototype.invoke = function(rez) {
var altRez;
altRez = FJCV['to' + (rez.format() === 'FRESH' ? 'JRS' : 'FRESH')](rez);
return altRez = FJCV.toSTRING(altRez);
};
JsonGenerator.prototype.generate = function(rez, f) {
FS.writeFileSync(f, this.invoke(rez), 'utf8');
};
return JsonGenerator;
})(BaseGenerator);
}).call(this); }).call(this);
//# sourceMappingURL=json-generator.js.map

View File

@ -1,12 +1,14 @@
/** /**
Definition of the JsonYamlGenerator class. Definition of the JsonYamlGenerator class.
@module json-yaml-generator.js @module generators/json-yaml-generator
@license MIT. See LICENSE.md for details. @license MIT. See LICENSE.md for details.
*/ */
(function() { (function() {
var BaseGenerator, FS, JsonYamlGenerator, YAML; var BaseGenerator, FS, JsonYamlGenerator, YAML,
extend = 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;
BaseGenerator = require('./base-generator'); BaseGenerator = require('./base-generator');
@ -21,18 +23,28 @@ Definition of the JsonYamlGenerator class.
also YamlGenerator (yaml-generator.js). also YamlGenerator (yaml-generator.js).
*/ */
JsonYamlGenerator = module.exports = BaseGenerator.extend({ module.exports = JsonYamlGenerator = (function(superClass) {
init: function() { extend(JsonYamlGenerator, superClass);
return this._super('yml');
}, function JsonYamlGenerator() {
invoke: function(rez, themeMarkup, cssInfo, opts) { JsonYamlGenerator.__super__.constructor.call(this, 'yml');
}
JsonYamlGenerator.prototype.invoke = function(rez, themeMarkup, cssInfo, opts) {
return YAML.stringify(JSON.parse(rez.stringify()), Infinity, 2); return YAML.stringify(JSON.parse(rez.stringify()), Infinity, 2);
}, };
generate: function(rez, f, opts) {
JsonYamlGenerator.prototype.generate = function(rez, f, opts) {
var data; var data;
data = YAML.stringify(JSON.parse(rez.stringify()), Infinity, 2); data = YAML.stringify(JSON.parse(rez.stringify()), Infinity, 2);
return FS.writeFileSync(f, data, 'utf8'); FS.writeFileSync(f, data, 'utf8');
} return data;
}); };
return JsonYamlGenerator;
})(BaseGenerator);
}).call(this); }).call(this);
//# sourceMappingURL=json-yaml-generator.js.map

View File

@ -1,12 +1,14 @@
/** /**
Definition of the LaTeXGenerator class. Definition of the LaTeXGenerator class.
@license MIT. See LICENSE.md for details.
@module generators/latex-generator @module generators/latex-generator
@license MIT. See LICENSE.md for details.
*/ */
(function() { (function() {
var LaTeXGenerator, TemplateGenerator; var LaTeXGenerator, TemplateGenerator,
extend = 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;
TemplateGenerator = require('./template-generator'); TemplateGenerator = require('./template-generator');
@ -15,10 +17,17 @@ Definition of the LaTeXGenerator class.
LaTeXGenerator generates a LaTeX resume via TemplateGenerator. LaTeXGenerator generates a LaTeX resume via TemplateGenerator.
*/ */
LaTeXGenerator = module.exports = TemplateGenerator.extend({ module.exports = LaTeXGenerator = (function(superClass) {
init: function() { extend(LaTeXGenerator, superClass);
return this._super('latex', 'tex');
function LaTeXGenerator() {
LaTeXGenerator.__super__.constructor.call(this, 'latex', 'tex');
} }
});
return LaTeXGenerator;
})(TemplateGenerator);
}).call(this); }).call(this);
//# sourceMappingURL=latex-generator.js.map

View File

@ -1,12 +1,14 @@
/** /**
Definition of the MarkdownGenerator class. Definition of the MarkdownGenerator class.
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk. @module generators/markdown-generator
@module markdown-generator.js @license MIT. See LICENSE.md for details.
*/ */
(function() { (function() {
var MarkdownGenerator, TemplateGenerator; var MarkdownGenerator, TemplateGenerator,
extend = 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;
TemplateGenerator = require('./template-generator'); TemplateGenerator = require('./template-generator');
@ -15,10 +17,17 @@ Definition of the MarkdownGenerator class.
MarkdownGenerator generates a Markdown-formatted resume via TemplateGenerator. MarkdownGenerator generates a Markdown-formatted resume via TemplateGenerator.
*/ */
MarkdownGenerator = module.exports = TemplateGenerator.extend({ module.exports = MarkdownGenerator = (function(superClass) {
init: function() { extend(MarkdownGenerator, superClass);
return this._super('md', 'txt');
function MarkdownGenerator() {
MarkdownGenerator.__super__.constructor.call(this, 'md', 'txt');
} }
});
return MarkdownGenerator;
})(TemplateGenerator);
}).call(this); }).call(this);
//# sourceMappingURL=markdown-generator.js.map

View File

@ -1,12 +1,14 @@
/** /**
Definition of the TemplateGenerator class. TODO: Refactor Definition of the TemplateGenerator class. TODO: Refactor
@module generators/template-generator
@license MIT. See LICENSE.md for details. @license MIT. See LICENSE.md for details.
@module template-generator.js
*/ */
(function() { (function() {
var BaseGenerator, EXTEND, FRESHTheme, FS, JRSTheme, MD, MKDIRP, PATH, TemplateGenerator, XML, _, _defaultOpts, _reg, freeze, parsePath, unfreeze; var BaseGenerator, EXTEND, FRESHTheme, FS, JRSTheme, MD, MKDIRP, PATH, TemplateGenerator, XML, _, _defaultOpts, _reg, createSymLinks, freeze, parsePath, unfreeze,
extend = 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-extra'); FS = require('fs-extra');
@ -38,16 +40,21 @@ Definition of the TemplateGenerator class. TODO: Refactor
@class TemplateGenerator @class TemplateGenerator
*/ */
TemplateGenerator = module.exports = BaseGenerator.extend({ module.exports = TemplateGenerator = (function(superClass) {
extend(TemplateGenerator, superClass);
/** Constructor. Set the output format and template format for this /** Constructor. Set the output format and template format for this
generator. Will usually be called by a derived generator such as generator. Will usually be called by a derived generator such as
HTMLGenerator or MarkdownGenerator. HTMLGenerator or MarkdownGenerator.
*/ */
init: function(outputFormat, templateFormat, cssFile) {
this._super(outputFormat); function TemplateGenerator(outputFormat, templateFormat, cssFile) {
TemplateGenerator.__super__.constructor.call(this, outputFormat);
this.tplFormat = templateFormat || outputFormat; this.tplFormat = templateFormat || outputFormat;
}, return;
}
/** Generate a resume using string-based inputs and outputs without touching /** Generate a resume using string-based inputs and outputs without touching
the filesystem. the filesystem.
@ -57,7 +64,8 @@ Definition of the TemplateGenerator class. TODO: Refactor
@returns {Array} An array of objects representing the generated output @returns {Array} An array of objects representing the generated output
files. files.
*/ */
invoke: function(rez, opts) {
TemplateGenerator.prototype.invoke = function(rez, opts) {
var curFmt, results; var curFmt, results;
opts = opts ? (this.opts = EXTEND(true, {}, _defaultOpts, opts)) : this.opts; opts = opts ? (this.opts = EXTEND(true, {}, _defaultOpts, opts)) : this.opts;
curFmt = opts.themeObj.getFormat(this.format); curFmt = opts.themeObj.getFormat(this.format);
@ -66,11 +74,18 @@ Definition of the TemplateGenerator class. TODO: Refactor
}); });
results = curFmt.files.map(function(tplInfo, idx) { results = curFmt.files.map(function(tplInfo, idx) {
var trx; var trx;
trx = this.single(rez, tplInfo.data, this.format, opts, opts.themeObj, curFmt); if (tplInfo.action === 'transform') {
if (tplInfo.ext === 'css') { trx = this.transform(rez, tplInfo.data, this.format, opts, opts.themeObj, curFmt);
curFmt.files[idx].data = trx; if (tplInfo.ext === 'css') {
curFmt.files[idx].data = trx;
} else {
tplInfo.ext === 'html';
}
} else { } else {
tplInfo.ext === 'html';
}
if (typeof opts.onTransform === "function") {
opts.onTransform(tplInfo);
} }
return { return {
info: tplInfo, info: tplInfo,
@ -80,7 +95,8 @@ Definition of the TemplateGenerator class. TODO: Refactor
return { return {
files: results files: results
}; };
}, };
/** Generate a resume using file-based inputs and outputs. Requires access /** Generate a resume using file-based inputs and outputs. Requires access
to the local filesystem. to the local filesystem.
@ -89,33 +105,44 @@ Definition of the TemplateGenerator class. TODO: Refactor
@param f Full path to the output resume file to generate. @param f Full path to the output resume file to generate.
@param opts Generator options. @param opts Generator options.
*/ */
generate: function(rez, f, opts) {
TemplateGenerator.prototype.generate = function(rez, f, opts) {
var curFmt, genInfo, outFolder; var curFmt, genInfo, outFolder;
this.opts = EXTEND(true, {}, _defaultOpts, opts); this.opts = EXTEND(true, {}, _defaultOpts, opts);
genInfo = this.invoke(rez, null); genInfo = this.invoke(rez, null);
outFolder = parsePath(f).dirname; outFolder = parsePath(f).dirname;
curFmt = opts.themeObj.getFormat(this.format); curFmt = opts.themeObj.getFormat(this.format);
genInfo.files.forEach(function(file) { genInfo.files.forEach(function(file) {
var fileName, thisFilePath; var thisFilePath;
file.info.orgPath = file.info.orgPath || ''; file.info.orgPath = file.info.orgPath || '';
thisFilePath = PATH.join(outFolder, file.info.orgPath); thisFilePath = file.info.primary ? f : PATH.join(outFolder, file.info.orgPath);
if (this.onBeforeSave) { if (file.info.action !== 'copy' && this.onBeforeSave) {
file.data = this.onBeforeSave({ file.data = this.onBeforeSave({
theme: opts.themeObj, theme: opts.themeObj,
outputFile: file.info.major ? f : thisFilePath, outputFile: thisFilePath,
mk: file.data, mk: file.data,
opts: this.opts opts: this.opts,
ext: file.info.ext
}); });
if (!file.data) { if (!file.data) {
return; return;
} }
} }
fileName = file.info.major ? f : thisFilePath; if (typeof opts.beforeWrite === "function") {
MKDIRP.sync(PATH.dirname(fileName)); opts.beforeWrite(thisFilePath);
FS.writeFileSync(fileName, file.data, { }
encoding: 'utf8', MKDIRP.sync(PATH.dirname(thisFilePath));
flags: 'w' if (file.info.action !== 'copy') {
}); FS.writeFileSync(thisFilePath, file.data, {
encoding: 'utf8',
flags: 'w'
});
} else {
FS.copySync(file.info.path, thisFilePath);
}
if (typeof opts.afterWrite === "function") {
opts.afterWrite(thisFilePath);
}
if (this.onAfterSave) { if (this.onAfterSave) {
return this.onAfterSave({ return this.onAfterSave({
outputFile: fileName, outputFile: fileName,
@ -124,19 +151,10 @@ Definition of the TemplateGenerator class. TODO: Refactor
}); });
} }
}, this); }, this);
if (curFmt.symLinks) { createSymLinks(curFmt, outFolder);
Object.keys(curFmt.symLinks).forEach(function(loc) {
var absLoc, absTarg, ref, type;
absLoc = PATH.join(outFolder, loc);
absTarg = PATH.join(PATH.dirname(absLoc), curFmt.symLinks[loc]);
type = (ref = parsePath(absLoc).extname) != null ? ref : {
'file': 'junction'
};
return FS.symlinkSync(absTarg, absLoc, type);
});
}
return genInfo; return genInfo;
}, };
/** Perform a single resume resume transformation using string-based inputs /** Perform a single resume resume transformation using string-based inputs
and outputs without touching the local file system. and outputs without touching the local file system.
@ -146,7 +164,8 @@ Definition of the TemplateGenerator class. TODO: Refactor
@param cssInfo Needs to be refactored. @param cssInfo Needs to be refactored.
@param opts Options and passthrough data. @param opts Options and passthrough data.
*/ */
single: function(json, jst, format, opts, theme, curFmt) {
TemplateGenerator.prototype.transform = function(json, jst, format, opts, theme, curFmt) {
var eng, result; var eng, result;
if (this.opts.freezeBreaks) { if (this.opts.freezeBreaks) {
jst = freeze(jst); jst = freeze(jst);
@ -157,13 +176,37 @@ Definition of the TemplateGenerator class. TODO: Refactor
result = unfreeze(result); result = unfreeze(result);
} }
return result; return result;
};
return TemplateGenerator;
})(BaseGenerator);
createSymLinks = function(curFmt, outFolder) {
if (curFmt.symLinks) {
Object.keys(curFmt.symLinks).forEach(function(loc) {
var absLoc, absTarg, succeeded, type;
absLoc = PATH.join(outFolder, loc);
absTarg = PATH.join(PATH.dirname(absLoc), curFmt.symLinks[loc]);
type = parsePath(absLoc).extname ? 'file' : 'junction';
try {
return FS.symlinkSync(absTarg, absLoc, type);
} catch (_error) {
succeeded = false;
if (_error.code === 'EEXIST') {
FS.unlinkSync(absLoc);
try {
FS.symlinkSync(absTarg, absLoc, type);
succeeded = true;
} catch (_error) {}
}
if (!succeeded) {
throw ex;
}
}
});
} }
}); };
/** Export the TemplateGenerator function/ctor. */
module.exports = TemplateGenerator;
/** Freeze newlines for protection against errant JST parsers. */ /** Freeze newlines for protection against errant JST parsers. */
@ -241,3 +284,5 @@ Definition of the TemplateGenerator class. TODO: Refactor
}; };
}).call(this); }).call(this);
//# sourceMappingURL=template-generator.js.map

View File

@ -1,12 +1,14 @@
/** /**
Definition of the TextGenerator class. Definition of the TextGenerator class.
@module generators/text-generator
@license MIT. See LICENSE.md for details. @license MIT. See LICENSE.md for details.
@module text-generator.js
*/ */
(function() { (function() {
var TemplateGenerator, TextGenerator; var TemplateGenerator, TextGenerator,
extend = 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;
TemplateGenerator = require('./template-generator'); TemplateGenerator = require('./template-generator');
@ -15,10 +17,17 @@ Definition of the TextGenerator class.
The TextGenerator generates a plain-text resume via the TemplateGenerator. The TextGenerator generates a plain-text resume via the TemplateGenerator.
*/ */
TextGenerator = module.exports = TemplateGenerator.extend({ module.exports = TextGenerator = (function(superClass) {
init: function() { extend(TextGenerator, superClass);
return this._super('txt');
function TextGenerator() {
TextGenerator.__super__.constructor.call(this, 'txt');
} }
});
return TextGenerator;
})(TemplateGenerator);
}).call(this); }).call(this);
//# sourceMappingURL=text-generator.js.map

View File

@ -1,19 +1,28 @@
/* /*
Definition of the WordGenerator class. Definition of the WordGenerator class.
@license MIT. See LICENSE.md for details.
@module generators/word-generator @module generators/word-generator
@license MIT. See LICENSE.md for details.
*/ */
(function() { (function() {
var TemplateGenerator, WordGenerator; var TemplateGenerator, WordGenerator,
extend = 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;
TemplateGenerator = require('./template-generator'); TemplateGenerator = require('./template-generator');
WordGenerator = module.exports = TemplateGenerator.extend({ module.exports = WordGenerator = (function(superClass) {
init: function() { extend(WordGenerator, superClass);
return this._super('doc', 'xml');
function WordGenerator() {
WordGenerator.__super__.constructor.call(this, 'doc', 'xml');
} }
});
return WordGenerator;
})(TemplateGenerator);
}).call(this); }).call(this);
//# sourceMappingURL=word-generator.js.map

View File

@ -6,19 +6,26 @@ Definition of the XMLGenerator class.
*/ */
(function() { (function() {
var BaseGenerator, XMLGenerator; var BaseGenerator, XMLGenerator,
extend = 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;
BaseGenerator = require('./base-generator'); BaseGenerator = require('./base-generator');
/** /** The XmlGenerator generates an XML resume via the TemplateGenerator. */
The XmlGenerator generates an XML resume via the TemplateGenerator.
*/
XMLGenerator = module.exports = BaseGenerator.extend({ module.exports = XMLGenerator = (function(superClass) {
init: function() { extend(XMLGenerator, superClass);
return this._super('xml');
function XMLGenerator() {
XMLGenerator.__super__.constructor.call(this, 'xml');
} }
});
return XMLGenerator;
})(BaseGenerator);
}).call(this); }).call(this);
//# sourceMappingURL=xml-generator.js.map

View File

@ -6,7 +6,9 @@ Definition of the YAMLGenerator class.
*/ */
(function() { (function() {
var TemplateGenerator, YAMLGenerator; var TemplateGenerator, YAMLGenerator,
extend = 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;
TemplateGenerator = require('./template-generator'); TemplateGenerator = require('./template-generator');
@ -15,10 +17,17 @@ Definition of the YAMLGenerator class.
YamlGenerator generates a YAML-formatted resume via TemplateGenerator. YamlGenerator generates a YAML-formatted resume via TemplateGenerator.
*/ */
YAMLGenerator = module.exports = TemplateGenerator.extend({ module.exports = YAMLGenerator = (function(superClass) {
init: function() { extend(YAMLGenerator, superClass);
return this._super('yml', 'yml');
function YAMLGenerator() {
YAMLGenerator.__super__.constructor.call(this, 'yml', 'yml');
} }
});
return YAMLGenerator;
})(TemplateGenerator);
}).call(this); }).call(this);
//# sourceMappingURL=yaml-generator.js.map

71
dist/helpers/block-helpers.js vendored Normal file
View File

@ -0,0 +1,71 @@
/**
Block helper definitions for HackMyResume / FluentCV.
@license MIT. See LICENSE.md for details.
@module helpers/generic-helpers
*/
(function() {
var BlockHelpers, HMSTATUS, LO, _, unused;
HMSTATUS = require('../core/status-codes');
LO = require('lodash');
_ = require('underscore');
unused = require('../utils/string');
/** Block helper function definitions. */
BlockHelpers = module.exports = {
/**
Emit the enclosed content if the resume has a section with
the specified name. Otherwise, emit an empty string ''.
*/
section: function(title, options) {
var obj, ret;
title = title.trim().toLowerCase();
obj = LO.get(this.r, title);
ret = '';
if (obj) {
if (_.isArray(obj)) {
if (obj.length) {
ret = options.fn(this);
}
} else if (_.isObject(obj)) {
if ((obj.history && obj.history.length) || (obj.sets && obj.sets.length)) {
ret = options.fn(this);
}
}
}
return ret;
},
/**
Emit the enclosed content if the resume has the named
property or subproperty.
*/
has: function(title, options) {
title = title && title.trim().toLowerCase();
if (LO.get(this.r, title)) {
return options.fn(this);
}
},
/**
Return true if either value is truthy.
@method either
*/
either: function(lhs, rhs, options) {
if (lhs || rhs) {
return options.fn(this);
}
}
};
}).call(this);
//# sourceMappingURL=block-helpers.js.map

View File

@ -62,3 +62,5 @@ Generic template helper definitions for command-line output.
}; };
}).call(this); }).call(this);
//# sourceMappingURL=console-helpers.js.map

View File

@ -38,36 +38,85 @@ Generic template helper definitions for HackMyResume / FluentCV.
GenericHelpers = module.exports = { GenericHelpers = module.exports = {
/** /**
Convert the input date to a specified format through Moment.js. Emit a formatted string representing the specified datetime.
If date is invalid, will return the time provided by the user, Convert the input date to the specified format through Moment.js. If date is
or default to the fallback param or 'Present' if that is set to true valid, return the formatted date string. If date is null, undefined, or other
@method formatDate falsy value, return the value of the 'fallback' parameter, if specified, or
null if no fallback was specified. If date is invalid, but not null/undefined/
falsy, return it as-is.
@param {string|Moment} datetime A date value.
@param {string} [dtFormat='YYYY-MM'] The desired datetime format. Must be a
Moment.js-compatible datetime format.
@param {string|Moment} fallback A fallback value to use if the specified date
is null, undefined, or falsy.
*/ */
formatDate: function(datetime, format, fallback) { formatDate: function(datetime, dtFormat, fallback) {
var momentDate, ref, ref1; var momentDate;
if (moment) { if (datetime == null) {
datetime = void 0;
}
if (dtFormat == null) {
dtFormat = 'YYYY-MM';
}
if (datetime && moment.isMoment(datetime)) {
return datetime.format(dtFormat);
}
if (String.is(datetime)) {
momentDate = moment(datetime, dtFormat);
if (momentDate.isValid()) {
return momentDate.format(dtFormat);
}
momentDate = moment(datetime); momentDate = moment(datetime);
if (momentDate.isValid()) { if (momentDate.isValid()) {
return momentDate.format(format); return momentDate.format(dtFormat);
} }
} }
return datetime || ((ref = typeof fallback === 'string') != null ? ref : { return datetime || (typeof fallback === 'string' ? fallback : (fallback === true ? 'Present' : ''));
fallback: (ref1 = fallback === true) != null ? ref1 : { },
'Present': null
} /**
}); Emit a formatted string representing the specified datetime.
@param {string} dateValue A raw date value from the FRESH or JRS resume.
@param {string} [dateFormat='YYYY-MM'] The desired datetime format. Must be
compatible with Moment.js datetime formats.
@param {string} [dateDefault=null] The default date value to use if the dateValue
parameter is null, undefined, or falsy.
*/
date: function(dateValue, dateFormat, dateDefault) {
var dateValueMoment, dateValueSafe, reserved;
if (!dateDefault || !String.is(dateDefault)) {
dateDefault = 'Current';
}
if (!dateFormat || !String.is(dateFormat)) {
dateFormat = 'YYYY-MM';
}
if (!dateValue || !String.is(dateValue)) {
dateValue = null;
}
if (!dateValue) {
return dateDefault;
}
reserved = ['current', 'present', 'now'];
dateValueSafe = dateValue.trim().toLowerCase();
if (_.contains(reserved, dateValueSafe)) {
return dateValue;
}
dateValueMoment = moment(dateValue, dateFormat);
if (dateValueMoment.isValid()) {
return dateValueMoment.format(dateFormat);
}
return dateValue;
}, },
/** /**
Given a resume sub-object with a start/end date, format a representation of Given a resume sub-object with a start/end date, format a representation of
the date range. the date range.
@method dateRange
*/ */
dateRange: function(obj, fmt, sep, fallback, options) { dateRange: function(obj, fmt, sep, fallback) {
if (!obj) { if (!obj) {
return ''; return '';
} }
return _fromTo(obj.start, obj.end, fmt, sep, fallback, options); return _fromTo(obj.start, obj.end, fmt, sep, fallback);
}, },
/** /**
@ -102,30 +151,6 @@ Generic template helper definitions for HackMyResume / FluentCV.
} }
}, },
/**
Return true if the section is present on the resume and has at least one
element.
@method section
*/
section: function(title, options) {
var obj, ret;
title = title.trim().toLowerCase();
obj = LO.get(this.r, title);
ret = '';
if (obj) {
if (_.isArray(obj)) {
if (obj.length) {
ret = options.fn(this);
}
} else if (_.isObject(obj)) {
if ((obj.history && obj.history.length) || (obj.sets && obj.sets.length)) {
ret = options.fn(this);
}
}
}
return ret;
},
/** /**
Emit the size of the specified named font. Emit the size of the specified named font.
@param key {String} A named style from the "fonts" section of the theme's @param key {String} A named style from the "fonts" section of the theme's
@ -283,7 +308,7 @@ Generic template helper definitions for HackMyResume / FluentCV.
}, },
/** /**
Capitalize the first letter of the word. Capitalize the first letter of the word. TODO: Rename
@method section @method section
*/ */
camelCase: function(val) { camelCase: function(val) {
@ -296,20 +321,8 @@ Generic template helper definitions for HackMyResume / FluentCV.
}, },
/** /**
Return true if the context has the property or subpropery. Display a user-overridable section title for a FRESH resume theme. Use this in
@method has lieue of hard-coding section titles.
*/
has: function(title, options) {
title = title && title.trim().toLowerCase();
if (LO.get(this.r, title)) {
return options.fn(this);
}
},
/**
Generic template helper function to display a user-overridable section
title for a FRESH resume theme. Use this in lieue of hard-coding section
titles.
Usage: Usage:
@ -332,10 +345,7 @@ Generic template helper definitions for HackMyResume / FluentCV.
return (this.opts.stitles && this.opts.stitles[sname.toLowerCase().trim()]) || stitle; return (this.opts.stitles && this.opts.stitles[sname.toLowerCase().trim()]) || stitle;
}, },
/** /** Convert inline Markdown to inline WordProcessingML. */
Convert inline Markdown to inline WordProcessingML.
@method wpml
*/
wpml: function(txt, inline) { wpml: function(txt, inline) {
if (!txt) { if (!txt) {
return ''; return '';
@ -444,16 +454,6 @@ Generic template helper definitions for HackMyResume / FluentCV.
} }
}, },
/**
Return true if either value is truthy.
@method either
*/
either: function(lhs, rhs, options) {
if (lhs || rhs) {
return options.fn(this);
}
},
/** /**
Conditional stylesheet link. Creates a link to the specified stylesheet with Conditional stylesheet link. Creates a link to the specified stylesheet with
<link> or embeds the styles inline with <style></style>, depending on the <link> or embeds the styles inline with <style></style>, depending on the
@ -488,7 +488,7 @@ Generic template helper definitions for HackMyResume / FluentCV.
compare: function(lvalue, rvalue, options) { compare: function(lvalue, rvalue, options) {
var operator, operators, result; var operator, operators, result;
if (arguments.length < 3) { if (arguments.length < 3) {
throw new Error("Handlerbars Helper 'compare' needs 2 parameters"); throw new Error("Template helper 'compare' needs 2 parameters");
} }
operator = options.hash.operator || "=="; operator = options.hash.operator || "==";
operators = { operators = {
@ -518,7 +518,7 @@ Generic template helper definitions for HackMyResume / FluentCV.
} }
}; };
if (!operators[operator]) { if (!operators[operator]) {
throw new Error("Handlerbars Helper 'compare' doesn't know the operator " + operator); throw new Error("Helper 'compare' doesn't know the operator " + operator);
} }
result = operators[operator](lvalue, rvalue); result = operators[operator](lvalue, rvalue);
if (result) { if (result) {
@ -526,6 +526,21 @@ Generic template helper definitions for HackMyResume / FluentCV.
} else { } else {
return options.inverse(this); return options.inverse(this);
} }
},
pad: function(stringOrArray, padAmount, unused) {
var PAD, ret;
stringOrArray = stringOrArray || '';
padAmount = padAmount || 0;
ret = '';
PAD = require('string-padding');
if (!String.is(stringOrArray)) {
ret = stringOrArray.map(function(line) {
return PAD(line, line.length + Math.abs(padAmount), null, padAmount < 0 ? PAD.LEFT : PAD.RIGHT);
}).join('\n');
} else {
ret = PAD(stringOrArray, stringOrArray.length + Math.abs(padAmount), null, padAmount < 0 ? PAD.LEFT : PAD.RIGHT);
}
return ret;
} }
}; };
@ -569,7 +584,7 @@ Generic template helper definitions for HackMyResume / FluentCV.
dateFrom = dateTemp.format(fmt); dateFrom = dateTemp.format(fmt);
} }
if (_.contains(reserved, dateBTrim)) { if (_.contains(reserved, dateBTrim)) {
dateTo = fallback || 'Current'; dateTo = fallback || 'Present';
} else { } else {
dateTemp = FluentDate.fmt(dateB); dateTemp = FluentDate.fmt(dateB);
dateTo = dateTemp.format(fmt); dateTo = dateTemp.format(fmt);
@ -614,3 +629,5 @@ Generic template helper definitions for HackMyResume / FluentCV.
}; };
}).call(this); }).call(this);
//# sourceMappingURL=generic-helpers.js.map

View File

@ -1,12 +1,12 @@
/** /**
Template helper definitions for Handlebars. Template helper definitions for Handlebars.
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk. @license MIT. See LICENSE.md for details.
@module handlebars-helpers.js @module handlebars-helpers.js
*/ */
(function() { (function() {
var HANDLEBARS, _, helpers; var HANDLEBARS, _, blockHelpers, helpers;
HANDLEBARS = require('handlebars'); HANDLEBARS = require('handlebars');
@ -14,6 +14,8 @@ Template helper definitions for Handlebars.
helpers = require('./generic-helpers'); helpers = require('./generic-helpers');
blockHelpers = require('./block-helpers');
/** /**
Register useful Handlebars helpers. Register useful Handlebars helpers.
@ -21,9 +23,26 @@ Template helper definitions for Handlebars.
*/ */
module.exports = function(theme, opts) { module.exports = function(theme, opts) {
var wrappedHelpers;
helpers.theme = theme; helpers.theme = theme;
helpers.opts = opts; helpers.opts = opts;
return HANDLEBARS.registerHelper(helpers); helpers.type = 'handlebars';
wrappedHelpers = _.mapObject(helpers, function(hVal, hKey) {
if (_.isFunction(hVal)) {
_.wrap(hVal, function(func) {
var args;
args = Array.prototype.slice.call(arguments);
args.shift();
args.pop();
return func.apply(this, args);
});
}
return hVal;
}, this);
HANDLEBARS.registerHelper(wrappedHelpers);
HANDLEBARS.registerHelper(blockHelpers);
}; };
}).call(this); }).call(this);
//# sourceMappingURL=handlebars-helpers.js.map

View File

@ -1,7 +1,7 @@
/** /**
Template helper definitions for Underscore. Template helper definitions for Underscore.
@license MIT. Copyright (c) 2016 hacksalot (https://github.com/hacksalot) @license MIT. See LICENSE.md for details.
@module handlebars-helpers.js @module handlebars-helpers.js
*/ */
@ -26,7 +26,7 @@ Template helper definitions for Underscore.
helpers.cssInfo = cssInfo; helpers.cssInfo = cssInfo;
helpers.engine = eng; helpers.engine = eng;
ctx.h = helpers; ctx.h = helpers;
return _.each(helpers, function(hVal, hKey) { _.each(helpers, function(hVal, hKey) {
if (_.isFunction(hVal)) { if (_.isFunction(hVal)) {
return _.bind(hVal, ctx); return _.bind(hVal, ctx);
} }
@ -34,3 +34,5 @@ Template helper definitions for Underscore.
}; };
}).call(this); }).call(this);
//# sourceMappingURL=underscore-helpers.js.map

11
dist/index.js vendored
View File

@ -6,14 +6,10 @@ External API surface for HackMyResume.
*/ */
/** /** API facade for HackMyResume. */
API facade for HackMyCore.
*/
(function() { (function() {
var HackMyCore; module.exports = {
HackMyCore = module.exports = {
verbs: { verbs: {
build: require('./verbs/build'), build: require('./verbs/build'),
analyze: require('./verbs/analyze'), analyze: require('./verbs/analyze'),
@ -33,6 +29,7 @@ API facade for HackMyCore.
JRSResume: require('./core/jrs-resume'), JRSResume: require('./core/jrs-resume'),
FRESHTheme: require('./core/fresh-theme'), FRESHTheme: require('./core/fresh-theme'),
JRSTheme: require('./core/jrs-theme'), JRSTheme: require('./core/jrs-theme'),
ResumeFactory: require('./core/resume-factory'),
FluentDate: require('./core/fluent-date'), FluentDate: require('./core/fluent-date'),
HtmlGenerator: require('./generators/html-generator'), HtmlGenerator: require('./generators/html-generator'),
TextGenerator: require('./generators/text-generator'), TextGenerator: require('./generators/text-generator'),
@ -47,3 +44,5 @@ API facade for HackMyCore.
}; };
}).call(this); }).call(this);
//# sourceMappingURL=index.js.map

View File

@ -136,3 +136,5 @@ Employment gap analysis for HackMyResume.
}; };
}).call(this); }).call(this);
//# sourceMappingURL=gap-inspector.js.map

View File

@ -59,3 +59,5 @@ Keyword analysis for HackMyResume.
}; };
}).call(this); }).call(this);
//# sourceMappingURL=keyword-inspector.js.map

View File

@ -47,3 +47,5 @@ Section analysis for HackMyResume.
}; };
}).call(this); }).call(this);
//# sourceMappingURL=totals-inspector.js.map

View File

@ -43,7 +43,7 @@ Definition of the HandlebarsGenerator class.
return template(data); return template(data);
} catch (_error) { } catch (_error) {
throw { throw {
fluenterror: template ? HMSTATUS.invokeTemplate : HMSTATUS.compileTemplate, fluenterror: HMSTATUS[template ? 'invokeTemplate' : 'compileTemplate'],
inner: _error inner: _error
}; };
} }
@ -98,3 +98,5 @@ Definition of the HandlebarsGenerator class.
}; };
}).call(this); }).call(this);
//# sourceMappingURL=handlebars-generator.js.map

View File

@ -57,3 +57,5 @@ Definition of the JRSGenerator class.
}; };
}).call(this); }).call(this);
//# sourceMappingURL=jrs-generator.js.map

View File

@ -6,13 +6,15 @@ Definition of the UnderscoreGenerator class.
*/ */
(function() { (function() {
var HMSTATUS, UnderscoreGenerator, _, registerHelpers; var UnderscoreGenerator, _, escapeLaTeX, registerHelpers;
_ = require('underscore'); _ = require('underscore');
registerHelpers = require('../helpers/underscore-helpers'); registerHelpers = require('../helpers/underscore-helpers');
HMSTATUS = require('../core/status-codes'); require('../utils/string');
escapeLaTeX = require('escape-latex');
/** /**
@ -22,19 +24,20 @@ Definition of the UnderscoreGenerator class.
UnderscoreGenerator = module.exports = { UnderscoreGenerator = module.exports = {
generateSimple: function(data, tpl) { generateSimple: function(data, tpl) {
var template; var HMS, t;
try { try {
template = _.template(tpl); t = _.template(tpl);
return template(data); return t(data);
} catch (_error) { } catch (_error) {
HMS = require('../core/status-codes');
throw { throw {
fluenterror: template ? HMSTATUS.invokeTemplate : HMSTATUS.compileTemplate, fluenterror: HMS[t ? 'invokeTemplate' : 'compileTemplate'],
inner: _error inner: _error
}; };
} }
}, },
generate: function(json, jst, format, cssInfo, opts, theme) { generate: function(json, jst, format, cssInfo, opts, theme) {
var ctx, delims; var ctx, delims, r, traverse;
delims = (opts.themeObj && opts.themeObj.delimeters) || opts.template; delims = (opts.themeObj && opts.themeObj.delimeters) || opts.template;
if (opts.themeObj && opts.themeObj.delimeters) { if (opts.themeObj && opts.themeObj.delimeters) {
delims = _.mapObject(delims, function(val, key) { delims = _.mapObject(delims, function(val, key) {
@ -42,9 +45,31 @@ Definition of the UnderscoreGenerator class.
}); });
} }
_.templateSettings = delims; _.templateSettings = delims;
jst = jst.replace(delims.comment, ''); r = null;
switch (format) {
case 'html':
r = json.markdownify();
break;
case 'pdf':
r = json.markdownify();
break;
case 'png':
r = json.markdownify();
break;
case 'latex':
traverse = require('traverse');
r = traverse(json).map(function(x) {
if (this.isLeaf && String.is(this.node)) {
return escapeLaTeX(this.node);
}
return this.node;
});
break;
default:
r = json;
}
ctx = { ctx = {
r: format === 'html' || format === 'pdf' || format === 'png' ? json.markdownify() : json, r: r,
filt: opts.filters, filt: opts.filters,
XML: require('xml-escape'), XML: require('xml-escape'),
RAW: json, RAW: json,
@ -58,3 +83,5 @@ Definition of the UnderscoreGenerator class.
}; };
}).call(this); }).call(this);
//# sourceMappingURL=underscore-generator.js.map

72
dist/utils/class.js vendored
View File

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

View File

@ -10,3 +10,5 @@ Definition of the SyntaxErrorEx class.
}; };
}).call(this); }).call(this);
//# sourceMappingURL=file-contains.js.map

View File

@ -59,3 +59,5 @@ Definition of the Markdown to WordProcessingML conversion routine.
}; };
}).call(this); }).call(this);
//# sourceMappingURL=html-to-wpml.js.map

View File

@ -26,3 +26,5 @@ Inline Markdown-to-Chalk conversion routines.
}; };
}).call(this); }).call(this);
//# sourceMappingURL=md2chalk.js.map

View File

@ -75,3 +75,5 @@
} }
}).call(this); }).call(this);
//# sourceMappingURL=rasterize.js.map

View File

@ -30,3 +30,5 @@ Definition of the SafeJsonLoader class.
}; };
}).call(this); }).call(this);
//# sourceMappingURL=safe-json-loader.js.map

View File

@ -5,42 +5,42 @@ Safe spawn utility for HackMyResume / FluentCV.
@license MIT. See LICENSE.md for details. @license MIT. See LICENSE.md for details.
*/ */
/** Safely spawn a process synchronously or asynchronously without throwing an
exception
*/
(function() { (function() {
module.exports = function(cmd, args, isSync, callback) { module.exports = function(cmd, args, isSync, callback, param) {
var info, spawn; var info, spawn;
try { try {
spawn = require('child_process')[isSync ? 'spawnSync' : 'spawn']; spawn = require('child_process')[isSync ? 'spawnSync' : 'spawn'];
info = spawn(cmd, args); info = spawn(cmd, args);
if (!isSync) { if (!isSync) {
info.on('error', function(err) { info.on('error', function(err) {
if (callback != null) { if (typeof callback === "function") {
callback(err); callback(err, param);
} else {
throw {
cmd: cmd,
inner: err
};
} }
}); });
} else { } else {
if (info.error) { if (info.error) {
if (callback != null) { if (typeof callback === "function") {
callback(err); callback(info.error, param);
} else {
throw {
cmd: cmd,
inner: info.error
};
} }
return {
cmd: cmd,
inner: info.error
};
} }
} }
} catch (_error) { } catch (_error) {
if (callback != null) { if (typeof callback === "function") {
return callback(_error); callback(_error, param);
} else {
throw _error;
} }
return _error;
} }
}; };
}).call(this); }).call(this);
//# sourceMappingURL=safe-spawn.js.map

View File

@ -60,3 +60,5 @@ Object string transformation.
}; };
}).call(this); }).call(this);
//# sourceMappingURL=string-transformer.js.map

View File

@ -25,3 +25,5 @@ See: http://stackoverflow.com/a/32800728/4942583
}; };
}).call(this); }).call(this);
//# sourceMappingURL=string.js.map

View File

@ -18,17 +18,31 @@ See: http://stackoverflow.com/q/13323356
(function() { (function() {
var SyntaxErrorEx; var SyntaxErrorEx;
SyntaxErrorEx = function(ex, rawData) { SyntaxErrorEx = (function() {
var JSONLint, colNum, lineNum, lint; function SyntaxErrorEx(ex, rawData) {
lineNum = null; var JSONLint, colNum, lineNum, lint, ref;
colNum = null; lineNum = null;
JSONLint = require('json-lint'); colNum = null;
lint = JSONLint(rawData, { JSONLint = require('json-lint');
comments: false lint = JSONLint(rawData, {
}); comments: false
this.line = lint.error ? lint.line : '???'; });
return this.col = lint.error ? lint.character : '???'; if (lint.error) {
}; ref = [lint.line, lint.character], this.line = ref[0], this.col = ref[1];
}
if (!lint.error) {
JSONLint = require('jsonlint');
try {
JSONLint.parse(rawData);
} catch (_error) {
this.line = (/on line (\d+)/.exec(_error))[1];
}
}
}
return SyntaxErrorEx;
})();
SyntaxErrorEx.is = function(ex) { SyntaxErrorEx.is = function(ex) {
return ex instanceof SyntaxError; return ex instanceof SyntaxError;
@ -37,3 +51,5 @@ See: http://stackoverflow.com/q/13323356
module.exports = SyntaxErrorEx; module.exports = SyntaxErrorEx;
}).call(this); }).call(this);
//# sourceMappingURL=syntax-error-ex.js.map

73
dist/verbs/analyze.js vendored
View File

@ -6,7 +6,9 @@ Implementation of the 'analyze' verb for HackMyResume.
*/ */
(function() { (function() {
var AnalyzeVerb, HMEVENT, HMSTATUS, MKDIRP, PATH, ResumeFactory, Verb, _, _analyze, _loadInspectors, analyze, chalk; var AnalyzeVerb, HMEVENT, HMSTATUS, MKDIRP, PATH, ResumeFactory, Verb, _, _analyze, _analyzeOne, _loadInspectors, chalk,
extend = 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;
MKDIRP = require('mkdirp'); MKDIRP = require('mkdirp');
@ -24,46 +26,61 @@ Implementation of the 'analyze' verb for HackMyResume.
chalk = require('chalk'); chalk = require('chalk');
AnalyzeVerb = module.exports = Verb.extend({
init: function() { /** An invokable resume analysis command. */
return this._super('analyze', analyze);
module.exports = AnalyzeVerb = (function(superClass) {
extend(AnalyzeVerb, superClass);
function AnalyzeVerb() {
AnalyzeVerb.__super__.constructor.call(this, 'analyze', _analyze);
} }
});
return AnalyzeVerb;
})(Verb);
/** /** Private workhorse for the 'analyze' command. */
Run the 'analyze' command.
*/
analyze = function(sources, dst, opts) { _analyze = function(sources, dst, opts) {
var nlzrs; var nlzrs, results;
if (!sources || !sources.length) { if (!sources || !sources.length) {
throw { this.err(HMSTATUS.resumeNotFound, {
fluenterror: HMSTATUS.resumeNotFound,
quit: true quit: true
}; });
return null;
} }
nlzrs = _loadInspectors(); nlzrs = _loadInspectors();
return _.each(sources, function(src) { results = _.map(sources, function(src) {
var result; var r;
result = ResumeFactory.loadOne(src, { r = ResumeFactory.loadOne(src, {
format: 'FRESH', format: 'FRESH',
objectify: true objectify: true
}, this); }, this);
if (result.fluenterror) { if (opts.assert && this.hasError()) {
return this.setError(result.fluenterror, result); return {};
}
if (r.fluenterror) {
r.quit = opts.assert;
this.err(r.fluenterror, r);
return r;
} else { } else {
return _analyze.call(this, result, nlzrs, opts); return _analyzeOne.call(this, r, nlzrs, opts);
} }
}, this); }, this);
if (this.hasError() && !opts.assert) {
this.reject(this.errorCode);
} else if (!this.hasError()) {
this.resolve(results);
}
return results;
}; };
/** /** Analyze a single resume. */
Analyze a single resume.
*/
_analyze = function(resumeObject, nlzrs, opts) { _analyzeOne = function(resumeObject, nlzrs, opts) {
var info, rez, safeFormat; var info, rez, safeFormat;
rez = resumeObject.rez; rez = resumeObject.rez;
safeFormat = rez.meta && rez.meta.format && rez.meta.format.startsWith('FRESH') ? 'FRESH' : 'JRS'; safeFormat = rez.meta && rez.meta.format && rez.meta.format.startsWith('FRESH') ? 'FRESH' : 'JRS';
@ -74,16 +91,12 @@ Implementation of the 'analyze' verb for HackMyResume.
info = _.mapObject(nlzrs, function(val, key) { info = _.mapObject(nlzrs, function(val, key) {
return val.run(rez); return val.run(rez);
}); });
return this.stat(HMEVENT.afterAnalyze, { this.stat(HMEVENT.afterAnalyze, {
info: info info: info
}); });
return info;
}; };
/**
Load inspectors.
*/
_loadInspectors = function() { _loadInspectors = function() {
return { return {
totals: require('../inspectors/totals-inspector'), totals: require('../inspectors/totals-inspector'),
@ -93,3 +106,5 @@ Implementation of the 'analyze' verb for HackMyResume.
}; };
}).call(this); }).call(this);
//# sourceMappingURL=analyze.js.map

101
dist/verbs/build.js vendored
View File

@ -6,7 +6,9 @@ Implementation of the 'build' verb for HackMyResume.
*/ */
(function() { (function() {
var BuildVerb, FRESHTheme, FS, HMEVENT, HMSTATUS, JRSTheme, MD, MKDIRP, PATH, RConverter, RTYPES, ResumeFactory, Verb, _, _err, _fmts, _log, _opts, _rezObj, addFreebieFormats, build, expand, extend, loadTheme, parsePath, prep, single, verifyOutputs, verifyTheme; var BuildVerb, FRESHTheme, FS, HMEVENT, HMSTATUS, JRSTheme, MD, MKDIRP, PATH, RConverter, RTYPES, ResumeFactory, Verb, _, _addFreebieFormats, _build, _err, _expand, _fmts, _loadTheme, _log, _opts, _prep, _rezObj, _single, _verifyOutputs, _verifyTheme, addFreebieFormats, build, expand, extend, loadTheme, parsePath, prep, single, verifyOutputs, verifyTheme,
extend1 = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
hasProp = {}.hasOwnProperty;
_ = require('underscore'); _ = require('underscore');
@ -70,13 +72,19 @@ Implementation of the 'build' verb for HackMyResume.
/** An invokable resume generation command. */ /** An invokable resume generation command. */
BuildVerb = module.exports = Verb.extend({ module.exports = BuildVerb = (function(superClass) {
extend1(BuildVerb, superClass);
/** Create a new build verb. */ /** Create a new build verb. */
init: function() {
return this._super('build', build); function BuildVerb() {
BuildVerb.__super__.constructor.call(this, 'build', _build);
} }
});
return BuildVerb;
})(Verb);
/** /**
@ -87,15 +95,15 @@ Implementation of the 'build' verb for HackMyResume.
@param opts Generation options. @param opts Generation options.
*/ */
build = function(src, dst, opts) { _build = function(src, dst, opts) {
var ex, inv, isFRESH, mixed, newEx, orgFormat, problemSheets, rez, sheetObjects, sheets, tFolder, targets, theme, toFormat; var inv, isFRESH, mixed, newEx, orgFormat, problemSheets, results, 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; return null;
} }
prep(src, dst, opts); _prep.call(this, src, dst, opts);
sheetObjects = ResumeFactory.load(src, { sheetObjects = ResumeFactory.load(src, {
format: null, format: null,
objectify: false, objectify: false,
@ -120,14 +128,18 @@ Implementation of the 'build' verb for HackMyResume.
theme: _opts.theme theme: _opts.theme
}); });
try { try {
tFolder = verifyTheme.call(this, _opts.theme); tFolder = _verifyTheme.call(this, _opts.theme);
theme = _opts.themeObj = loadTheme(tFolder); if (tFolder.fluenterror) {
addFreebieFormats(theme); tFolder.quit = true;
this.err(tFolder.fluenterror, tFolder);
return;
}
theme = _opts.themeObj = _loadTheme(tFolder);
_addFreebieFormats(theme);
} catch (_error) { } catch (_error) {
ex = _error;
newEx = { newEx = {
fluenterror: HMSTATUS.themeLoad, fluenterror: HMSTATUS.themeLoad,
inner: ex, inner: _error,
attempted: _opts.theme, attempted: _opts.theme,
quit: true quit: true
}; };
@ -137,7 +149,7 @@ Implementation of the 'build' verb for HackMyResume.
this.stat(HMEVENT.afterTheme, { this.stat(HMEVENT.afterTheme, {
theme: theme theme: theme
}); });
inv = verifyOutputs.call(this, dst, theme); inv = _verifyOutputs.call(this, dst, theme);
if (inv && inv.length) { if (inv && inv.length) {
this.err(HMSTATUS.invalidFormat, { this.err(HMSTATUS.invalidFormat, {
data: inv, data: inv,
@ -187,15 +199,29 @@ Implementation of the 'build' verb for HackMyResume.
theme: theme theme: theme
}); });
_rezObj = new RTYPES[toFormat]().parseJSON(rez); _rezObj = new RTYPES[toFormat]().parseJSON(rez);
targets = expand(dst, theme); targets = _expand(dst, theme);
_.each(targets, function(t) { _.each(targets, function(t) {
return t.final = single.call(this, t, theme, targets); var ref;
if (this.hasError() && opts.assert) {
return {};
}
t.final = _single.call(this, t, theme, targets);
if ((ref = t.final) != null ? ref.fluenterror : void 0) {
t.final.quit = opts.assert;
this.err(t.final.fluenterror, t.final);
}
}, this); }, this);
return { results = {
sheet: _rezObj, sheet: _rezObj,
targets: targets, targets: targets,
processed: targets processed: targets
}; };
if (this.hasError() && !opts.assert) {
this.reject(results);
} else if (!this.hasError()) {
this.resolve(results);
}
return results;
}; };
@ -203,7 +229,8 @@ Implementation of the 'build' verb for HackMyResume.
Prepare for a BUILD run. Prepare for a BUILD run.
*/ */
prep = function(src, dst, opts) { _prep = function(src, dst, opts) {
var that;
_opts.theme = (opts.theme && opts.theme.toLowerCase().trim()) || 'modern'; _opts.theme = (opts.theme && opts.theme.toLowerCase().trim()) || 'modern';
_opts.prettify = opts.prettify === true; _opts.prettify = opts.prettify === true;
_opts.css = opts.css; _opts.css = opts.css;
@ -215,6 +242,16 @@ Implementation of the 'build' verb for HackMyResume.
_opts.noTips = opts.noTips; _opts.noTips = opts.noTips;
_opts.debug = opts.debug; _opts.debug = opts.debug;
_opts.sort = opts.sort; _opts.sort = opts.sort;
that = this;
_opts.onTransform = function(info) {
that.stat(HMEVENT.afterTransform, info);
};
_opts.beforeWrite = function(info) {
that.stat(HMEVENT.beforeWrite, info);
};
_opts.afterWrite = function(info) {
that.stat(HMEVENT.afterWrite, info);
};
(src.length > 1 && (!dst || !dst.length)) && dst.push(src.pop()); (src.length > 1 && (!dst || !dst.length)) && dst.push(src.pop());
}; };
@ -226,14 +263,14 @@ Implementation of the 'build' verb for HackMyResume.
@param theme A FRESHTheme or JRSTheme object. @param theme A FRESHTheme or JRSTheme object.
*/ */
single = function(targInfo, theme, finished) { _single = function(targInfo, theme, finished) {
var e, ex, f, fName, fType, outFolder, ret, theFormat; var e, ex, f, fName, fType, outFolder, ret, theFormat;
ret = null; ret = null;
ex = null; ex = null;
f = targInfo.file; f = targInfo.file;
try { try {
if (!targInfo.fmt) { if (!targInfo.fmt) {
return; return {};
} }
fType = targInfo.fmt.outFormat; fType = targInfo.fmt.outFormat;
fName = PATH.basename(f, '.' + fType); fName = PATH.basename(f, '.' + fType);
@ -268,11 +305,12 @@ Implementation of the 'build' verb for HackMyResume.
}); });
if (ex) { if (ex) {
if (ex.fluenterror) { if (ex.fluenterror) {
this.err(ex.fluenterror, ex); ret = ex;
} else { } else {
this.err(HMSTATUS.generateError, { ret = {
fluenterror: HMSTATUS.generateError,
inner: ex inner: ex
}); };
} }
} }
return ret; return ret;
@ -281,7 +319,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, {
targets: targets, targets: targets,
theme: theme theme: theme
@ -308,7 +346,7 @@ Implementation of the 'build' verb for HackMyResume.
@param theTheme A FRESHTheme or JRSTheme object. @param theTheme A FRESHTheme or JRSTheme object.
*/ */
addFreebieFormats = function(theTheme) { _addFreebieFormats = function(theTheme) {
theTheme.formats.json = theTheme.formats.json || { theTheme.formats.json = theTheme.formats.json || {
freebie: true, freebie: true,
title: 'json', title: 'json',
@ -347,7 +385,7 @@ Implementation of the 'build' verb for HackMyResume.
@param theTheme A FRESHTheme or JRSTheme object. @param theTheme A FRESHTheme or JRSTheme object.
*/ */
expand = function(dst, theTheme) { _expand = function(dst, theTheme) {
var destColl, targets; var destColl, targets;
destColl = (dst && dst.length && dst) || [PATH.normalize('out/resume.all')]; destColl = (dst && dst.length && dst) || [PATH.normalize('out/resume.all')];
targets = []; targets = [];
@ -378,16 +416,17 @@ Implementation of the 'build' verb for HackMyResume.
Verify the specified theme name/path. Verify the specified theme name/path.
*/ */
verifyTheme = function(themeNameOrPath) { _verifyTheme = function(themeNameOrPath) {
var exists, tFolder; var exists, tFolder;
tFolder = PATH.join(parsePath(require.resolve('fresh-themes')).dirname, '/themes/', themeNameOrPath); tFolder = PATH.join(parsePath(require.resolve('fresh-themes')).dirname, '/themes/', themeNameOrPath);
exists = require('path-exists').sync; exists = require('path-exists').sync;
if (!exists(tFolder)) { if (!exists(tFolder)) {
tFolder = PATH.resolve(themeNameOrPath); tFolder = PATH.resolve(themeNameOrPath);
if (!exists(tFolder)) { if (!exists(tFolder)) {
this.err(HMSTATUS.themeNotFound, { return {
fluenterror: HMSTATUS.themeNotFound,
data: _opts.theme data: _opts.theme
}); };
} }
} }
return tFolder; return tFolder;
@ -399,7 +438,7 @@ Implementation of the 'build' verb for HackMyResume.
theme. theme.
*/ */
loadTheme = function(tFolder) { _loadTheme = function(tFolder) {
var theTheme; var theTheme;
theTheme = _opts.theme.indexOf('jsonresume-theme-') > -1 ? new JRSTheme().open(tFolder) : new FRESHTheme().open(tFolder); theTheme = _opts.theme.indexOf('jsonresume-theme-') > -1 ? new JRSTheme().open(tFolder) : new FRESHTheme().open(tFolder);
_opts.themeObj = theTheme; _opts.themeObj = theTheme;
@ -407,3 +446,5 @@ Implementation of the 'build' verb for HackMyResume.
}; };
}).call(this); }).call(this);
//# sourceMappingURL=build.js.map

111
dist/verbs/convert.js vendored
View File

@ -6,7 +6,9 @@ Implementation of the 'convert' verb for HackMyResume.
*/ */
(function() { (function() {
var ConvertVerb, HMEVENT, HMSTATUS, ResumeFactory, Verb, _, chalk, convert; var ConvertVerb, HMEVENT, HMSTATUS, ResumeFactory, Verb, _, _convert, _convertOne, chalk,
extend = 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;
ResumeFactory = require('../core/resume-factory'); ResumeFactory = require('../core/resume-factory');
@ -20,69 +22,94 @@ Implementation of the 'convert' verb for HackMyResume.
HMEVENT = require('../core/event-codes'); HMEVENT = require('../core/event-codes');
ConvertVerb = module.exports = Verb.extend({ module.exports = ConvertVerb = (function(superClass) {
init: function() { extend(ConvertVerb, superClass);
return this._super('convert', convert);
function ConvertVerb() {
ConvertVerb.__super__.constructor.call(this, 'convert', _convert);
} }
});
return ConvertVerb;
})(Verb);
/** /** Private workhorse method. Convert 0..N resumes between FRESH and JRS
Convert between FRESH and JRS formats. formats.
*/ */
convert = function(srcs, dst, opts) { _convert = function(srcs, dst, opts) {
var results;
if (!srcs || !srcs.length) { if (!srcs || !srcs.length) {
throw { this.err(HMSTATUS.resumeNotFound, {
fluenterror: 6,
quit: true quit: true
}; });
return null;
} }
if (!dst || !dst.length) { if (!dst || !dst.length) {
if (srcs.length === 1) { if (srcs.length === 1) {
throw { this.err(HMSTATUS.inputOutputParity, {
fluenterror: HMSTATUS.inputOutputParity,
quit: true quit: true
}; });
} else if (srcs.length === 2) { } else if (srcs.length === 2) {
dst = dst || []; dst = dst || [];
dst.push(srcs.pop()); dst.push(srcs.pop());
} else { } else {
throw { this.err(HMSTATUS.inputOutputParity, {
fluenterror: HMSTATUS.inputOutputParity,
quit: true quit: true
}; });
} }
} }
if (srcs && dst && srcs.length && dst.length && srcs.length !== dst.length) { if (srcs && dst && srcs.length && dst.length && srcs.length !== dst.length) {
throw { this.err(HMSTATUS.inputOutputParity, {
fluenterror: HMSTATUS.inputOutputParity({ quit: true
quit: true });
})
};
} }
_.each(srcs, function(src, idx) { results = _.map(srcs, function(src, idx) {
var rinfo, s, srcFmt, targetFormat; var r;
rinfo = ResumeFactory.loadOne(src, { if (opts.assert && this.hasError()) {
format: null, return {};
objectify: true,
"throw": false
});
if (rinfo.fluenterror) {
this.err(rinfo.fluenterror, rinfo);
return;
} }
s = rinfo.rez; r = _convertOne.call(this, src, dst, idx);
srcFmt = ((s.basics && s.basics.imp) || s.imp).orgFormat === 'JRS' ? 'JRS' : 'FRESH'; if (r.fluenterror) {
targetFormat = srcFmt === 'JRS' ? 'FRESH' : 'JRS'; r.quit = opts.assert;
this.stat(HMEVENT.beforeConvert, { this.err(r.fluenterror, r);
srcFile: rinfo.file, }
srcFmt: srcFmt, return r;
dstFile: dst[idx],
dstFmt: targetFormat
});
s.saveAs(dst[idx], targetFormat);
}, this); }, this);
if (this.hasError() && !opts.assert) {
this.reject(results);
} else if (!this.hasError()) {
this.resolve(results);
}
return results;
};
/** Private workhorse method. Convert a single resume. */
_convertOne = function(src, dst, idx) {
var rinfo, s, srcFmt, targetFormat;
rinfo = ResumeFactory.loadOne(src, {
format: null,
objectify: true
});
if (rinfo.fluenterror) {
return rinfo;
}
s = rinfo.rez;
srcFmt = ((s.basics && s.basics.imp) || s.imp).orgFormat === 'JRS' ? 'JRS' : 'FRESH';
targetFormat = srcFmt === 'JRS' ? 'FRESH' : 'JRS';
this.stat(HMEVENT.beforeConvert, {
srcFile: rinfo.file,
srcFmt: srcFmt,
dstFile: dst[idx],
dstFmt: targetFormat
});
s.saveAs(dst[idx], targetFormat);
return s;
}; };
}).call(this); }).call(this);
//# sourceMappingURL=convert.js.map

79
dist/verbs/create.js vendored
View File

@ -6,7 +6,9 @@ Implementation of the 'create' verb for HackMyResume.
*/ */
(function() { (function() {
var CreateVerb, HMEVENT, HMSTATUS, MKDIRP, PATH, Verb, _, chalk, create; var CreateVerb, HMEVENT, HMSTATUS, MKDIRP, PATH, Verb, _, _create, _createOne, chalk,
extend = 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;
MKDIRP = require('mkdirp'); MKDIRP = require('mkdirp');
@ -22,26 +24,55 @@ Implementation of the 'create' verb for HackMyResume.
HMEVENT = require('../core/event-codes'); HMEVENT = require('../core/event-codes');
CreateVerb = module.exports = Verb.extend({ module.exports = CreateVerb = (function(superClass) {
init: function() { extend(CreateVerb, superClass);
return this._super('new', create);
function CreateVerb() {
CreateVerb.__super__.constructor.call(this, 'new', _create);
} }
});
return CreateVerb;
})(Verb);
/** /** Create a new empty resume in either FRESH or JRS format. */
Create a new empty resume in either FRESH or JRS format.
*/
create = function(src, dst, opts) { _create = function(src, dst, opts) {
var results;
if (!src || !src.length) { if (!src || !src.length) {
throw { this.err(HMSTATUS.createNameMissing, {
fluenterror: HMSTATUS.createNameMissing,
quit: true quit: true
}; });
return null;
} }
_.each(src, function(t) { results = _.map(src, function(t) {
var RezClass, safeFmt; var r;
if (opts.assert && this.hasError()) {
return {};
}
r = _createOne.call(this, t, opts);
if (r.fluenterror) {
r.quit = opts.assert;
this.err(r.fluenterror, r);
}
return r;
}, this);
if (this.hasError() && !opts.assert) {
this.reject(this.errorCode);
} else if (!this.hasError()) {
this.resolve(results);
}
return results;
};
/** Create a single new resume */
_createOne = function(t, opts) {
var RezClass, newRez, ret, safeFmt;
try {
ret = null;
safeFmt = opts.format.toUpperCase(); safeFmt = opts.format.toUpperCase();
this.stat(HMEVENT.beforeCreate, { this.stat(HMEVENT.beforeCreate, {
fmt: safeFmt, fmt: safeFmt,
@ -49,12 +80,24 @@ Implementation of the 'create' verb for HackMyResume.
}); });
MKDIRP.sync(PATH.dirname(t)); MKDIRP.sync(PATH.dirname(t));
RezClass = require('../core/' + safeFmt.toLowerCase() + '-resume'); RezClass = require('../core/' + safeFmt.toLowerCase() + '-resume');
RezClass["default"]().save(t); newRez = RezClass["default"]();
return this.stat(HMEVENT.afterCreate, { newRez.save(t);
ret = newRez;
} catch (_error) {
ret = {
fluenterror: HMSTATUS.createError,
inner: _error
};
} finally {
this.stat(HMEVENT.afterCreate, {
fmt: safeFmt, fmt: safeFmt,
file: t file: t,
isError: ret.fluenterror
}); });
}, this); return ret;
}
}; };
}).call(this); }).call(this);
//# sourceMappingURL=create.js.map

104
dist/verbs/peek.js vendored
View File

@ -6,7 +6,9 @@ Implementation of the 'peek' verb for HackMyResume.
*/ */
(function() { (function() {
var HMEVENT, HMSTATUS, PeekVerb, Verb, _, __, peek, safeLoadJSON; var HMEVENT, HMSTATUS, PeekVerb, Verb, _, __, _peek, _peekOne, safeLoadJSON,
extend = 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;
Verb = require('../verbs/verb'); Verb = require('../verbs/verb');
@ -20,51 +22,85 @@ Implementation of the 'peek' verb for HackMyResume.
HMEVENT = require('../core/event-codes'); HMEVENT = require('../core/event-codes');
PeekVerb = module.exports = Verb.extend({ module.exports = PeekVerb = (function(superClass) {
init: function() { extend(PeekVerb, superClass);
return this._super('peek', peek);
function PeekVerb() {
PeekVerb.__super__.constructor.call(this, 'peek', _peek);
} }
});
return PeekVerb;
})(Verb);
/** Peek at a resume, resume section, or resume field. */ /** Peek at a resume, resume section, or resume field. */
peek = function(src, dst, opts) { _peek = function(src, dst, opts) {
var objPath; var objPath, results;
if (!src || !src.length) { if (!src || !src.length) {
({ this.err(HMSTATUS.resumeNotFound, {
"throw": { quit: true
fluenterror: HMSTATUS.resumeNotFound
}
}); });
return null;
} }
objPath = (dst && dst[0]) || ''; objPath = (dst && dst[0]) || '';
_.each(src, function(t) { results = _.map(src, function(t) {
var errCode, obj, tgt; var tgt;
this.stat(HMEVENT.beforePeek, { if (opts.assert && this.hasError()) {
file: t, return {};
target: objPath
});
obj = safeLoadJSON(t);
tgt = null;
if (!obj.ex) {
tgt = objPath ? __.get(obj.json, objPath) : obj.json;
} }
this.stat(HMEVENT.afterPeek, { tgt = _peekOne.call(this, t, objPath);
file: t, if (tgt.error) {
requested: objPath, this.setError(tgt.error.fluenterror, tgt.error);
target: tgt,
error: obj.ex
});
if (obj.ex) {
errCode = obj.ex.operation === 'parse' ? HMSTATUS.parseError : HMSTATUS.readError;
if (errCode === HMSTATUS.readError) {
obj.ex.quiet = true;
}
this.setError(errCode, obj.ex);
return this.err(errCode, obj.ex);
} }
return tgt;
}, this); }, this);
if (this.hasError() && !opts.assert) {
this.reject(this.errorCode);
} else if (!this.hasError()) {
this.resolve(results);
}
return results;
};
/** Peek at a single resume, resume section, or resume field. */
_peekOne = function(t, objPath) {
var errCode, obj, pkgError, tgt;
this.stat(HMEVENT.beforePeek, {
file: t,
target: objPath
});
obj = safeLoadJSON(t);
tgt = null;
if (!obj.ex) {
tgt = objPath ? __.get(obj.json, objPath) : obj.json;
}
pkgError = null;
if (obj.ex) {
errCode = obj.ex.operation === 'parse' ? HMSTATUS.parseError : HMSTATUS.readError;
if (errCode === HMSTATUS.readError) {
obj.ex.quiet = true;
}
pkgError = {
fluenterror: errCode,
inner: obj.ex
};
}
this.stat(HMEVENT.afterPeek, {
file: t,
requested: objPath,
target: obj.ex ? void 0 : tgt,
error: pkgError
});
return {
val: obj.ex ? void 0 : tgt,
error: pkgError
};
}; };
}).call(this); }).call(this);
//# sourceMappingURL=peek.js.map

135
dist/verbs/validate.js vendored
View File

@ -6,7 +6,9 @@ Implementation of the 'validate' verb for HackMyResume.
*/ */
(function() { (function() {
var FS, HMEVENT, HMSTATUS, ResumeFactory, SyntaxErrorEx, ValidateVerb, Verb, _, chalk, safeLoadJSON, validate; var FS, HMEVENT, HMSTATUS, ResumeFactory, SyntaxErrorEx, ValidateVerb, Verb, _, _validate, _validateOne, chalk, safeLoadJSON,
extend = 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');
@ -29,74 +31,109 @@ Implementation of the 'validate' verb for HackMyResume.
/** An invokable resume validation command. */ /** An invokable resume validation command. */
ValidateVerb = module.exports = Verb.extend({ module.exports = ValidateVerb = (function(superClass) {
init: function() { extend(ValidateVerb, superClass);
return this._super('validate', validate);
function ValidateVerb() {
ValidateVerb.__super__.constructor.call(this, 'validate', _validate);
} }
});
return ValidateVerb;
/** Validate 1 to N resumes in FRESH or JSON Resume format. */ })(Verb);
validate = function(sources, unused, opts) { _validate = function(sources, unused, opts) {
var schemas, validator; var results, schemas, validator;
if (!sources || !sources.length) { if (!sources || !sources.length) {
throw { this.err(HMSTATUS.resumeNotFoundAlt, {
fluenterror: HMSTATUS.resumeNotFoundAlt,
quit: true quit: true
}; });
return null;
} }
validator = require('is-my-json-valid'); validator = require('is-my-json-valid');
schemas = { schemas = {
fresh: require('fresca'), fresh: require('fresca'),
jars: require('../core/resume.json') jars: require('../core/resume.json')
}; };
return _.map(sources, function(t) { results = _.map(sources, function(t) {
var errCode, errors, fmt, json, obj, ret; var r;
ret = { r = _validateOne.call(this, t, validator, schemas, opts);
file: t, if (r.error) {
isValid: false this.err(r.error.fluenterror, r.error);
}; }
return r;
}, this);
if (this.hasError() && !opts.assert) {
this.reject(this.errorCode);
} else if (!this.hasError()) {
this.resolve(results);
}
return results;
};
/**
Validate a single resume.
@returns {
file: <fileName>,
isValid: <validFlag>,
status: <validationStatus>,
violations: <validationErrors>,
schema: <schemaType>,
error: <errorObject>
}
*/
_validateOne = function(t, validator, schemas, opts) {
var errCode, obj, ret, validate;
ret = {
file: t,
isValid: false,
status: 'unknown',
schema: '-----'
};
try {
obj = safeLoadJSON(t); obj = safeLoadJSON(t);
if (!obj.ex) { if (!obj.ex) {
json = obj.json; if (obj.json.basics) {
fmt = json.basics ? 'jrs' : 'fresh'; ret.schema = 'jars';
errors = []; } else {
try { ret.schema = 'fresh';
validate = validator(schemas[fmt], { }
formats: { validate = validator(schemas[ret.schema], {
date: /^\d{4}(?:-(?:0[0-9]{1}|1[0-2]{1})(?:-[0-9]{2})?)?$/ formats: {
} date: /^\d{4}(?:-(?:0[0-9]{1}|1[0-2]{1})(?:-[0-9]{2})?)?$/
});
ret.isValid = validate(json);
if (!ret.isValid) {
errors = validate.errors;
} }
} catch (_error) { });
ret.ex = _error; ret.isValid = validate(obj.json);
ret.status = ret.isValid ? 'valid' : 'invalid';
if (!ret.isValid) {
ret.violations = validate.errors;
} }
} else { } else {
errCode = obj.ex.operation === 'parse' ? HMSTATUS.parseError : HMSTATUS.readError; if (obj.ex.operation === 'parse') {
if (errCode === HMSTATUS.readError) { errCode = HMSTATUS.parseError;
obj.ex.quiet = true; ret.status = 'broken';
} else {
errCode = HMSTATUS.readError;
ret.status = 'missing';
} }
this.setError(errCode, obj.ex); ret.error = {
this.err(errCode, obj.ex); fluenterror: errCode,
} inner: obj.ex.inner,
this.stat(HMEVENT.afterValidate, { quiet: errCode === HMSTATUS.readError
file: t,
isValid: ret.isValid,
fmt: fmt != null ? fmt.replace('jars', 'JSON Resume') : void 0,
errors: errors
});
if (opts.assert && !ret.isValid) {
throw {
fluenterror: HMSTATUS.invalid,
shouldExit: true
}; };
} }
return ret; } catch (_error) {
}, this); ret.error = {
fluenterror: HMSTATUS.validateError,
inner: _error
};
}
this.stat(HMEVENT.afterValidate, ret);
return ret;
}; };
}).call(this); }).call(this);
//# sourceMappingURL=validate.js.map

85
dist/verbs/verb.js vendored
View File

@ -6,78 +6,113 @@ Definition of the Verb class.
*/ */
(function() { (function() {
var Class, EVENTS, HMEVENT, Verb; var EVENTS, HMEVENT, Promise, Verb;
Class = require('../utils/class');
EVENTS = require('events'); EVENTS = require('events');
HMEVENT = require('../core/event-codes'); HMEVENT = require('../core/event-codes');
Promise = require('pinkie-promise');
/** /**
An instantiation of a HackMyResume command. An abstract invokable verb.
Provides base class functionality for verbs. Provide common services such as
error handling, event management, and promise support.
@class Verb @class Verb
*/ */
Verb = module.exports = Class.extend({ module.exports = Verb = (function() {
/** Constructor. Automatically called at creation. */ /** Constructor. Automatically called at creation. */
init: function(moniker, workhorse) { function Verb(moniker, workhorse) {
this.moniker = moniker; this.moniker = moniker;
this.emitter = new EVENTS.EventEmitter();
this.workhorse = workhorse; this.workhorse = workhorse;
}, this.emitter = new EVENTS.EventEmitter();
return;
}
/** Invoke the command. */ /** Invoke the command. */
invoke: function() {
var ret; Verb.prototype.invoke = function() {
var argsArray, that;
this.stat(HMEVENT.begin, { this.stat(HMEVENT.begin, {
cmd: this.moniker cmd: this.moniker
}); });
ret = this.workhorse.apply(this, arguments); argsArray = Array.prototype.slice.call(arguments);
this.stat(HMEVENT.end); that = this;
return ret; return this.promise = new Promise(function(res, rej) {
}, that.resolve = res;
that.reject = rej;
that.workhorse.apply(that, argsArray);
});
};
/** Forward subscriptions to the event emitter. */ /** Forward subscriptions to the event emitter. */
on: function() {
Verb.prototype.on = function() {
return this.emitter.on.apply(this.emitter, arguments); return this.emitter.on.apply(this.emitter, arguments);
}, };
/** Fire an arbitrary event, scoped to "hmr:". */ /** Fire an arbitrary event, scoped to "hmr:". */
fire: function(evtName, payload) {
Verb.prototype.fire = function(evtName, payload) {
payload = payload || {}; payload = payload || {};
payload.cmd = this.moniker; payload.cmd = this.moniker;
this.emitter.emit('hmr:' + evtName, payload); this.emitter.emit('hmr:' + evtName, payload);
return true; return true;
}, };
/** Handle an error condition. */ /** Handle an error condition. */
err: function(errorCode, payload, hot) {
Verb.prototype.err = function(errorCode, payload, hot) {
payload = payload || {}; payload = payload || {};
payload.sub = payload.fluenterror = errorCode; payload.sub = payload.fluenterror = errorCode;
payload["throw"] = hot; payload["throw"] = hot;
this.setError(errorCode, payload);
if (payload.quit) {
this.reject(errorCode);
}
this.fire('error', payload); this.fire('error', payload);
if (hot) { if (hot) {
throw payload; throw payload;
} }
return true; return true;
}, };
/** Fire the 'hmr:status' error event. */ /** Fire the 'hmr:status' error event. */
stat: function(subEvent, payload) {
Verb.prototype.stat = function(subEvent, payload) {
payload = payload || {}; payload = payload || {};
payload.sub = subEvent; payload.sub = subEvent;
this.fire('status', payload); this.fire('status', payload);
return true; return true;
}, };
/** Has an error occurred during this verb invocation? */
Verb.prototype.hasError = function() {
return this.errorCode || this.errorObj;
};
/** Associate error info with the invocation. */ /** Associate error info with the invocation. */
setError: function(code, obj) {
Verb.prototype.setError = function(code, obj) {
this.errorCode = code; this.errorCode = code;
this.errorObj = obj; this.errorObj = obj;
} };
});
return Verb;
})();
}).call(this); }).call(this);
//# sourceMappingURL=verb.js.map

View File

@ -1,6 +1,6 @@
{ {
"name": "hackmyresume", "name": "hackmyresume",
"version": "1.7.4", "version": "1.8.0",
"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",
@ -40,33 +40,36 @@
"license": "MIT", "license": "MIT",
"preferGlobal": "true", "preferGlobal": "true",
"bugs": { "bugs": {
"url": "https://github.com/hacksalot/HackMyResume/issues" "url": "https://github.com/JuanCanham/HackMyResume/issues"
}, },
"bin": { "bin": {
"hackmyresume": "dist/cli/index.js" "hackmyresume": "dist/cli/index.js"
}, },
"main": "src/index.js", "main": "dist/index.js",
"homepage": "https://github.com/hacksalot/HackMyResume", "homepage": "https://github.com/JuanCanham/HackMyResume",
"dependencies": { "dependencies": {
"chalk": "^1.1.1", "chalk": "^1.1.1",
"commander": "^2.9.0", "commander": "^2.9.0",
"copy": "^0.1.3", "copy": "^0.1.3",
"escape-latex": "^0.1.2",
"extend": "^3.0.0", "extend": "^3.0.0",
"fresca": "~0.6.0", "fresca": "~0.6.0",
"fresh-jrs-converter": "^0.2.0", "fresh-jrs-converter": "^0.2.2",
"fresh-resume-starter": "^0.2.2", "fresh-resume-starter": "^0.2.2",
"fresh-themes": "^0.14.1-beta", "fresh-themes": "git+https://git.juancanham.com/JuanCanham/fresh-themes.git#feature/certifications",
"fs-extra": "^0.26.4", "fs-extra": "^0.26.4",
"handlebars": "^4.0.5", "handlebars": "^4.0.5",
"html": "0.0.10", "html": "0.0.10",
"is-my-json-valid": "^2.12.4", "is-my-json-valid": "^2.12.4",
"json-lint": "^0.1.0", "json-lint": "^0.1.0",
"jsonlint": "^1.6.2",
"lodash": "^3.10.1", "lodash": "^3.10.1",
"marked": "^0.3.5", "marked": "^0.3.5",
"mkdirp": "^0.5.1", "mkdirp": "^0.5.1",
"moment": "^2.11.1", "moment": "^2.11.1",
"parse-filepath": "^0.6.3", "parse-filepath": "^0.6.3",
"path-exists": "^2.1.0", "path-exists": "^2.1.0",
"pinkie-promise": "^2.0.0",
"printf": "^0.2.3", "printf": "^0.2.3",
"recursive-readdir-sync": "^1.0.6", "recursive-readdir-sync": "^1.0.6",
"simple-html-tokenizer": "^0.2.1", "simple-html-tokenizer": "^0.2.1",
@ -82,7 +85,8 @@
}, },
"devDependencies": { "devDependencies": {
"chai": "*", "chai": "*",
"fresh-test-resumes": "^0.6.0", "dir-compare": "0.0.2",
"fresh-test-resumes": "^0.7.0",
"grunt": "*", "grunt": "*",
"grunt-cli": "^0.1.13", "grunt-cli": "^0.1.13",
"grunt-contrib-clean": "^0.7.0", "grunt-contrib-clean": "^0.7.0",

View File

@ -22,10 +22,8 @@ require 'string.prototype.startswith'
###* ###* Error handler for HackMyResume. All errors are handled here.
Error handler for HackMyResume. All errors are handled here. @class ErrorHandler ###
@class ErrorHandler
###
ErrorHandler = module.exports = ErrorHandler = module.exports =
init: ( debug, assert, silent ) -> init: ( debug, assert, silent ) ->
@ -38,7 +36,7 @@ ErrorHandler = module.exports =
err: ( ex, shouldExit ) -> err: ( ex, shouldExit ) ->
# Short-circuit logging output if --silent is on # Short-circuit logging output if --silent is on
o = if this.silent then () -> else _defaultLog o = if @silent then () -> else _defaultLog
# Special case; can probably be removed. # Special case; can probably be removed.
throw ex if ex.pass throw ex if ex.pass
@ -51,7 +49,7 @@ ErrorHandler = module.exports =
# Output the error message # Output the error message
objError = assembleError.call @, ex objError = assembleError.call @, ex
o( this[ 'format_' + objError.etype ]( objError.msg )) o( @[ 'format_' + objError.etype ]( objError.msg ))
# Output the stack (sometimes) # Output the stack (sometimes)
if objError.withStack if objError.withStack
@ -59,20 +57,20 @@ ErrorHandler = module.exports =
stack && o( chalk.gray( stack ) ); stack && o( chalk.gray( stack ) );
# Quit if necessary # Quit if necessary
if ex.quit || objError.quit if shouldExit
if @debug if @debug
o chalk.cyan('Exiting with error code ' + ex.fluenterror.toString()) o chalk.cyan('Exiting with error code ' + ex.fluenterror.toString())
if this.assert if @assert
ex.pass = true ex.pass = true
throw ex throw ex
process.exit ex.fluenterror process.exit ex.fluenterror
# Handle raw exceptions # Handle raw exceptions
else else
o( ex ) o ex
stackTrace = ex.stack || (ex.inner && ex.inner.stack) stackTrace = ex.stack || (ex.inner && ex.inner.stack)
if stackTrace && this.debug if stackTrace && this.debug
o( M2C(ex.stack || ex.inner.stack, 'gray') ) o M2C(ex.stack || ex.inner.stack, 'gray')
@ -139,7 +137,6 @@ assembleError = ( ex ) ->
when HMSTATUS.pdfGeneration when HMSTATUS.pdfGeneration
msg = M2C( this.msgs.pdfGeneration.msg, 'bold' ) msg = M2C( this.msgs.pdfGeneration.msg, 'bold' )
msg += chalk.red('\n' + ex.inner) if ex.inner msg += chalk.red('\n' + ex.inner) if ex.inner
withStack = true
quit = false quit = false
etype = 'error' etype = 'error'
@ -205,16 +202,30 @@ assembleError = ( ex ) ->
etype = 'custom' etype = 'custom'
when HMSTATUS.parseError when HMSTATUS.parseError
if SyntaxErrorEx.is( ex.inner ) if SyntaxErrorEx.is ex.inner
console.error printf( M2C(this.msgs.readError.msg, 'red'), ex.file ) console.error printf( M2C(this.msgs.readError.msg, 'red'), ex.file )
se = new SyntaxErrorEx ex, ex.raw se = new SyntaxErrorEx ex, ex.raw
msg = printf M2C( this.msgs.parseError.msg, 'red' ), se.line, se.col if se.line? and se.col?
else if ex.inner && ex.inner.line != undefined && ex.inner.col != undefined msg = printf M2C( this.msgs.parseError.msg[0], 'red' ), se.line, se.col
msg = printf( M2C( this.msgs.parseError.msg, 'red' ), ex.inner.line, ex.inner.col) else if se.line?
msg = printf M2C( this.msgs.parseError.msg[1], 'red' ), se.line
else
msg = M2C @msgs.parseError.msg[2], 'red'
else if ex.inner && ex.inner.line? && ex.inner.col?
msg = printf( M2C( this.msgs.parseError.msg[0], 'red' ), ex.inner.line, ex.inner.col)
else else
msg = ex msg = ex
etype = 'error' etype = 'error'
when HMSTATUS.createError
# inner.code could be EPERM, EACCES, etc
msg = printf M2C( this.msgs.createError.msg ), ex.inner.path
etype = 'error'
when HMSTATUS.validateError
msg = printf M2C( @msgs.validateError.msg ), ex.inner.toString()
etype = 'error'
msg: msg # The error message to display msg: msg # The error message to display
withStack: withStack # Whether to include the stack withStack: withStack # Whether to include the stack
quit: quit quit: quit

View File

@ -20,9 +20,13 @@ _ = require 'underscore'
OUTPUT = require './out' OUTPUT = require './out'
PAD = require 'string-padding' PAD = require 'string-padding'
Command = require('commander').Command Command = require('commander').Command
M2C = require '../utils/md2chalk'
printf = require 'printf'
_opts = { } _opts = { }
_title = chalk.white.bold('\n*** HackMyResume v' +PKG.version+ ' ***') _title = chalk.white.bold('\n*** HackMyResume v' +PKG.version+ ' ***')
_out = new OUTPUT( _opts ) _out = new OUTPUT( _opts )
_err = require('./error')
_exitCallback = null
@ -33,9 +37,9 @@ line interface as a single method accepting a parameter array.
@param rawArgs {Array} An array of command-line parameters. Will either be @param rawArgs {Array} An array of command-line parameters. Will either be
process.argv (in production) or custom parameters (in test). process.argv (in production) or custom parameters (in test).
### ###
main = module.exports = (rawArgs) -> main = module.exports = ( rawArgs, exitCallback ) ->
initInfo = initialize( rawArgs ) initInfo = initialize( rawArgs, exitCallback )
args = initInfo.args args = initInfo.args
# Create the top-level (application) command... # Create the top-level (application) command...
@ -124,15 +128,15 @@ main = module.exports = (rawArgs) ->
program.parse( args ) program.parse( args )
if !program.args.length if !program.args.length
throw { fluenterror: 4 } throw fluenterror: 4
### Massage command-line args and setup Commander.js. ### ### Massage command-line args and setup Commander.js. ###
initialize = ( ar ) -> initialize = ( ar, exitCallback ) ->
o = initOptions( ar );
_exitCallback = exitCallback || process.exit
o = initOptions ar
o.silent || logMsg( _title ) o.silent || logMsg( _title )
# Emit debug prelude if --debug was specified # Emit debug prelude if --debug was specified
@ -147,14 +151,22 @@ initialize = ( ar ) ->
#_out.log(chalk.cyan(PAD(' fresh-jrs-converter:',25, null, PAD.RIGHT)) + chalk.cyan.bold( PKG.dependencies['fresh-jrs-converter'] )) #_out.log(chalk.cyan(PAD(' fresh-jrs-converter:',25, null, PAD.RIGHT)) + chalk.cyan.bold( PKG.dependencies['fresh-jrs-converter'] ))
_out.log('') _out.log('')
_err.init o.debug, o.assert, o.silent
# Handle invalid verbs here (a bit easier here than in commander.js)... # Handle invalid verbs here (a bit easier here than in commander.js)...
if o.verb && !HMR.verbs[ o.verb ] && !HMR.alias[ o.verb ] if o.verb && !HMR.verbs[ o.verb ] && !HMR.alias[ o.verb ]
throw { fluenterror: HMSTATUS.invalidCommand, quit: true, attempted: o.orgVerb } _err.err fluenterror: HMSTATUS.invalidCommand, quit: true, attempted: o.orgVerb, true
# Override the .missingArgument behavior # Override the .missingArgument behavior
Command.prototype.missingArgument = (name) -> Command.prototype.missingArgument = (name) ->
if this.name() != 'new' _err.err
throw { fluenterror: HMSTATUS.resumeNotFound, quit: true } fluenterror:
if this.name() != 'new'
then HMSTATUS.resumeNotFound
else HMSTATUS.createNameMissing
, true
return
# Override the .helpInformation behavior # Override the .helpInformation behavior
Command.prototype.helpInformation = -> Command.prototype.helpInformation = ->
@ -205,23 +217,17 @@ initOptions = ( ar ) ->
oJSON = inf.json oJSON = inf.json
# TODO: Error handling # TODO: Error handling
# Grab the --debug flag # Grab the --debug flag, --silent, --assert and --no-color flags
isDebug = _.some( args, (v) -> isDebug = _.some args, (v) -> v == '-d' || v == '--debug'
return v == '-d' || v == '--debug' isSilent = _.some args, (v) -> v == '-s' || v == '--silent'
) isAssert = _.some args, (v) -> v == '-a' || v == '--assert'
# Grab the --silent flag
isSilent = _.some( args, (v) ->
return v == '-s' || v == '--silent'
)
# Grab the --no-color flag
isMono = _.some args, (v) -> v == '--no-color' isMono = _.some args, (v) -> v == '--no-color'
return { return {
color: !isMono, color: !isMono,
debug: isDebug, debug: isDebug,
silent: isSilent, silent: isSilent,
assert: isAssert,
orgVerb: oVerb, orgVerb: oVerb,
verb: verb, verb: verb,
json: oJSON, json: oJSON,
@ -233,19 +239,45 @@ initOptions = ( ar ) ->
### Invoke a HackMyResume verb. ### ### Invoke a HackMyResume verb. ###
execute = ( src, dst, opts, log ) -> execute = ( src, dst, opts, log ) ->
loadOptions.call( this, opts, this.parent.jsonArgs ) # Create the verb
hand = require( './error' ) v = new HMR.verbs[ @name() ]()
hand.init( _opts.debug, _opts.assert, _opts.silent )
v = new HMR.verbs[ this.name() ]()
_opts.errHandler = v
_out.init( _opts )
v.on( 'hmr:status', -> _out.do.apply( _out, arguments ) )
v.on( 'hmr:error', -> hand.err.apply( hand, arguments ) )
v.invoke.call( v, src, dst, _opts, log )
if v.errorCode
console.log 'Exiting with error code ' + v.errorCode
process.exit(v.errorCode)
# Initialize command-specific options
loadOptions.call( this, opts, this.parent.jsonArgs )
# Set up error/output handling
_opts.errHandler = v
_out.init _opts
# Hook up event notifications
v.on 'hmr:status', -> _out.do.apply _out, arguments
v.on 'hmr:error', -> _err.err.apply _err, arguments
# Invoke the verb using promise syntax
prom = v.invoke.call v, src, dst, _opts, log
prom.then executeSuccess, executeFail
return
### Success handler for verb invocations. Calls process.exit by default ###
executeSuccess = (obj) ->
# Can't call _exitCallback here (process.exit) when PDF is running in BK
#_exitCallback 0; return
### Failure handler for verb invocations. Calls process.exit by default ###
executeFail = (err) ->
finalErrorCode = -1
if err
finalErrorCode = if err.fluenterror then err.fluenterror else err
if _opts.debug
msgs = require('./msg').errors;
logMsg printf M2C( msgs.exiting.msg, 'cyan' ), finalErrorCode
logMsg err.stack if err.stack
_exitCallback finalErrorCode
return

View File

@ -3,6 +3,8 @@ events:
msg: Invoking **%s** command. msg: Invoking **%s** command.
beforeCreate: beforeCreate:
msg: Creating new **%s** resume: **%s** msg: Creating new **%s** resume: **%s**
afterCreate:
msg: Creating new **%s** resume: **%s**
afterRead: afterRead:
msg: Reading **%s** resume: **%s** msg: Reading **%s** resume: **%s**
beforeTheme: beforeTheme:
@ -41,6 +43,8 @@ events:
- "VALID!" - "VALID!"
- "INVALID" - "INVALID"
- "BROKEN" - "BROKEN"
- "MISSING"
- "ERROR"
beforePeek: beforePeek:
msg: msg:
- Peeking at **%s** in **%s** - Peeking at **%s** in **%s**
@ -79,7 +83,10 @@ errors:
readError: readError:
msg: Reading **???** resume: **%s** msg: Reading **???** resume: **%s**
parseError: parseError:
msg: Invalid or corrupt JSON on line %s column %s. msg:
- Invalid or corrupt JSON on line %s column %s.
- Invalid or corrupt JSON on line %s.
- Invalid or corrupt JSON.
invalidHelperUse: invalidHelperUse:
msg: "**Warning**: Incorrect use of the **%s** theme helper." msg: "**Warning**: Incorrect use of the **%s** theme helper."
fileSaveError: fileSaveError:
@ -96,3 +103,9 @@ errors:
msg: "Invalid number of parameters. Expected: **%s**." msg: "Invalid number of parameters. Expected: **%s**."
missingParam: missingParam:
msg: The '**%s**' parameter was needed but not supplied. msg: The '**%s**' parameter was needed but not supplied.
createError:
msg: Failed to create **'%s'**.
exiting:
msg: Exiting with status code **%s**.
validateError:
msg: "An error occurred during validation:\n%s"

View File

@ -9,7 +9,6 @@ Output routines for HackMyResume.
chalk = require('chalk') chalk = require('chalk')
HME = require('../core/event-codes') HME = require('../core/event-codes')
_ = require('underscore') _ = require('underscore')
Class = require('../utils/class.js')
M2C = require('../utils/md2chalk.js') M2C = require('../utils/md2chalk.js')
PATH = require('path') PATH = require('path')
LO = require('lodash') LO = require('lodash')
@ -22,11 +21,20 @@ pad = require('string-padding')
dbgStyle = 'cyan'; dbgStyle = 'cyan';
###* A stateful output module. All HMR console output handled here. ###
OutputHandler = module.exports = Class.extend
init: ( opts ) -> ###* A stateful output module. All HMR console output handled here. ###
@opts = EXTEND( true, this.opts || { }, opts ) module.exports = class OutputHandler
constructor: ( opts ) ->
@init opts
return
init: (opts) ->
@opts = EXTEND( true, @opts || { }, opts )
@msgs = YAML.load(PATH.join( __dirname, 'msg.yml' )).events @msgs = YAML.load(PATH.join( __dirname, 'msg.yml' )).events
return return
@ -39,6 +47,7 @@ OutputHandler = module.exports = Class.extend
@opts.silent || console.log( finished ) @opts.silent || console.log( finished )
do: ( evt ) -> do: ( evt ) ->
that = @ that = @
@ -50,8 +59,12 @@ OutputHandler = module.exports = Class.extend
this.opts.debug && this.opts.debug &&
L( M2C( this.msgs.begin.msg, dbgStyle), evt.cmd.toUpperCase() ) L( M2C( this.msgs.begin.msg, dbgStyle), evt.cmd.toUpperCase() )
when HME.beforeCreate #when HME.beforeCreate
L( M2C( this.msgs.beforeCreate.msg, 'green' ), evt.fmt, evt.file ) #L( M2C( this.msgs.beforeCreate.msg, 'green' ), evt.fmt, evt.file )
#break;
when HME.afterCreate
L( M2C( @msgs.beforeCreate.msg, if evt.isError then 'red' else 'green' ), evt.fmt, evt.file )
break; break;
when HME.beforeTheme when HME.beforeTheme
@ -127,29 +140,43 @@ OutputHandler = module.exports = Class.extend
evt.file, evt.fmt ); evt.file, evt.fmt );
when HME.afterValidate when HME.afterValidate
style = if evt.isValid then 'green' else 'yellow' style = 'red'
L( adj = ''
M2C( this.msgs.afterValidate.msg[0], 'white' ) + msgs = @msgs.afterValidate.msg;
chalk[style].bold( switch evt.status
if evt.isValid when 'valid' then style = 'green'; adj = msgs[1]
then this.msgs.afterValidate.msg[1] when 'invalid' then style = 'yellow'; adj = msgs[2]
else this.msgs.afterValidate.msg[2] ), when 'broken' then style = 'red'; adj = msgs[3]
evt.file, evt.fmt when 'missing' then style = 'red'; adj = msgs[4]
); when 'unknown' then style = 'red'; adj = msgs[5]
evt.schema = evt.schema.replace('jars','JSON Resume').toUpperCase()
L(M2C( msgs[0], 'white' ) + chalk[style].bold(adj), evt.file, evt.schema)
if evt.errors if evt.violations
_.each( evt.errors, (err,idx) -> _.each evt.violations, (err,idx) ->
L( chalk.yellow.bold('--> ') + chalk.yellow(err.field.replace('data.','resume.').toUpperCase() + ' ' + err.message)) L( chalk.yellow.bold('--> ') +
, @) chalk.yellow(err.field.replace('data.','resume.').toUpperCase() +
' ' + err.message))
return
, @
return
when HME.afterPeek when HME.afterPeek
sty = if evt.error then 'red' else ( if evt.target != undefined then 'green' else 'yellow' ) sty = if evt.error then 'red' else ( if evt.target != undefined then 'green' else 'yellow' )
# "Peeking at 'someKey' in 'someFile'."
if evt.requested if evt.requested
L(M2C(this.msgs.beforePeek.msg[0], sty), evt.requested, evt.file) L(M2C(this.msgs.beforePeek.msg[0], sty), evt.requested, evt.file)
else else
L(M2C(this.msgs.beforePeek.msg[1], sty), evt.file) L(M2C(this.msgs.beforePeek.msg[1], sty), evt.file)
if evt.target != undefined # If the key was present, print it
if evt.target != undefined and !evt.error
console.dir( evt.target, { depth: null, colors: true } ) console.dir( evt.target, { depth: null, colors: true } )
# If the key was not present, but no error occurred, print it
else if !evt.error else if !evt.error
L(M2C( this.msgs.afterPeek.msg, 'yellow'), evt.requested, evt.file); L M2C( this.msgs.afterPeek.msg, 'yellow'), evt.requested, evt.file
else if evt.error
L chalk.red( evt.error.inner.inner )

View File

@ -33,3 +33,6 @@ module.exports =
afterInlineConvert: 23 afterInlineConvert: 23
beforeValidate: 24 beforeValidate: 24
afterValidate: 25 afterValidate: 25
beforeWrite: 26
afterWrite: 27
applyTheme: 28

View File

@ -29,12 +29,7 @@ object is an instantiation of that JSON decorated with utility methods.
### ###
class FreshResume extends AbstractResume class FreshResume extends AbstractResume
###* Initialize the FreshResume from file. ###
open: ( file, opts ) ->
raw = FS.readFileSync file, 'utf8'
ret = this.parse raw, opts
@imp.file = file
ret
###* Initialize the the FreshResume from JSON string data. ### ###* Initialize the the FreshResume from JSON string data. ###
parse: ( stringData, opts ) -> parse: ( stringData, opts ) ->
@ -42,6 +37,7 @@ class FreshResume extends AbstractResume
this.parseJSON JSON.parse( stringData ), opts this.parseJSON JSON.parse( stringData ), opts
###* ###*
Initialize the FreshResume from JSON. Initialize the FreshResume from JSON.
Open and parse the specified FRESH resume. Merge the JSON object model onto Open and parse the specified FRESH resume. Merge the JSON object model onto
@ -92,6 +88,7 @@ class FreshResume extends AbstractResume
@ @
###* Save the sheet to disk (for environments that have disk access). ### ###* Save the sheet to disk (for environments that have disk access). ###
save: ( filename ) -> save: ( filename ) ->
@imp.file = filename || @imp.file @imp.file = filename || @imp.file
@ -114,7 +111,6 @@ class FreshResume extends AbstractResume
###* ###*
Duplicate this FreshResume instance. Duplicate this FreshResume instance.
This method first extend()s this object onto an empty, creating a deep copy, This method first extend()s this object onto an empty, creating a deep copy,
@ -137,6 +133,7 @@ class FreshResume extends AbstractResume
stringify: () -> FreshResume.stringify @ stringify: () -> FreshResume.stringify @
###* ###*
Create a copy of this resume in which all string fields have been run through Create a copy of this resume in which all string fields have been run through
a transformation function (such as a Markdown filter or XML encoder). a transformation function (such as a Markdown filter or XML encoder).
@ -232,10 +229,6 @@ class FreshResume extends AbstractResume
###* Add work experience to the sheet. ### ###* Add work experience to the sheet. ###
add: ( moniker ) -> add: ( moniker ) ->
defSheet = FreshResume.default() defSheet = FreshResume.default()
@ -258,7 +251,6 @@ class FreshResume extends AbstractResume
###* ###*
Determine if the sheet includes a specific social profile (eg, GitHub). Determine if the sheet includes a specific social profile (eg, GitHub).
### ###
@ -268,6 +260,7 @@ class FreshResume extends AbstractResume
p.network.trim().toLowerCase() == socialNetwork p.network.trim().toLowerCase() == socialNetwork
###* Return the specified network profile. ### ###* Return the specified network profile. ###
getProfile: ( socialNetwork ) -> getProfile: ( socialNetwork ) ->
socialNetwork = socialNetwork.trim().toLowerCase() socialNetwork = socialNetwork.trim().toLowerCase()
@ -275,6 +268,7 @@ class FreshResume extends AbstractResume
sn.network.trim().toLowerCase() == socialNetwork sn.network.trim().toLowerCase() == socialNetwork
###* ###*
Return an array of profiles for the specified network, for when the user Return an array of profiles for the specified network, for when the user
has multiple eg. GitHub accounts. has multiple eg. GitHub accounts.
@ -285,6 +279,7 @@ class FreshResume extends AbstractResume
sn.network.trim().toLowerCase() == socialNetwork sn.network.trim().toLowerCase() == socialNetwork
###* Determine if the sheet includes a specific skill. ### ###* Determine if the sheet includes a specific skill. ###
hasSkill: ( skill ) -> hasSkill: ( skill ) ->
skill = skill.trim().toLowerCase() skill = skill.trim().toLowerCase()
@ -308,10 +303,12 @@ class FreshResume extends AbstractResume
ret ret
duration: (unit) -> duration: (unit) ->
super('employment.history', 'start', 'end', unit) super('employment.history', 'start', 'end', unit)
###* ###*
Sort dated things on the sheet by start date descending. Assumes that dates Sort dated things on the sheet by start date descending. Assumes that dates
on the sheet have been processed with _parseDates(). on the sheet have been processed with _parseDates().
@ -340,11 +337,13 @@ class FreshResume extends AbstractResume
else ( a.safe.date.isAfter(b.safe.date) && -1 ) || 0 else ( a.safe.date.isAfter(b.safe.date) && -1 ) || 0
###* ###*
Get the default (starter) sheet. Get the default (starter) sheet.
### ###
FreshResume.default = () -> FreshResume.default = () ->
new FreshResume().parseJSON( require 'fresh-resume-starter' ) new FreshResume().parseJSON require('fresh-resume-starter').fresh
###* ###*
@ -360,6 +359,7 @@ FreshResume.stringify = ( obj ) ->
JSON.stringify obj, replacer, 2 JSON.stringify obj, replacer, 2
###* ###*
Convert human-friendly dates into formal Moment.js dates for all collections. Convert human-friendly dates into formal Moment.js dates for all collections.
We don't want to lose the raw textual date as entered by the user, so we store We don't want to lose the raw textual date as entered by the user, so we store
@ -401,6 +401,8 @@ _parseDates = () ->
###* Export the Sheet function/ctor. ### ###* Export the Sheet function/ctor. ###
module.exports = FreshResume module.exports = FreshResume
# Note 1: Adjust default date validation to allow YYYY and YYYY-MM formats # Note 1: Adjust default date validation to allow YYYY and YYYY-MM formats
# in addition to YYYY-MM-DD. The original regex: # in addition to YYYY-MM-DD. The original regex:
# #

View File

@ -20,32 +20,26 @@ READFILES = require 'recursive-readdir-sync'
### ### A representation of a FRESH theme asset.
The FRESHTheme class is a representation of a FRESH theme @class FRESHTheme ###
asset. See also: JRSTheme.
@class FRESHTheme
###
class FRESHTheme class FRESHTheme
### Open and parse the specified theme. ###
###
Open and parse the specified theme.
###
open: ( themeFolder ) -> open: ( themeFolder ) ->
this.folder = themeFolder; @folder = themeFolder
# Open the [theme-name].json file; should have the same name as folder # Open the [theme-name].json file; should have the same name as folder
pathInfo = parsePath( themeFolder ) pathInfo = parsePath themeFolder
# Set up a formats hash for the theme # Set up a formats hash for the theme
formatsHash = { } formatsHash = { }
# Load the theme # Load the theme
themeFile = PATH.join( themeFolder, 'theme.json' ) themeFile = PATH.join themeFolder, 'theme.json'
themeInfo = loadSafeJson( themeFile ) themeInfo = loadSafeJson themeFile
if themeInfo.ex if themeInfo.ex
throw throw
fluenterror: fluenterror:
@ -69,13 +63,8 @@ class FRESHTheme
cached[ th ] = cached[th] || new FRESHTheme().open( themePath ) cached[ th ] = cached[th] || new FRESHTheme().open( themePath )
formatsHash[ key ] = cached[ th ].getFormat( key ) formatsHash[ key ] = cached[ th ].getFormat( key )
# Check for an explicit "formats" entry in the theme JSON. If it has one, # Load theme files
# then this theme declares its files explicitly. formatsHash = _load.call @, formatsHash
if !!@formats
formatsHash = loadExplicit.call this, formatsHash
@explicit = true;
else
formatsHash = loadImplicit.call this, formatsHash
# Cache # Cache
@formats = formatsHash @formats = formatsHash
@ -91,72 +80,22 @@ class FRESHTheme
getFormat: ( fmt ) -> @formats[ fmt ] getFormat: ( fmt ) -> @formats[ fmt ]
### Load the theme implicitly, by scanning the theme folder for files. TODO:
Refactor duplicated code with loadExplicit. ###
loadImplicit = (formatsHash) ->
# Set up a hash of formats supported by this theme. ### Load and parse theme source files. ###
_load = (formatsHash) ->
that = @ that = @
major = false major = false
# Establish the base theme folder
tplFolder = PATH.join @folder, 'src' tplFolder = PATH.join @folder, 'src'
copyOnly = ['.ttf','.otf', '.png','.jpg','.jpeg','.pdf']
# Iterate over all files in the theme folder, producing an array, fmts, # Iterate over all files in the theme folder, producing an array, fmts,
# containing info for each file. While we're doing that, also build up # containing info for each file. While we're doing that, also build up
# the formatsHash object. # the formatsHash object.
fmts = READFILES(tplFolder).map (absPath) -> fmts = READFILES(tplFolder).map (absPath) ->
_loadOne.call @, absPath, formatsHash, tplFolder
# If this file lives in a specific format folder within the theme, , @
# such as "/latex" or "/html", then that format is the output format
# for all files within the folder.
pathInfo = parsePath absPath
outFmt = ''
isMajor = false
portion = pathInfo.dirname.replace tplFolder,''
if portion && portion.trim()
return if portion[1] == '_'
reg = /^(?:\/|\\)(html|latex|doc|pdf|png|partials)(?:\/|\\)?/ig
res = reg.exec( portion )
if res
if res[1] != 'partials'
outFmt = res[1]
else
that.partials = that.partials || []
that.partials.push( { name: pathInfo.name, path: absPath } )
return null
# Otherwise, the output format is inferred from the filename, as in
# compact-[outputformat].[extension], for ex, compact-pdf.html.
if !outFmt
idx = pathInfo.name.lastIndexOf '-'
outFmt = if idx == -1 then pathInfo.name else pathInfo.name.substr( idx + 1 )
isMajor = true
# We should have a valid output format now.
formatsHash[ outFmt ] = formatsHash[outFmt] || {
outFormat: outFmt,
files: []
}
# Create the file representation object.
obj =
action: 'transform'
path: absPath
major: isMajor
orgPath: PATH.relative(tplFolder, absPath)
ext: pathInfo.extname.slice(1)
title: friendlyName( outFmt )
pre: outFmt
# outFormat: outFmt || pathInfo.name,
data: FS.readFileSync( absPath, 'utf8' )
css: null
# Add this file to the list of files for this format type.
formatsHash[ outFmt ].files.push( obj )
obj
# Now, get all the CSS files... # Now, get all the CSS files...
@cssFiles = fmts.filter (fmt) -> fmt and (fmt.ext == 'css') @cssFiles = fmts.filter (fmt) -> fmt and (fmt.ext == 'css')
@ -180,98 +119,91 @@ loadImplicit = (formatsHash) ->
### ### Load a single theme file. ###
Load the theme explicitly, by following the 'formats' hash _loadOne = ( absPath, formatsHash, tplFolder ) ->
in the theme's JSON settings file.
###
loadExplicit = (formatsHash) ->
# Housekeeping pathInfo = parsePath absPath
tplFolder = PATH.join this.folder, 'src' absPathSafe = absPath.trim().toLowerCase()
act = null outFmt = ''
that = this act = 'copy'
isPrimary = false
# Iterate over all files in the theme folder, producing an array, fmts, # If this is an "explicit" theme, all files of importance are specified in
# containing info for each file. While we're doing that, also build up # the "transform" section of the theme.json file.
# the formatsHash object. if @explicit
fmts = READFILES( tplFolder ).map (absPath) ->
act = null
# If this file is mentioned in the theme's JSON file under "transforms"
pathInfo = parsePath(absPath)
absPathSafe = absPath.trim().toLowerCase()
outFmt = _.find Object.keys( that.formats ), ( fmtKey ) ->
fmtVal = that.formats[ fmtKey ]
_.some fmtVal.transform, (fpath) ->
absPathB = PATH.join( that.folder, fpath ).trim().toLowerCase()
absPathB == absPathSafe
outFmt = _.find Object.keys( @formats ), ( fmtKey ) ->
fmtVal = @formats[ fmtKey ]
_.some fmtVal.transform, (fpath) ->
absPathB = PATH.join( @folder, fpath ).trim().toLowerCase()
absPathB == absPathSafe
, @
, @
act = 'transform' if outFmt act = 'transform' if outFmt
if !outFmt
# If this file lives in a specific format folder within the theme, # If this file lives in a specific format folder within the theme,
# such as "/latex" or "/html", then that format is the output format # such as "/latex" or "/html", then that format is the implicit output
# for all files within the folder. # format for all files within the folder
if !outFmt portion = pathInfo.dirname.replace tplFolder,''
portion = pathInfo.dirname.replace tplFolder,'' if portion && portion.trim()
if portion && portion.trim() return if portion[1] == '_'
reg = /^(?:\/|\\)(html|latex|doc|pdf)(?:\/|\\)?/ig reg = /^(?:\/|\\)(html|latex|doc|pdf|png|partials)(?:\/|\\)?/ig
res = reg.exec portion res = reg.exec( portion )
res && (outFmt = res[1]) if res
if res[1] != 'partials'
outFmt = res[1]
act = 'transform' if !@explicit
else
@partials = @partials || []
@partials.push( { name: pathInfo.name, path: absPath } )
return null
# Otherwise, the output format is inferred from the filename, as in # Otherwise, the output format is inferred from the filename, as in
# compact-[outputformat].[extension], for ex, compact-pdf.html. # compact-[outputformat].[extension], for ex, compact-pdf.html
if !outFmt if !outFmt
idx = pathInfo.name.lastIndexOf '-' idx = pathInfo.name.lastIndexOf '-'
outFmt = if (idx == -1) then pathInfo.name else pathInfo.name.substr(idx + 1) outFmt = if idx == -1 then pathInfo.name else pathInfo.name.substr idx+1
act = 'transform' if !@explicit
defFormats = require './default-formats'
isPrimary = _.some defFormats, (form) ->
form.name == outFmt and pathInfo.extname != '.css'
# We should have a valid output format now. # Make sure we have a valid formatsHash
formatsHash[ outFmt ] = formatsHash[ outFmt ] = formatsHash[outFmt] || {
formatsHash[ outFmt ] || { outFormat: outFmt,
outFormat: outFmt, files: []
files: [], }
symLinks: that.formats[ outFmt ].symLinks
};
# Create the file representation object. # Move symlink descriptions from theme.json to the format
obj = if @formats?[ outFmt ]?.symLinks
action: act formatsHash[ outFmt ].symLinks = @formats[ outFmt ].symLinks
orgPath: PATH.relative(that.folder, absPath)
path: absPath
ext: pathInfo.extname.slice(1)
title: friendlyName( outFmt )
pre: outFmt
# outFormat: outFmt || pathInfo.name,
data: FS.readFileSync( absPath, 'utf8' )
css: null
# Add this file to the list of files for this format type. # Create the file representation object
formatsHash[ outFmt ].files.push( obj ) obj =
obj action: act
primary: isPrimary
path: absPath
orgPath: PATH.relative tplFolder, absPath
ext: pathInfo.extname.slice 1
title: friendlyName outFmt
pre: outFmt
# outFormat: outFmt || pathInfo.name,
data: FS.readFileSync absPath, 'utf8'
css: null
# Now, get all the CSS files... # Add this file to the list of files for this format type.
@cssFiles = fmts.filter ( fmt ) -> fmt.ext == 'css' formatsHash[ outFmt ].files.push( obj )
obj
# For each CSS file, get its corresponding HTML file
@cssFiles.forEach ( cssf ) ->
# For each CSS file, get its corresponding HTML file
idx = _.findIndex fmts, ( fmt ) ->
fmt.pre == cssf.pre && fmt.ext == 'html'
fmts[ idx ].css = cssf.data;
fmts[ idx ].cssPath = cssf.path;
# Remove CSS files from the formats array
fmts = fmts.filter ( fmt) -> fmt.ext != 'css'
formatsHash
### ### Return a more friendly name for certain formats. ###
Return a more friendly name for certain formats.
TODO: Refactor
###
friendlyName = ( val ) -> friendlyName = ( val ) ->
val = val.trim().toLowerCase() val = (val && val.trim().toLowerCase()) || ''
friendly = { yml: 'yaml', md: 'markdown', txt: 'text' } friendly = { yml: 'yaml', md: 'markdown', txt: 'text' }
friendly[val] || val friendly[val] || val
module.exports = FRESHTheme module.exports = FRESHTheme

View File

@ -26,14 +26,6 @@ class JRSResume extends AbstractResume
###* Initialize the JSResume from file. ###
open: ( file, opts ) ->
raw = FS.readFileSync file, 'utf8'
ret = this.parse raw, opts
@imp.file = file
ret
###* Initialize the the JSResume from string. ### ###* Initialize the the JSResume from string. ###
parse: ( stringData, opts ) -> parse: ( stringData, opts ) ->
@imp = @imp ? raw: stringData @imp = @imp ? raw: stringData
@ -113,8 +105,7 @@ class JRSResume extends AbstractResume
###* Return the resume format. ### ###* Return the resume format. ###
format = () -> 'JRS' format: () -> 'JRS'
@ -157,7 +148,6 @@ class JRSResume extends AbstractResume
###* Add work experience to the sheet. ### ###* Add work experience to the sheet. ###
add: ( moniker ) -> add: ( moniker ) ->
defSheet = JRSResume.default() defSheet = JRSResume.default()
@ -203,10 +193,12 @@ class JRSResume extends AbstractResume
ret ret
duration: (unit) -> duration: (unit) ->
super('work', 'startDate', 'endDate', unit) super('work', 'startDate', 'endDate', unit)
###* ###*
Sort dated things on the sheet by start date descending. Assumes that dates Sort dated things on the sheet by start date descending. Assumes that dates
on the sheet have been processed with _parseDates(). on the sheet have been processed with _parseDates().
@ -233,6 +225,7 @@ class JRSResume extends AbstractResume
else ( a.safeReleaseDate.isAfter(b.safeReleaseDate) && -1 ) || 0 else ( a.safeReleaseDate.isAfter(b.safeReleaseDate) && -1 ) || 0
dupe: () -> dupe: () ->
rnew = new JRSResume() rnew = new JRSResume()
rnew.parse this.stringify(), { } rnew.parse this.stringify(), { }
@ -292,7 +285,7 @@ class JRSResume extends AbstractResume
###* Get the default (empty) sheet. ### ###* Get the default (empty) sheet. ###
JRSResume.default = () -> JRSResume.default = () ->
new JRSResume().open PATH.join( __dirname, 'empty-jrs.json'), 'Empty' new JRSResume().parseJSON require('fresh-resume-starter').jrs
@ -310,6 +303,7 @@ JRSResume.stringify = ( obj ) ->
JSON.stringify obj, replacer, 2 JSON.stringify obj, replacer, 2
###* ###*
Convert human-friendly dates into formal Moment.js dates for all collections. Convert human-friendly dates into formal Moment.js dates for all collections.
We don't want to lose the raw textual date as entered by the user, so we store We don't want to lose the raw textual date as entered by the user, so we store

View File

@ -51,7 +51,7 @@ class JRSTheme
files: [{ files: [{
action: 'transform', action: 'transform',
render: this.render, render: this.render,
major: true, primary: true,
ext: 'html', ext: 'html',
css: null css: null
}] }]
@ -60,7 +60,7 @@ class JRSTheme
files: [{ files: [{
action: 'transform', action: 'transform',
render: this.render, render: this.render,
major: true, primary: true,
ext: 'pdf', ext: 'pdf',
css: null css: null
}] }]

View File

@ -103,13 +103,7 @@ _parse = ( fileName, opts, eve ) ->
return ret return ret
catch catch
# Can be ENOENT, EACCES, SyntaxError, etc. # Can be ENOENT, EACCES, SyntaxError, etc.
ex = fluenterror: if rawData then HACKMYSTATUS.parseError else HACKMYSTATUS.readError
fluenterror: if rawData then HACKMYSTATUS.parseError else HACKMYSTATUS.readError inner: _error
inner: _error raw: rawData
raw: rawData file: fileName
file: fileName
shouldExit: false
opts.quit && (ex.quit = true)
eve && eve.err ex.fluenterror, ex
throw ex if opts.throw
ex

View File

@ -15,7 +15,7 @@ module.exports =
resumeNotFoundAlt: 6 resumeNotFoundAlt: 6
inputOutputParity: 7 inputOutputParity: 7
createNameMissing: 8 createNameMissing: 8
pdfgeneration: 9 pdfGeneration: 9
missingPackageJSON: 10 missingPackageJSON: 10
invalid: 11 invalid: 11
invalidFormat: 12 invalidFormat: 12
@ -31,3 +31,5 @@ module.exports =
themeLoad: 22 themeLoad: 22
invalidParamCount: 23 invalidParamCount: 23
missingParam: 24 missingParam: 24
createError: 25
validateError: 26

View File

@ -1,25 +1,19 @@
###* ###*
Definition of the BaseGenerator class. Definition of the BaseGenerator class.
@module base-generator.js @module generators/base-generator
@license MIT. See LICENSE.md for details. @license MIT. See LICENSE.md for details.
### ###
# Use J. Resig's nifty class implementation
Class = require '../utils/class'
###* ###*
The BaseGenerator class is the root of the generator hierarchy. Functionality The BaseGenerator class is the root of the generator hierarchy. Functionality
common to ALL generators lives here. common to ALL generators lives here.
### ###
BaseGenerator = module.exports = Class.extend module.exports = class BaseGenerator
###* Base-class initialize. ### ###* Base-class initialize. ###
init: ( outputFormat ) -> @format = outputFormat constructor: ( @format ) ->
###* Status codes. ### ###* Status codes. ###
codes: require '../core/status-codes' codes: require '../core/status-codes'

View File

@ -1,7 +1,7 @@
###* ###*
Definition of the HTMLGenerator class. Definition of the HTMLGenerator class.
@module generators/html-generator
@license MIT. See LICENSE.md for details. @license MIT. See LICENSE.md for details.
@module html-generator.js
### ###
@ -14,9 +14,9 @@ require 'string.prototype.endswith'
HtmlGenerator = module.exports = TemplateGenerator.extend module.exports = class HtmlGenerator extends TemplateGenerator
init: -> @_super 'html' constructor: -> super 'html'
###* ###*
Copy satellite CSS files to the destination and optionally pretty-print Copy satellite CSS files to the destination and optionally pretty-print

View File

@ -1,18 +1,18 @@
###* ###*
Definition of the HtmlPdfCLIGenerator class. Definition of the HtmlPdfCLIGenerator class.
@module html-pdf-generator.js @module generators/html-pdf-generator.js
@license MIT. See LICENSE.md for details. @license MIT. See LICENSE.md for details.
### ###
TemplateGenerator = require('./template-generator') TemplateGenerator = require './template-generator'
FS = require('fs-extra') FS = require 'fs-extra'
HTML = require( 'html' ) PATH = require 'path'
PATH = require('path') SLASH = require 'slash'
SPAWN = require('../utils/safe-spawn') _ = require 'underscore'
SLASH = require('slash'); HMSTATUS = require '../core/status-codes'
SPAWN = require '../utils/safe-spawn'
###* ###*
@ -21,33 +21,34 @@ wkhtmltopdf, and other PDF engines over a CLI (command-line interface).
If an engine isn't installed for a particular platform, error out gracefully. If an engine isn't installed for a particular platform, error out gracefully.
### ###
HtmlPdfCLIGenerator = module.exports = TemplateGenerator.extend module.exports = class HtmlPdfCLIGenerator extends TemplateGenerator
init: () -> @_super 'pdf', 'html'
constructor: () -> super 'pdf', 'html'
###* Generate the binary PDF. ### ###* Generate the binary PDF. ###
onBeforeSave: ( info ) -> onBeforeSave: ( info ) ->
try #console.dir _.omit( info, 'mk' ), depth: null, colors: true
safe_eng = info.opts.pdf || 'wkhtmltopdf'; return info.mk if info.ext != 'html' and info.ext != 'pdf'
if safe_eng != 'none' safe_eng = info.opts.pdf || 'wkhtmltopdf'
engines[ safe_eng ].call this, info.mk, info.outputFile safe_eng = 'phantomjs' if safe_eng == 'phantom'
return null # halt further processing if _.has engines, safe_eng
catch ex @errHandler = info.opts.errHandler
# { [Error: write EPIPE] code: 'EPIPE', errno: 'EPIPE', ... } engines[ safe_eng ].call @, info.mk, info.outputFile, @onError
# { [Error: ENOENT] } return null # halt further processing
if ex.inner && ex.inner.code == 'ENOENT'
throw
fluenterror: this.codes.notOnPath
inner: ex.inner ### Low-level error callback for spawn(). May be called after HMR process
engine: ex.cmd, termination, so object references may not be valid here. That's okay; if
stack: ex.inner && ex.inner.stack the references are invalid, the error was already logged. We could use
else spawn-watch here but that causes issues on legacy Node.js. ###
throw onError: (ex, param) ->
fluenterror: this.codes.pdfGeneration param.errHandler?.err? HMSTATUS.pdfGeneration, ex
inner: ex return
stack: ex.stack
@ -63,11 +64,12 @@ engines =
TODO: If HTML generation has run, reuse that output TODO: If HTML generation has run, reuse that output
TODO: Local web server to ease wkhtmltopdf rendering TODO: Local web server to ease wkhtmltopdf rendering
### ###
wkhtmltopdf: (markup, fOut) -> wkhtmltopdf: (markup, fOut, on_error) ->
# Save the markup to a temporary file # Save the markup to a temporary file
tempFile = fOut.replace /\.pdf$/i, '.pdf.html' tempFile = fOut.replace /\.pdf$/i, '.pdf.html'
FS.writeFileSync tempFile, markup, 'utf8' FS.writeFileSync tempFile, markup, 'utf8'
info = SPAWN 'wkhtmltopdf', [ tempFile, fOut ] SPAWN 'wkhtmltopdf', [ tempFile, fOut ], false, on_error, @
return
@ -78,14 +80,13 @@ engines =
TODO: If HTML generation has run, reuse that output TODO: If HTML generation has run, reuse that output
TODO: Local web server to ease Phantom rendering TODO: Local web server to ease Phantom rendering
### ###
phantomjs: ( markup, fOut, on_error ) ->
phantom: ( markup, fOut ) ->
# Save the markup to a temporary file # Save the markup to a temporary file
tempFile = fOut.replace(/\.pdf$/i, '.pdf.html'); tempFile = fOut.replace /\.pdf$/i, '.pdf.html'
FS.writeFileSync tempFile, markup, 'utf8' FS.writeFileSync tempFile, markup, 'utf8'
scriptPath = SLASH( PATH.relative( process.cwd(), scriptPath = PATH.relative process.cwd(), PATH.resolve( __dirname, '../utils/rasterize.js' )
PATH.resolve( __dirname, '../utils/rasterize.js' ) ) ); scriptPath = SLASH scriptPath
sourcePath = SLASH( PATH.relative( process.cwd(), tempFile) ); sourcePath = SLASH PATH.relative( process.cwd(), tempFile)
destPath = SLASH( PATH.relative( process.cwd(), fOut) ); destPath = SLASH PATH.relative( process.cwd(), fOut)
info = SPAWN('phantomjs', [ scriptPath, sourcePath, destPath ]); SPAWN 'phantomjs', [ scriptPath, sourcePath, destPath ], false, on_error, @
return

View File

@ -1,7 +1,7 @@
###* ###*
Definition of the HtmlPngGenerator class. Definition of the HtmlPngGenerator class.
@module generators/html-png-generator
@license MIT. See LICENSE.MD for details. @license MIT. See LICENSE.MD for details.
@module html-png-generator.js
### ###
@ -17,9 +17,9 @@ PATH = require 'path'
###* ###*
An HTML-based PNG resume generator for HackMyResume. An HTML-based PNG resume generator for HackMyResume.
### ###
HtmlPngGenerator = module.exports = TemplateGenerator.extend module.exports = class HtmlPngGenerator extends TemplateGenerator
init: -> @_super 'png', 'html' constructor: -> super 'png', 'html'
invoke: ( rez, themeMarkup, cssInfo, opts ) -> invoke: ( rez, themeMarkup, cssInfo, opts ) ->
# TODO: Not currently called or callable. # TODO: Not currently called or callable.

View File

@ -1,35 +1,25 @@
###* ###*
Definition of the JsonGenerator class. Definition of the JsonGenerator class.
@license MIT. See LICENSE.md for details.
@module generators/json-generator @module generators/json-generator
@license MIT. See LICENSE.md for details.
### ###
BaseGenerator = require './base-generator' BaseGenerator = require './base-generator'
FS = require 'fs' FS = require 'fs'
_ = require 'underscore' _ = require 'underscore'
FJCV = require 'fresh-jrs-converter'
###* ###* The JsonGenerator generates a FRESH or JRS resume as an output. ###
The JsonGenerator generates a JSON resume directly.
###
JsonGenerator = module.exports = BaseGenerator.extend module.exports = class JsonGenerator extends BaseGenerator
init: () -> @_super 'json' constructor: () -> super 'json'
keys: ['imp', 'warnings', 'computed', 'filt', 'ctrl', 'index',
'safeStartDate', 'safeEndDate', 'safeDate', 'safeReleaseDate', 'result',
'isModified', 'htmlPreview', 'safe' ]
invoke: ( rez ) -> invoke: ( rez ) ->
altRez = FJCV[ 'to' + if rez.format() == 'FRESH' then 'JRS' else 'FRESH' ] rez
# TODO: merge with FCVD altRez = FJCV.toSTRING( altRez )
replacer = ( key,value ) -> # Exclude these keys from stringification #altRez.stringify()
if (_.some @keys, (val) -> key.trim() == val)
return undefined
else
value
JSON.stringify rez, replacer, 2
generate: ( rez, f ) -> generate: ( rez, f ) ->
FS.writeFileSync( f, this.invoke(rez), 'utf8' ) FS.writeFileSync f, @invoke(rez), 'utf8'
return return

View File

@ -1,6 +1,6 @@
###* ###*
Definition of the JsonYamlGenerator class. Definition of the JsonYamlGenerator class.
@module json-yaml-generator.js @module generators/json-yaml-generator
@license MIT. See LICENSE.md for details. @license MIT. See LICENSE.md for details.
### ###
@ -18,9 +18,9 @@ JSON without a template, producing an equivalent YAML-formatted resume. See
also YamlGenerator (yaml-generator.js). also YamlGenerator (yaml-generator.js).
### ###
JsonYamlGenerator = module.exports = BaseGenerator.extend module.exports = class JsonYamlGenerator extends BaseGenerator
init: () -> @_super 'yml' constructor: () -> super 'yml'
invoke: ( rez, themeMarkup, cssInfo, opts ) -> invoke: ( rez, themeMarkup, cssInfo, opts ) ->
YAML.stringify JSON.parse( rez.stringify() ), Infinity, 2 YAML.stringify JSON.parse( rez.stringify() ), Infinity, 2
@ -28,3 +28,4 @@ JsonYamlGenerator = module.exports = BaseGenerator.extend
generate: ( rez, f, opts ) -> generate: ( rez, f, opts ) ->
data = YAML.stringify JSON.parse( rez.stringify() ), Infinity, 2 data = YAML.stringify JSON.parse( rez.stringify() ), Infinity, 2
FS.writeFileSync f, data, 'utf8' FS.writeFileSync f, data, 'utf8'
data

View File

@ -1,7 +1,7 @@
###* ###*
Definition of the LaTeXGenerator class. Definition of the LaTeXGenerator class.
@license MIT. See LICENSE.md for details.
@module generators/latex-generator @module generators/latex-generator
@license MIT. See LICENSE.md for details.
### ###
TemplateGenerator = require './template-generator' TemplateGenerator = require './template-generator'
@ -9,6 +9,6 @@ TemplateGenerator = require './template-generator'
###* ###*
LaTeXGenerator generates a LaTeX resume via TemplateGenerator. LaTeXGenerator generates a LaTeX resume via TemplateGenerator.
### ###
LaTeXGenerator = module.exports = TemplateGenerator.extend module.exports = class LaTeXGenerator extends TemplateGenerator
init: () -> @_super 'latex', 'tex' constructor: () -> super 'latex', 'tex'

View File

@ -1,7 +1,7 @@
###* ###*
Definition of the MarkdownGenerator class. Definition of the MarkdownGenerator class.
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk. @module generators/markdown-generator
@module markdown-generator.js @license MIT. See LICENSE.md for details.
### ###
TemplateGenerator = require './template-generator' TemplateGenerator = require './template-generator'
@ -9,6 +9,6 @@ TemplateGenerator = require './template-generator'
###* ###*
MarkdownGenerator generates a Markdown-formatted resume via TemplateGenerator. MarkdownGenerator generates a Markdown-formatted resume via TemplateGenerator.
### ###
MarkdownGenerator = module.exports = TemplateGenerator.extend module.exports = class MarkdownGenerator extends TemplateGenerator
init: () -> @_super 'md', 'txt' constructor: () -> super 'md', 'txt'

View File

@ -1,7 +1,7 @@
###* ###*
Definition of the TemplateGenerator class. TODO: Refactor Definition of the TemplateGenerator class. TODO: Refactor
@module generators/template-generator
@license MIT. See LICENSE.md for details. @license MIT. See LICENSE.md for details.
@module template-generator.js
### ###
@ -27,14 +27,16 @@ plain text, and XML versions of Microsoft Word, Excel, and OpenOffice.
@class TemplateGenerator @class TemplateGenerator
### ###
TemplateGenerator = module.exports = BaseGenerator.extend module.exports = class TemplateGenerator extends BaseGenerator
###* Constructor. Set the output format and template format for this ###* Constructor. Set the output format and template format for this
generator. Will usually be called by a derived generator such as generator. Will usually be called by a derived generator such as
HTMLGenerator or MarkdownGenerator. ### HTMLGenerator or MarkdownGenerator. ###
init: ( outputFormat, templateFormat, cssFile ) -> constructor: ( outputFormat, templateFormat, cssFile ) ->
@_super outputFormat super outputFormat
@tplFormat = templateFormat || outputFormat @tplFormat = templateFormat || outputFormat
return return
@ -52,27 +54,32 @@ TemplateGenerator = module.exports = BaseGenerator.extend
opts = opts =
if opts if opts
then (this.opts = EXTEND( true, { }, _defaultOpts, opts )) then (@opts = EXTEND( true, { }, _defaultOpts, opts ))
else this.opts else @opts
# Sort such that CSS files are processed before others # Sort such that CSS files are processed before others
curFmt = opts.themeObj.getFormat( this.format ) curFmt = opts.themeObj.getFormat( this.format )
curFmt.files = _.sortBy curFmt.files, (fi) -> fi.ext != 'css' curFmt.files = _.sortBy curFmt.files, (fi) -> fi.ext != 'css'
# Run the transformation! # Run the transformation!
results = curFmt.files.map( ( tplInfo, idx ) -> results = curFmt.files.map ( tplInfo, idx ) ->
trx = @.single rez, tplInfo.data, this.format, opts, opts.themeObj, curFmt if tplInfo.action == 'transform'
if tplInfo.ext == 'css' trx = @transform rez, tplInfo.data, @format, opts, opts.themeObj, curFmt
curFmt.files[idx].data = trx if tplInfo.ext == 'css'
else tplInfo.ext == 'html' curFmt.files[idx].data = trx
#tplInfo.css contains the CSS data loaded by theme else tplInfo.ext == 'html'
#tplInfo.cssPath contains the absolute path to the source CSS File #tplInfo.css contains the CSS data loaded by theme
#tplInfo.cssPath contains the absolute path to the source CSS File
else
# Images and non-transformable binary files
opts.onTransform? tplInfo
return info: tplInfo, data: trx return info: tplInfo, data: trx
, @) , @
files: results files: results
###* Generate a resume using file-based inputs and outputs. Requires access ###* Generate a resume using file-based inputs and outputs. Requires access
to the local filesystem. to the local filesystem.
@method generate @method generate
@ -83,53 +90,60 @@ TemplateGenerator = module.exports = BaseGenerator.extend
generate: ( rez, f, opts ) -> generate: ( rez, f, opts ) ->
# Prepare # Prepare
this.opts = EXTEND( true, { }, _defaultOpts, opts ); @opts = EXTEND true, { }, _defaultOpts, opts
# Call the string-based generation method to perform the generation. # Call the string-based generation method
genInfo = this.invoke( rez, null ) genInfo = @invoke rez, null
outFolder = parsePath( f ).dirname outFolder = parsePath( f ).dirname
curFmt = opts.themeObj.getFormat( this.format ) curFmt = opts.themeObj.getFormat @format
# Process individual files within this format. For example, the HTML # Process individual files within this format. For example, the HTML
# output format for a theme may have multiple HTML files, CSS files, # output format for a theme may have multiple HTML files, CSS files,
# etc. Process them here. # etc. Process them here.
genInfo.files.forEach(( file ) -> genInfo.files.forEach ( file ) ->
# console.dir _.omit(file.info,'cssData','data','css' )
# Pre-processing # Pre-processing
file.info.orgPath = file.info.orgPath || '' # <-- For JRS themes file.info.orgPath = file.info.orgPath || ''
thisFilePath = PATH.join( outFolder, file.info.orgPath ) thisFilePath =
if this.onBeforeSave if file.info.primary
then f
else PATH.join outFolder, file.info.orgPath
if file.info.action != 'copy' and @onBeforeSave
file.data = this.onBeforeSave file.data = this.onBeforeSave
theme: opts.themeObj theme: opts.themeObj
outputFile: if file.info.major then f else thisFilePath outputFile: thisFilePath
mk: file.data mk: file.data
opts: this.opts opts: @opts,
ext: file.info.ext
if !file.data if !file.data
return # PDF etc return
# Write the file # Write the file
fileName = if file.info.major then f else thisFilePath opts.beforeWrite? thisFilePath
MKDIRP.sync PATH.dirname( fileName ) MKDIRP.sync PATH.dirname( thisFilePath )
FS.writeFileSync fileName, file.data, { encoding: 'utf8', flags: 'w' }
if file.info.action != 'copy'
FS.writeFileSync thisFilePath, file.data, encoding: 'utf8', flags: 'w'
else
FS.copySync file.info.path, thisFilePath
opts.afterWrite? thisFilePath
# Post-processing # Post-processing
if @onAfterSave if @onAfterSave
@onAfterSave( outputFile: fileName, mk: file.data, opts: this.opts ) @onAfterSave outputFile: fileName, mk: file.data, opts: this.opts
, @) , @
# Some themes require a symlink structure. If so, create it. # Some themes require a symlink structure. If so, create it.
if curFmt.symLinks createSymLinks curFmt, outFolder
Object.keys( curFmt.symLinks ).forEach (loc) ->
absLoc = PATH.join outFolder, loc
absTarg = PATH.join PATH.dirname(absLoc), curFmt.symLinks[loc]
# 'file', 'dir', or 'junction' (Windows only)
type = parsePath( absLoc ).extname ? 'file' : 'junction'
FS.symlinkSync absTarg, absLoc, type
genInfo genInfo
###* Perform a single resume resume transformation using string-based inputs ###* Perform a single resume resume transformation using string-based inputs
and outputs without touching the local file system. and outputs without touching the local file system.
@param json A FRESH or JRS resume object. @param json A FRESH or JRS resume object.
@ -138,8 +152,8 @@ TemplateGenerator = module.exports = BaseGenerator.extend
@param cssInfo Needs to be refactored. @param cssInfo Needs to be refactored.
@param opts Options and passthrough data. ### @param opts Options and passthrough data. ###
single: ( json, jst, format, opts, theme, curFmt ) -> transform: ( json, jst, format, opts, theme, curFmt ) ->
if this.opts.freezeBreaks if @opts.freezeBreaks
jst = freeze jst jst = freeze jst
eng = require '../renderers/' + theme.engine + '-generator' eng = require '../renderers/' + theme.engine + '-generator'
result = eng.generate json, jst, format, curFmt, opts, theme result = eng.generate json, jst, format, curFmt, opts, theme
@ -149,9 +163,27 @@ TemplateGenerator = module.exports = BaseGenerator.extend
###* Export the TemplateGenerator function/ctor. ### createSymLinks = ( curFmt, outFolder ) ->
module.exports = TemplateGenerator # Some themes require a symlink structure. If so, create it.
if curFmt.symLinks
Object.keys( curFmt.symLinks ).forEach (loc) ->
absLoc = PATH.join outFolder, loc
absTarg = PATH.join PATH.dirname(absLoc), curFmt.symLinks[loc]
# Set type to 'file', 'dir', or 'junction' (Windows only)
type = if parsePath( absLoc ).extname then 'file' else 'junction'
try
FS.symlinkSync absTarg, absLoc, type
catch
succeeded = false
if _error.code == 'EEXIST'
FS.unlinkSync absLoc
try
FS.symlinkSync absTarg, absLoc, type
succeeded = true
if !succeeded
throw ex
return
###* Freeze newlines for protection against errant JST parsers. ### ###* Freeze newlines for protection against errant JST parsers. ###
@ -167,6 +199,7 @@ unfreeze = ( markup ) ->
markup.replace _reg.regSymN, '\n' markup.replace _reg.regSymN, '\n'
###* Default template generator options. ### ###* Default template generator options. ###
_defaultOpts = _defaultOpts =
engine: 'underscore' engine: 'underscore'

View File

@ -1,7 +1,7 @@
###* ###*
Definition of the TextGenerator class. Definition of the TextGenerator class.
@module generators/text-generator
@license MIT. See LICENSE.md for details. @license MIT. See LICENSE.md for details.
@module text-generator.js
### ###
TemplateGenerator = require './template-generator' TemplateGenerator = require './template-generator'
@ -9,6 +9,6 @@ TemplateGenerator = require './template-generator'
###* ###*
The TextGenerator generates a plain-text resume via the TemplateGenerator. The TextGenerator generates a plain-text resume via the TemplateGenerator.
### ###
TextGenerator = module.exports = TemplateGenerator.extend module.exports = class TextGenerator extends TemplateGenerator
init: () -> @_super 'txt' constructor: () -> super 'txt'

View File

@ -1,11 +1,12 @@
### ###
Definition of the WordGenerator class. Definition of the WordGenerator class.
@license MIT. See LICENSE.md for details.
@module generators/word-generator @module generators/word-generator
@license MIT. See LICENSE.md for details.
### ###
TemplateGenerator = require './template-generator' TemplateGenerator = require './template-generator'
WordGenerator = module.exports = TemplateGenerator.extend module.exports = class WordGenerator extends TemplateGenerator
init: () -> @_super 'doc', 'xml'
constructor: () -> super 'doc', 'xml'

View File

@ -6,8 +6,7 @@ Definition of the XMLGenerator class.
BaseGenerator = require './base-generator' BaseGenerator = require './base-generator'
###* ###* The XmlGenerator generates an XML resume via the TemplateGenerator. ###
The XmlGenerator generates an XML resume via the TemplateGenerator. module.exports = class XMLGenerator extends BaseGenerator
###
XMLGenerator = module.exports = BaseGenerator.extend constructor: () -> super 'xml'
init: () -> @_super 'xml'

View File

@ -11,5 +11,6 @@ TemplateGenerator = require './template-generator'
YamlGenerator generates a YAML-formatted resume via TemplateGenerator. YamlGenerator generates a YAML-formatted resume via TemplateGenerator.
### ###
YAMLGenerator = module.exports = TemplateGenerator.extend module.exports = class YAMLGenerator extends TemplateGenerator
init: () -> @_super 'yml', 'yml'
constructor: () -> super 'yml', 'yml'

View File

@ -0,0 +1,58 @@
###*
Block helper definitions for HackMyResume / FluentCV.
@license MIT. See LICENSE.md for details.
@module helpers/generic-helpers
###
HMSTATUS = require '../core/status-codes'
LO = require 'lodash'
_ = require 'underscore'
unused = require '../utils/string'
###* Block helper function definitions. ###
BlockHelpers = module.exports =
###*
Emit the enclosed content if the resume has a section with
the specified name. Otherwise, emit an empty string ''.
###
section: ( title, options ) ->
title = title.trim().toLowerCase()
obj = LO.get this.r, title
ret = ''
if obj
if _.isArray obj
if obj.length
ret = options.fn @
else if _.isObject obj
if (obj.history && obj.history.length) || (obj.sets && obj.sets.length)
ret = options.fn @
ret
###*
Emit the enclosed content if the resume has the named
property or subproperty.
###
has: ( title, options ) ->
title = title && title.trim().toLowerCase()
if LO.get this.r, title
return options.fn this
return
###*
Return true if either value is truthy.
@method either
###
either: ( lhs, rhs, options ) -> options.fn @ if lhs || rhs

View File

@ -17,32 +17,95 @@ LO = require 'lodash'
PATH = require 'path' PATH = require 'path'
printf = require 'printf' printf = require 'printf'
_ = require 'underscore' _ = require 'underscore'
unused = require '../utils/string'; unused = require '../utils/string'
###* Generic template helper function definitions. ### ###* Generic template helper function definitions. ###
GenericHelpers = module.exports = GenericHelpers = module.exports =
###*
Convert the input date to a specified format through Moment.js.
If date is invalid, will return the time provided by the user,
or default to the fallback param or 'Present' if that is set to true
@method formatDate
###
formatDate: (datetime, format, fallback) ->
if moment
momentDate = moment datetime
return momentDate.format(format) if momentDate.isValid()
datetime || (typeof fallback == 'string' ? fallback : (fallback == true ? 'Present' : null));
###*
Emit a formatted string representing the specified datetime.
Convert the input date to the specified format through Moment.js. If date is
valid, return the formatted date string. If date is null, undefined, or other
falsy value, return the value of the 'fallback' parameter, if specified, or
null if no fallback was specified. If date is invalid, but not null/undefined/
falsy, return it as-is.
@param {string|Moment} datetime A date value.
@param {string} [dtFormat='YYYY-MM'] The desired datetime format. Must be a
Moment.js-compatible datetime format.
@param {string|Moment} fallback A fallback value to use if the specified date
is null, undefined, or falsy.
###
formatDate: (datetime, dtFormat, fallback) ->
datetime ?= undefined
dtFormat ?= 'YYYY-MM'
# If a Moment.js object was passed in, just call format on it
if datetime and moment.isMoment datetime
return datetime.format dtFormat
if String.is datetime
# If a string was passed in, convert to Moment using the 2-paramter
# constructor with an explicit format string.
momentDate = moment datetime, dtFormat
return momentDate.format(dtFormat) if momentDate.isValid()
# If that didn't work, try again with the single-parameter constructor
# but this may throw a deprecation warning
momentDate = moment datetime
return momentDate.format(dtFormat) if momentDate.isValid()
# We weren't able to format the provided datetime. Now do one of three
# things.
# 1. If datetime is non-null/non-falsy, return it. For this helper,
# string date values that we can't parse are assumed to be display dates.
# 2. If datetime IS null or falsy, use the value from the fallback.
# 3. If the fallback value is specifically 'true', emit 'Present'.
datetime ||
if typeof fallback == 'string'
then fallback
else (if fallback == true then 'Present' else '')
###*
Emit a formatted string representing the specified datetime.
@param {string} dateValue A raw date value from the FRESH or JRS resume.
@param {string} [dateFormat='YYYY-MM'] The desired datetime format. Must be
compatible with Moment.js datetime formats.
@param {string} [dateDefault=null] The default date value to use if the dateValue
parameter is null, undefined, or falsy.
###
date: (dateValue, dateFormat, dateDefault) ->
dateDefault = 'Current' if !dateDefault or !String.is dateDefault
dateFormat = 'YYYY-MM' if !dateFormat or !String.is dateFormat
dateValue = null if !dateValue or !String.is dateValue
return dateDefault if !dateValue
reserved = ['current', 'present', 'now']
dateValueSafe = dateValue.trim().toLowerCase();
return dateValue if _.contains reserved, dateValueSafe
dateValueMoment = moment dateValue, dateFormat
return dateValueMoment.format dateFormat if dateValueMoment.isValid()
dateValue
###* ###*
Given a resume sub-object with a start/end date, format a representation of Given a resume sub-object with a start/end date, format a representation of
the date range. the date range.
@method dateRange
### ###
dateRange: ( obj, fmt, sep, fallback, options ) -> dateRange: ( obj, fmt, sep, fallback ) ->
return '' if !obj return '' if !obj
_fromTo obj.start, obj.end, fmt, sep, fallback, options _fromTo obj.start, obj.end, fmt, sep, fallback
###* ###*
Format a from/to date range for display. Format a from/to date range for display.
@ -50,6 +113,8 @@ GenericHelpers = module.exports =
### ###
fromTo: () -> _fromTo.apply this, arguments fromTo: () -> _fromTo.apply this, arguments
###* ###*
Return a named color value as an RRGGBB string. Return a named color value as an RRGGBB string.
@method toFrom @method toFrom
@ -66,23 +131,7 @@ GenericHelpers = module.exports =
return colorDefault return colorDefault
ret ret
###*
Return true if the section is present on the resume and has at least one
element.
@method section
###
section: ( title, options ) ->
title = title.trim().toLowerCase()
obj = LO.get this.r, title
ret = ''
if obj
if _.isArray obj
if obj.length
ret = options.fn @
else if _.isObject obj
if (obj.history && obj.history.length) || (obj.sets && obj.sets.length)
ret = options.fn @
ret
###* ###*
Emit the size of the specified named font. Emit the size of the specified named font.
@ -133,6 +182,7 @@ GenericHelpers = module.exports =
ret ret
###* ###*
Emit the font face (such as 'Helvetica' or 'Calibri') associated with the Emit the font face (such as 'Helvetica' or 'Calibri') associated with the
provided key. provided key.
@ -185,6 +235,8 @@ GenericHelpers = module.exports =
return ret; return ret;
###* ###*
Emit a comma-delimited list of font names suitable associated with the Emit a comma-delimited list of font names suitable associated with the
provided key. provided key.
@ -240,28 +292,21 @@ GenericHelpers = module.exports =
return ret; return ret;
###* ###*
Capitalize the first letter of the word. Capitalize the first letter of the word. TODO: Rename
@method section @method section
### ###
camelCase: (val) -> camelCase: (val) ->
val = (val && val.trim()) || '' val = (val && val.trim()) || ''
return if val then (val.charAt(0).toUpperCase() + val.slice(1)) else val return if val then (val.charAt(0).toUpperCase() + val.slice(1)) else val
###*
Return true if the context has the property or subpropery.
@method has
###
has: ( title, options ) ->
title = title && title.trim().toLowerCase()
if LO.get this.r, title
return options.fn this
return
###* ###*
Generic template helper function to display a user-overridable section Display a user-overridable section title for a FRESH resume theme. Use this in
title for a FRESH resume theme. Use this in lieue of hard-coding section lieue of hard-coding section titles.
titles.
Usage: Usage:
@ -291,10 +336,9 @@ GenericHelpers = module.exports =
this.opts.stitles[ sname.toLowerCase().trim() ] ) || this.opts.stitles[ sname.toLowerCase().trim() ] ) ||
stitle; stitle;
###*
Convert inline Markdown to inline WordProcessingML.
@method wpml ###* Convert inline Markdown to inline WordProcessingML. ###
###
wpml: ( txt, inline ) -> wpml: ( txt, inline ) ->
return '' if !txt return '' if !txt
inline = (inline && !inline.hash) || false inline = (inline && !inline.hash) || false
@ -304,6 +348,7 @@ GenericHelpers = module.exports =
return txt return txt
###* ###*
Emit a conditional link. Emit a conditional link.
@method link @method link
@ -311,6 +356,8 @@ GenericHelpers = module.exports =
link: ( text, url ) -> link: ( text, url ) ->
return if url && url.trim() then ('<a href="' + url + '">' + text + '</a>') else text return if url && url.trim() then ('<a href="' + url + '">' + text + '</a>') else text
###* ###*
Return the last word of the specified text. Return the last word of the specified text.
@method lastWord @method lastWord
@ -318,6 +365,8 @@ GenericHelpers = module.exports =
lastWord: ( txt ) -> lastWord: ( txt ) ->
return if txt && txt.trim() then _.last( txt.split(' ') ) else '' return if txt && txt.trim() then _.last( txt.split(' ') ) else ''
###* ###*
Convert a skill level to an RGB color triplet. TODO: refactor Convert a skill level to an RGB color triplet. TODO: refactor
@method skillColor @method skillColor
@ -333,6 +382,8 @@ GenericHelpers = module.exports =
[ '#FFFFFF', '#5CB85C', '#F1C40F', '#428BCA', '#C00000' ] [ '#FFFFFF', '#5CB85C', '#F1C40F', '#428BCA', '#C00000' ]
return skillColors[idx] return skillColors[idx]
###* ###*
Return an appropriate height. TODO: refactor Return an appropriate height. TODO: refactor
@method lastWord @method lastWord
@ -341,6 +392,8 @@ GenericHelpers = module.exports =
idx = skillLevelToIndex lvl idx = skillLevelToIndex lvl
['38.25', '30', '16', '8', '0'][idx] ['38.25', '30', '16', '8', '0'][idx]
###* ###*
Return all but the last word of the input text. Return all but the last word of the input text.
@method initialWords @method initialWords
@ -348,6 +401,8 @@ GenericHelpers = module.exports =
initialWords: ( txt ) -> initialWords: ( txt ) ->
if txt && txt.trim() then _.initial( txt.split(' ') ).join(' ') else '' if txt && txt.trim() then _.initial( txt.split(' ') ).join(' ') else ''
###* ###*
Trim the protocol (http or https) from a URL/ Trim the protocol (http or https) from a URL/
@method trimURL @method trimURL
@ -355,27 +410,23 @@ GenericHelpers = module.exports =
trimURL: ( url ) -> trimURL: ( url ) ->
if url && url.trim() then url.trim().replace(/^https?:\/\//i, '') else '' if url && url.trim() then url.trim().replace(/^https?:\/\//i, '') else ''
###*
Convert text to lowercase.
@method toLower
###
toLower: ( txt ) ->
if txt && txt.trim() then txt.toLowerCase() else ''
###* ###*
Convert text to lowercase. Convert text to lowercase.
@method toLower @method toLower
### ###
toUpper: ( txt ) -> toLower: ( txt ) -> if txt && txt.trim() then txt.toLowerCase() else ''
if txt && txt.trim() then txt.toUpperCase() else ''
###* ###*
Return true if either value is truthy. Convert text to lowercase.
@method either @method toLower
### ###
either: ( lhs, rhs, options ) -> toUpper: ( txt ) -> if txt && txt.trim() then txt.toUpperCase() else ''
if lhs || rhs
return options.fn this
###* ###*
Conditional stylesheet link. Creates a link to the specified stylesheet with Conditional stylesheet link. Creates a link to the specified stylesheet with
@ -413,6 +464,8 @@ GenericHelpers = module.exports =
# it when Handlebars is the chosen engine, which is most of the time. # it when Handlebars is the chosen engine, which is most of the time.
ret ret
###* ###*
Perform a generic comparison. Perform a generic comparison.
See: http://doginthehat.com.au/2012/02/comparison-block-helper-for-handlebars-templates See: http://doginthehat.com.au/2012/02/comparison-block-helper-for-handlebars-templates
@ -420,7 +473,7 @@ GenericHelpers = module.exports =
### ###
compare: (lvalue, rvalue, options) -> compare: (lvalue, rvalue, options) ->
if arguments.length < 3 if arguments.length < 3
throw new Error("Handlerbars Helper 'compare' needs 2 parameters") throw new Error "Template helper 'compare' needs 2 parameters"
operator = options.hash.operator || "==" operator = options.hash.operator || "=="
operators = operators =
'==': (l,r) -> l == r '==': (l,r) -> l == r
@ -432,12 +485,27 @@ GenericHelpers = module.exports =
'>=': (l,r) -> l >= r '>=': (l,r) -> l >= r
'typeof': (l,r) -> typeof l == r 'typeof': (l,r) -> typeof l == r
if !operators[operator] if !operators[operator]
throw new Error("Handlerbars Helper 'compare' doesn't know the operator "+operator) throw new Error("Helper 'compare' doesn't know the operator "+operator)
result = operators[operator]( lvalue, rvalue ) result = operators[operator]( lvalue, rvalue )
return if result then options.fn(this) else options.inverse(this) return if result then options.fn(this) else options.inverse(this)
pad: (stringOrArray, padAmount, unused ) ->
stringOrArray = stringOrArray || ''
padAmount = padAmount || 0
ret = ''
PAD = require 'string-padding'
if !String.is stringOrArray
ret = stringOrArray
.map (line) -> PAD line, line.length + Math.abs(padAmount), null, if padAmount < 0 then PAD.LEFT else PAD.RIGHT
.join '\n'
else
ret = PAD stringOrArray, stringOrArray.length + Math.abs(padAmount), null, if padAmount < 0 then PAD.LEFT else PAD.RIGHT
ret
###* ###*
Report an error to the outside world without throwing an exception. Currently Report an error to the outside world without throwing an exception. Currently
relies on kludging the running verb into. opts. relies on kludging the running verb into. opts.
@ -445,6 +513,8 @@ relies on kludging the running verb into. opts.
_reportError = ( code, params ) -> _reportError = ( code, params ) ->
GenericHelpers.opts.errHandler.err( code, params ) GenericHelpers.opts.errHandler.err( code, params )
###* ###*
Format a from/to date range for display. Format a from/to date range for display.
### ###
@ -476,7 +546,7 @@ _fromTo = ( dateA, dateB, fmt, sep, fallback ) ->
dateFrom = dateTemp.format( fmt ) dateFrom = dateTemp.format( fmt )
if _.contains( reserved, dateBTrim ) if _.contains( reserved, dateBTrim )
dateTo = fallback || 'Current' dateTo = fallback || 'Present'
else else
dateTemp = FluentDate.fmt( dateB ) dateTemp = FluentDate.fmt( dateB )
dateTo = dateTemp.format( fmt ) dateTo = dateTemp.format( fmt )
@ -487,6 +557,8 @@ _fromTo = ( dateA, dateB, fmt, sep, fallback ) ->
return dateFrom || dateTo return dateFrom || dateTo
return '' return ''
skillLevelToIndex = ( lvl ) -> skillLevelToIndex = ( lvl ) ->
idx = 0 idx = 0
if String.is( lvl ) if String.is( lvl )
@ -507,6 +579,7 @@ skillLevelToIndex = ( lvl ) ->
idx idx
# Note [1] -------------------------------------------------------------------- # Note [1] --------------------------------------------------------------------
# Make sure it's precisely a string or array since some template engines jam # Make sure it's precisely a string or array since some template engines jam
# their options/context object into the last parameter and we are allowing the # their options/context object into the last parameter and we are allowing the

View File

@ -1,13 +1,14 @@
###* ###*
Template helper definitions for Handlebars. Template helper definitions for Handlebars.
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk. @license MIT. See LICENSE.md for details.
@module handlebars-helpers.js @module handlebars-helpers.js
### ###
HANDLEBARS = require 'handlebars' HANDLEBARS = require 'handlebars'
_ = require 'underscore' _ = require 'underscore'
helpers = require './generic-helpers' helpers = require './generic-helpers'
blockHelpers = require './block-helpers'
###* ###*
Register useful Handlebars helpers. Register useful Handlebars helpers.
@ -15,6 +16,21 @@ Register useful Handlebars helpers.
### ###
module.exports = ( theme, opts ) -> module.exports = ( theme, opts ) ->
helpers.theme = theme helpers.theme = theme
helpers.opts = opts helpers.opts = opts
HANDLEBARS.registerHelper helpers helpers.type = 'handlebars'
wrappedHelpers = _.mapObject helpers, ( hVal, hKey ) ->
if _.isFunction hVal
_.wrap hVal, (func) ->
args = Array.prototype.slice.call arguments
args.shift() # lose the 1st element (func)
args.pop() # lose the last element (the Handlebars options hash)
func.apply @, args
hVal
, @
HANDLEBARS.registerHelper wrappedHelpers
HANDLEBARS.registerHelper blockHelpers
return

View File

@ -1,13 +1,17 @@
###* ###*
Template helper definitions for Underscore. Template helper definitions for Underscore.
@license MIT. Copyright (c) 2016 hacksalot (https://github.com/hacksalot) @license MIT. See LICENSE.md for details.
@module handlebars-helpers.js @module handlebars-helpers.js
### ###
HANDLEBARS = require('handlebars') HANDLEBARS = require('handlebars')
_ = require('underscore') _ = require('underscore')
helpers = require('./generic-helpers') helpers = require('./generic-helpers')
###* ###*
Register useful Underscore helpers. Register useful Underscore helpers.
@method registerHelpers @method registerHelpers
@ -22,3 +26,4 @@ module.exports = ( theme, opts, cssInfo, ctx, eng ) ->
if _.isFunction hVal if _.isFunction hVal
_.bind hVal, ctx _.bind hVal, ctx
, @ , @
return

View File

@ -6,11 +6,9 @@ External API surface for HackMyResume.
###* ###* API facade for HackMyResume. ###
API facade for HackMyCore.
###
HackMyCore = module.exports = module.exports =
verbs: verbs:
build: require './verbs/build' build: require './verbs/build'
@ -31,6 +29,7 @@ HackMyCore = module.exports =
JRSResume: require './core/jrs-resume' JRSResume: require './core/jrs-resume'
FRESHTheme: require './core/fresh-theme' FRESHTheme: require './core/fresh-theme'
JRSTheme: require './core/jrs-theme' JRSTheme: require './core/jrs-theme'
ResumeFactory: require './core/resume-factory'
FluentDate: require './core/fluent-date' FluentDate: require './core/fluent-date'
HtmlGenerator: require './generators/html-generator' HtmlGenerator: require './generators/html-generator'
TextGenerator: require './generators/text-generator' TextGenerator: require './generators/text-generator'

View File

@ -33,7 +33,8 @@ HandlebarsGenerator = module.exports =
return template data return template data
catch catch
throw throw
fluenterror: if template then HMSTATUS.invokeTemplate else HMSTATUS.compileTemplate fluenterror:
HMSTATUS[ if template then 'invokeTemplate' else 'compileTemplate' ]
inner: _error inner: _error

View File

@ -8,8 +8,8 @@ Definition of the UnderscoreGenerator class.
_ = require 'underscore' _ = require 'underscore'
registerHelpers = require '../helpers/underscore-helpers' registerHelpers = require '../helpers/underscore-helpers'
HMSTATUS = require '../core/status-codes' require '../utils/string'
escapeLaTeX = require 'escape-latex'
###* ###*
Perform template-based resume generation using Underscore.js. Perform template-based resume generation using Underscore.js.
@ -17,36 +17,57 @@ Perform template-based resume generation using Underscore.js.
### ###
UnderscoreGenerator = module.exports = UnderscoreGenerator = module.exports =
generateSimple: ( data, tpl ) -> generateSimple: ( data, tpl ) ->
try try
# Compile and run the Handlebars template. # Compile and run the Handlebars template.
template = _.template( tpl ); t = _.template tpl
return template( data ); t data
catch catch
#console.dir _error
HMS = require '../core/status-codes'
throw throw
fluenterror: if template then HMSTATUS.invokeTemplate else HMSTATUS.compileTemplate, fluenterror: HMS[if t then 'invokeTemplate' else 'compileTemplate']
inner: _error inner: _error
generate: ( json, jst, format, cssInfo, opts, theme ) -> generate: ( json, jst, format, cssInfo, opts, theme ) ->
# Tweak underscore's default template delimeters # Tweak underscore's default template delimeters
delims = (opts.themeObj && opts.themeObj.delimeters) || opts.template; delims = (opts.themeObj && opts.themeObj.delimeters) || opts.template;
if opts.themeObj && opts.themeObj.delimeters if opts.themeObj && opts.themeObj.delimeters
delims = _.mapObject delims, (val,key) -> new RegExp( val, "ig") delims = _.mapObject delims, (val,key) -> new RegExp val, "ig"
_.templateSettings = delims; _.templateSettings = delims;
# Strip {# comments #} # Massage resume strings / text
jst = jst.replace delims.comment, '' r = null
switch format
when 'html' then r = json.markdownify()
when 'pdf' then r = json.markdownify()
when 'png' then r = json.markdownify()
when 'latex'
traverse = require 'traverse'
r = traverse(json).map (x) ->
if @isLeaf && String.is @node
return escapeLaTeX @node
@node
else r = json
# Set up the context
ctx = ctx =
r: if format == 'html' || format == 'pdf' || format == 'png' then json.markdownify() else json r: r
filt: opts.filters filt: opts.filters
XML: require 'xml-escape' XML: require 'xml-escape'
RAW: json RAW: json
cssInfo: cssInfo cssInfo: cssInfo
#engine: this #engine: @
headFragment: opts.headFragment || '' headFragment: opts.headFragment || ''
opts: opts opts: opts
registerHelpers theme, opts, cssInfo, ctx, this # Link to our helpers
registerHelpers theme, opts, cssInfo, ctx, @
# Generate!
@generateSimple ctx, jst @generateSimple ctx, jst

View File

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

View File

@ -4,25 +4,26 @@ Safe spawn utility for HackMyResume / FluentCV.
@license MIT. See LICENSE.md for details. @license MIT. See LICENSE.md for details.
### ###
module.exports = ( cmd, args, isSync, callback ) -> ###* Safely spawn a process synchronously or asynchronously without throwing an
exception ###
module.exports = ( cmd, args, isSync, callback, param ) ->
try try
# .spawnSync not available on earlier Node.js, so default to .spawn # .spawnSync not available on earlier Node.js, so default to .spawn
spawn = require('child_process')[ if isSync then 'spawnSync' else 'spawn']; spawn = require('child_process')[ if isSync then 'spawnSync' else 'spawn']
info = spawn cmd, args info = spawn cmd, args
# Check for error depending on whether we're sync or async # Check for error depending on whether we're sync or async TODO: Promises
if !isSync if !isSync
info.on 'error', (err) -> info.on 'error', (err) ->
if callback? then callback err; return callback?(err, param)
else throw cmd: cmd, inner: err return
return return
else else
if info.error if info.error
if callback? then callback err; return callback?(info.error, param)
else throw cmd: cmd, inner: info.error return cmd: cmd, inner: info.error
catch catch
if callback? then callback _error callback?(_error, param)
else throw _error _error

View File

@ -4,6 +4,8 @@ Definition of the SyntaxErrorEx class.
@license MIT. See LICENSE.md for details. @license MIT. See LICENSE.md for details.
### ###
###* ###*
Represents a SyntaxError exception with line and column info. Represents a SyntaxError exception with line and column info.
Collect syntax error information from the provided exception object. The Collect syntax error information from the provided exception object. The
@ -13,13 +15,21 @@ See: http://stackoverflow.com/q/13323356
@class SyntaxErrorEx @class SyntaxErrorEx
### ###
SyntaxErrorEx = ( ex, rawData ) -> class SyntaxErrorEx
lineNum = null constructor: ( ex, rawData ) ->
colNum = null lineNum = null
JSONLint = require 'json-lint' colNum = null
lint = JSONLint rawData, { comments: false } JSONLint = require 'json-lint'
this.line = if lint.error then lint.line else '???' lint = JSONLint rawData, { comments: false }
this.col = if lint.error then lint.character else '???' [@line, @col] = [lint.line, lint.character] if lint.error
if !lint.error
JSONLint = require 'jsonlint'
try
JSONLint.parse rawData
catch
@line = (/on line (\d+)/.exec _error)[1]
SyntaxErrorEx.is = ( ex ) -> ex instanceof SyntaxError SyntaxErrorEx.is = ( ex ) -> ex instanceof SyntaxError
module.exports = SyntaxErrorEx; module.exports = SyntaxErrorEx;

View File

@ -17,39 +17,44 @@ chalk = require('chalk')
AnalyzeVerb = module.exports = Verb.extend ###* An invokable resume analysis command. ###
module.exports = class AnalyzeVerb extends Verb
init: -> @_super 'analyze', analyze constructor: -> super 'analyze', _analyze
###* ###* Private workhorse for the 'analyze' command. ###
Run the 'analyze' command. _analyze = ( sources, dst, opts ) ->
###
analyze = ( sources, dst, opts ) ->
if !sources || !sources.length if !sources || !sources.length
throw @err HMSTATUS.resumeNotFound, { quit: true }
fluenterror: HMSTATUS.resumeNotFound return null
quit: true
nlzrs = _loadInspectors() nlzrs = _loadInspectors()
results = _.map sources, (src) ->
r = ResumeFactory.loadOne src, format: 'FRESH', objectify: true, @
return { } if opts.assert and @hasError()
_.each(sources, (src) -> if r.fluenterror
result = ResumeFactory.loadOne src, format: 'FRESH', objectify: true, @ r.quit = opts.assert
if result.fluenterror @err r.fluenterror, r
this.setError result.fluenterror, result r
else else
_analyze.call @, result, nlzrs, opts _analyzeOne.call @, r, nlzrs, opts
, @) , @
if @hasError() and !opts.assert
@reject @errorCode
else if !@hasError()
@resolve results
results
###* ###* Analyze a single resume. ###
Analyze a single resume. _analyzeOne = ( resumeObject, nlzrs, opts ) ->
###
_analyze = ( resumeObject, nlzrs, opts ) ->
rez = resumeObject.rez rez = resumeObject.rez
safeFormat = safeFormat =
if rez.meta and rez.meta.format and rez.meta.format.startsWith 'FRESH' if rez.meta and rez.meta.format and rez.meta.format.startsWith 'FRESH'
@ -58,13 +63,11 @@ _analyze = ( resumeObject, nlzrs, opts ) ->
this.stat( HMEVENT.beforeAnalyze, { fmt: safeFormat, file: resumeObject.file }) this.stat( HMEVENT.beforeAnalyze, { fmt: safeFormat, file: resumeObject.file })
info = _.mapObject nlzrs, (val, key) -> val.run rez info = _.mapObject nlzrs, (val, key) -> val.run rez
this.stat HMEVENT.afterAnalyze, { info: info } this.stat HMEVENT.afterAnalyze, { info: info }
info
###*
Load inspectors.
###
_loadInspectors = -> _loadInspectors = ->
totals: require '../inspectors/totals-inspector' totals: require '../inspectors/totals-inspector'
coverage: require '../inspectors/gap-inspector' coverage: require '../inspectors/gap-inspector'
keywords: require '../inspectors/keyword-inspector' keywords: require '../inspectors/keyword-inspector'

View File

@ -6,25 +6,25 @@ Implementation of the 'build' verb for HackMyResume.
_ = require('underscore') _ = require 'underscore'
PATH = require('path') PATH = require 'path'
FS = require('fs') FS = require 'fs'
MD = require('marked') MD = require 'marked'
MKDIRP = require('mkdirp') MKDIRP = require 'mkdirp'
extend = require('extend') extend = require 'extend'
parsePath = require('parse-filepath') parsePath = require 'parse-filepath'
RConverter = require('fresh-jrs-converter') RConverter = require 'fresh-jrs-converter'
HMSTATUS = require('../core/status-codes') HMSTATUS = require '../core/status-codes'
HMEVENT = require('../core/event-codes') HMEVENT = require '../core/event-codes'
RTYPES = RTYPES =
FRESH: require('../core/fresh-resume') FRESH: require '../core/fresh-resume'
JRS: require('../core/jrs-resume') JRS: require '../core/jrs-resume'
_opts = require('../core/default-options') _opts = require '../core/default-options'
FRESHTheme = require('../core/fresh-theme') FRESHTheme = require '../core/fresh-theme'
JRSTheme = require('../core/jrs-theme') JRSTheme = require '../core/jrs-theme'
ResumeFactory = require('../core/resume-factory') ResumeFactory = require '../core/resume-factory'
_fmts = require('../core/default-formats') _fmts = require '../core/default-formats'
Verb = require('../verbs/verb') Verb = require '../verbs/verb'
_err = null _err = null
_log = null _log = null
@ -39,10 +39,10 @@ verifyTheme = null
loadTheme = null loadTheme = null
###* An invokable resume generation command. ### ###* An invokable resume generation command. ###
BuildVerb = module.exports = Verb.extend module.exports = class BuildVerb extends Verb
###* Create a new build verb. ### ###* Create a new build verb. ###
init: () -> @_super 'build', build constructor: () -> super 'build', _build
@ -53,23 +53,23 @@ theme file, generate 0..N resumes in the desired formats.
@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 opts Generation options. @param opts Generation options.
### ###
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 return null
prep src, dst, opts _prep.call @, src, dst, opts
# Load input resumes as JSON... # Load input resumes as JSON...
sheetObjects = ResumeFactory.load(src, { sheetObjects = ResumeFactory.load src,
format: null, objectify: false, quit: true, inner: { sort: _opts.sort } format: null, objectify: false, quit: true, inner: { sort: _opts.sort }
}, @); , @
# Explicit check for any resume loading errors... # Explicit check for any resume loading errors...
problemSheets = _.filter( sheetObjects, (so) -> return so.fluenterror ) problemSheets = _.filter sheetObjects, (so) -> so.fluenterror
if( problemSheets && problemSheets.length ) if problemSheets and problemSheets.length
problemSheets[0].quit = true problemSheets[0].quit = true # can't go on
@err problemSheets[0].fluenterror, problemSheets[0] @err problemSheets[0].fluenterror, problemSheets[0]
return null return null
@ -80,27 +80,31 @@ build = ( src, dst, opts ) ->
theme = null theme = null
@stat HMEVENT.beforeTheme, { theme: _opts.theme } @stat HMEVENT.beforeTheme, { theme: _opts.theme }
try try
tFolder = verifyTheme.call @, _opts.theme tFolder = _verifyTheme.call @, _opts.theme
theme = _opts.themeObj = loadTheme tFolder if tFolder.fluenterror
addFreebieFormats theme tFolder.quit = true
catch ex @err tFolder.fluenterror, tFolder
return
theme = _opts.themeObj = _loadTheme tFolder
_addFreebieFormats theme
catch
newEx = newEx =
fluenterror: HMSTATUS.themeLoad fluenterror: HMSTATUS.themeLoad
inner: ex inner: _error
attempted: _opts.theme attempted: _opts.theme
quit: true quit: true
@err HMSTATUS.themeLoad, newEx @err HMSTATUS.themeLoad, newEx
return null return null
@stat HMEVENT.afterTheme, { theme: theme } @stat HMEVENT.afterTheme, theme: theme
# 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, quit: true @err HMSTATUS.invalidFormat, data: inv, theme: theme, quit: true
return null return null
## Merge input resumes, yielding a single source resume. ## Merge input resumes, yielding a single source resume...
rez = null rez = null
if sheets.length > 1 if sheets.length > 1
isFRESH = !sheets[0].basics isFRESH = !sheets[0].basics
@ -108,15 +112,13 @@ build = ( src, dst, opts ) ->
@stat HMEVENT.beforeMerge, { f: _.clone(sheetObjects), mixed: mixed } @stat HMEVENT.beforeMerge, { f: _.clone(sheetObjects), mixed: mixed }
if mixed if mixed
@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 )
@stat HMEVENT.afterMerge, { r: rez } @stat HMEVENT.afterMerge, { r: rez }
else else
rez = sheets[0]; rez = sheets[0];
# Convert the merged source resume to the theme's format, if necessary # Convert the merged source resume to the theme's format, if necessary..
orgFormat = if rez.basics then 'JRS' else 'FRESH'; orgFormat = if rez.basics then 'JRS' else 'FRESH';
toFormat = if theme.render then 'JRS' else 'FRESH'; toFormat = if theme.render then 'JRS' else 'FRESH';
if toFormat != orgFormat if toFormat != orgFormat
@ -125,30 +127,42 @@ build = ( src, dst, opts ) ->
@stat HMEVENT.afterInlineConvert, { file: sheetObjects[0].file, fmt: toFormat } @stat HMEVENT.afterInlineConvert, { file: sheetObjects[0].file, fmt: toFormat }
# Announce the theme # Announce the 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
_rezObj = new (RTYPES[ toFormat ])().parseJSON( rez ); _rezObj = new (RTYPES[ toFormat ])().parseJSON( rez );
# Expand output resumes... # Expand output resumes...
targets = expand( dst, theme ); targets = _expand dst, theme
# Run the transformation! # Run the transformation!
_.each targets, (t) -> _.each targets, (t) ->
t.final = single.call this, t, theme, targets return { } if @hasError() and opts.assert
t.final = _single.call @, t, theme, targets
if t.final?.fluenterror
t.final.quit = opts.assert
@err t.final.fluenterror, t.final
return
, @ , @
# Don't send the client back empty-handed results =
sheet: _rezObj sheet: _rezObj
targets: targets targets: targets
processed: targets processed: targets
if @hasError() and !opts.assert
@reject results
else if !@hasError()
@resolve results
results
###* ###*
Prepare for a BUILD run. Prepare for a BUILD run.
### ###
prep = ( src, dst, opts ) -> _prep = ( src, dst, opts ) ->
# Cherry-pick options //_opts = extend( true, _opts, opts ); # Cherry-pick options //_opts = extend( true, _opts, opts );
_opts.theme = (opts.theme && opts.theme.toLowerCase().trim()) || 'modern'; _opts.theme = (opts.theme && opts.theme.toLowerCase().trim()) || 'modern';
@ -162,6 +176,16 @@ prep = ( src, dst, opts ) ->
_opts.noTips = opts.noTips _opts.noTips = opts.noTips
_opts.debug = opts.debug _opts.debug = opts.debug
_opts.sort = opts.sort _opts.sort = opts.sort
that = @
# Set up callbacks for internal generators
_opts.onTransform = (info) ->
that.stat HMEVENT.afterTransform, info; return
_opts.beforeWrite = (info) ->
that.stat HMEVENT.beforeWrite, info; return
_opts.afterWrite = (info) ->
that.stat HMEVENT.afterWrite, info; return
# If two or more files are passed to the GENERATE command and the TO # If two or more files are passed to the GENERATE command and the TO
# keyword is omitted, the last file specifies the output file. # keyword is omitted, the last file specifies the output file.
@ -176,7 +200,7 @@ TODO: Refactor.
@param targInfo Information for the target resume. @param targInfo Information for the target resume.
@param theme A FRESHTheme or JRSTheme object. @param theme A FRESHTheme or JRSTheme object.
### ###
single = ( targInfo, theme, finished ) -> _single = ( targInfo, theme, finished ) ->
ret = null ret = null
ex = null ex = null
@ -185,24 +209,25 @@ single = ( targInfo, theme, finished ) ->
try try
if !targInfo.fmt if !targInfo.fmt
return return { }
fType = targInfo.fmt.outFormat fType = targInfo.fmt.outFormat
fName = PATH.basename f, '.' + fType fName = PATH.basename f, '.' + fType
theFormat = null theFormat = null
@.stat HMEVENT.beforeGenerate, @stat HMEVENT.beforeGenerate,
fmt: targInfo.fmt.outFormat fmt: targInfo.fmt.outFormat
file: PATH.relative(process.cwd(), f) file: PATH.relative process.cwd(), f
_opts.targets = finished; _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) ->
(fmt) -> return fmt.name == targInfo.fmt.outFormat )[0]; return fmt.name == targInfo.fmt.outFormat
MKDIRP.sync( PATH.dirname( f ) ); # Ensure dest folder exists; )[0];
ret = theFormat.gen.generate( _rezObj, f, _opts ); MKDIRP.sync PATH.dirname( f )
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
# gets "for free". # gets "for free".
@ -211,38 +236,32 @@ single = ( targInfo, theme, finished ) ->
return fmt.name == targInfo.fmt.outFormat return fmt.name == targInfo.fmt.outFormat
)[0]; )[0];
outFolder = PATH.dirname f outFolder = PATH.dirname f
MKDIRP.sync( outFolder ); # Ensure dest folder exists; MKDIRP.sync outFolder # Ensure dest folder exists;
ret = theFormat.gen.generate( _rezObj, f, _opts ); ret = theFormat.gen.generate _rezObj, f, _opts
catch e catch e
# Catch any errors caused by generating this file and don't let them ex = e
# propagate -- typically we want to continue processing other formats
# even if this format failed.
ex = e;
this.stat HMEVENT.afterGenerate, this.stat HMEVENT.afterGenerate,
fmt: targInfo.fmt.outFormat, fmt: targInfo.fmt.outFormat
file: PATH.relative( process.cwd(), f ), file: PATH.relative process.cwd(), f
error: ex error: ex
if ex if ex
if ex.fluenterror if ex.fluenterror
this.err( ex.fluenterror, ex ); ret = ex
else else
this.err( HMSTATUS.generateError, { inner: ex } ); ret = fluenterror: HMSTATUS.generateError, inner: ex
ret
return ret
###* 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
{ format: pathInfo.extname.substr(1) ),
format: pathInfo.extname.substr(1)
}),
(t) -> t.format == 'all' || theme.hasFormat( t.format ) (t) -> t.format == 'all' || theme.hasFormat( t.format )
@ -256,7 +275,7 @@ that declares an HTML format; the theme doesn't have to provide an explicit
PNG template. PNG template.
@param theTheme A FRESHTheme or JRSTheme object. @param theTheme A FRESHTheme or JRSTheme object.
### ###
addFreebieFormats = ( theTheme ) -> _addFreebieFormats = ( theTheme ) ->
# Add freebie formats (JSON, YAML, PNG) every theme gets... # Add freebie formats (JSON, YAML, PNG) every theme gets...
# Add HTML-driven PNG only if the theme has an HTML format. # Add HTML-driven PNG only if the theme has an HTML format.
theTheme.formats.json = theTheme.formats.json || { theTheme.formats.json = theTheme.formats.json || {
@ -282,7 +301,7 @@ Expand output files. For example, "foo.all" should be expanded to
@param dst An array of output files as specified by the user. @param dst An array of output files as specified by the user.
@param theTheme A FRESHTheme or JRSTheme object. @param theTheme A FRESHTheme or JRSTheme object.
### ###
expand = ( dst, theTheme ) -> _expand = ( dst, theTheme ) ->
# Set up the destination collection. It's either the array of files passed # Set up the destination collection. It's either the array of files passed
# by the user or 'out/resume.all' if no targets were specified. # by the user or 'out/resume.all' if no targets were specified.
@ -309,7 +328,7 @@ expand = ( dst, theTheme ) ->
###* ###*
Verify the specified theme name/path. Verify the specified theme name/path.
### ###
verifyTheme = ( themeNameOrPath ) -> _verifyTheme = ( themeNameOrPath ) ->
tFolder = PATH.join( tFolder = PATH.join(
parsePath( require.resolve('fresh-themes') ).dirname, parsePath( require.resolve('fresh-themes') ).dirname,
'/themes/', '/themes/',
@ -319,7 +338,7 @@ verifyTheme = ( themeNameOrPath ) ->
if !exists( tFolder ) if !exists( tFolder )
tFolder = PATH.resolve themeNameOrPath tFolder = PATH.resolve themeNameOrPath
if !exists tFolder if !exists tFolder
this.err HMSTATUS.themeNotFound, { data: _opts.theme } return fluenterror: HMSTATUS.themeNotFound, data: _opts.theme
tFolder tFolder
@ -328,7 +347,7 @@ verifyTheme = ( themeNameOrPath ) ->
Load the specified theme, which could be either a FRESH theme or a JSON Resume Load the specified theme, which could be either a FRESH theme or a JSON Resume
theme. theme.
### ###
loadTheme = ( tFolder ) -> _loadTheme = ( tFolder ) ->
# Create a FRESH or JRS theme object # Create a FRESH or JRS theme object
theTheme = theTheme =

View File

@ -15,58 +15,70 @@ HMEVENT = require('../core/event-codes');
ConvertVerb = module.exports = Verb.extend module.exports = class ConvertVerb extends Verb
init: -> @_super 'convert', convert constructor: -> super 'convert', _convert
###* ###* Private workhorse method. Convert 0..N resumes between FRESH and JRS
Convert between FRESH and JRS formats. formats. ###
###
convert = ( srcs, dst, opts ) ->
# Housekeeping _convert = ( srcs, dst, opts ) ->
throw { fluenterror: 6, quit: true } if !srcs || !srcs.length
# Housekeeping...
if !srcs || !srcs.length
@err HMSTATUS.resumeNotFound, { quit: true }
return null
if !dst || !dst.length if !dst || !dst.length
if srcs.length == 1 if srcs.length == 1
throw { fluenterror: HMSTATUS.inputOutputParity, quit: true }; @err HMSTATUS.inputOutputParity, { quit: true }
else if srcs.length == 2 else if srcs.length == 2
dst = dst || []; dst.push( srcs.pop() ) dst = dst || []; dst.push( srcs.pop() )
else else
throw fluenterror: HMSTATUS.inputOutputParity, quit: true @err HMSTATUS.inputOutputParity, { quit: true }
if srcs && dst && srcs.length && dst.length && srcs.length != dst.length if srcs && dst && srcs.length && dst.length && srcs.length != dst.length
throw fluenterror: HMSTATUS.inputOutputParity quit: true @err HMSTATUS.inputOutputParity, { quit: true }
# Load source resumes # Load source resumes
_.each(srcs, ( src, idx ) -> results = _.map srcs, ( src, idx ) ->
return { } if opts.assert and @hasError()
r = _convertOne.call @, src, dst, idx
if r.fluenterror
r.quit = opts.assert
@err r.fluenterror, r
r
, @
# Load the resume if @hasError() and !opts.assert
rinfo = ResumeFactory.loadOne src, @reject results
format: null, objectify: true, throw: false else if !@hasError()
@resolve results
results
# If a load error occurs, report it and move on to the next file (if any)
if rinfo.fluenterror
this.err rinfo.fluenterror, rinfo
return
s = rinfo.rez
srcFmt =
if ((s.basics && s.basics.imp) || s.imp).orgFormat == 'JRS'
then 'JRS' else 'FRESH'
targetFormat = if srcFmt == 'JRS' then 'FRESH' else 'JRS'
this.stat HMEVENT.beforeConvert, ###* Private workhorse method. Convert a single resume. ###
srcFile: rinfo.file _convertOne = (src, dst, idx) ->
srcFmt: srcFmt # Load the resume
dstFile: dst[idx] rinfo = ResumeFactory.loadOne src, format: null, objectify: true
dstFmt: targetFormat
# Save it to the destination format # If a load error occurs, report it and move on to the next file (if any)
s.saveAs dst[idx], targetFormat if rinfo.fluenterror
return return rinfo
, @) s = rinfo.rez
srcFmt =
if ((s.basics && s.basics.imp) || s.imp).orgFormat == 'JRS'
then 'JRS' else 'FRESH'
targetFormat = if srcFmt == 'JRS' then 'FRESH' else 'JRS'
return this.stat HMEVENT.beforeConvert,
srcFile: rinfo.file
srcFmt: srcFmt
dstFile: dst[idx]
dstFmt: targetFormat
# Save it to the destination format
s.saveAs dst[idx], targetFormat
s

Some files were not shown because too many files have changed in this diff Show More