1
0
mirror of https://github.com/JuanCanham/HackMyResume.git synced 2024-11-21 16:00:11 +00:00

Merge branch 'dev' into docs-2.0

This commit is contained in:
hacksalot 2018-02-12 00:35:45 -05:00
commit 031b666b0a
112 changed files with 2831 additions and 2247 deletions

View File

@ -16,9 +16,6 @@ install:
- wkhtmltoimage -V - wkhtmltoimage -V
language: node_js language: node_js
node_js: node_js:
- "4.0"
- "4"
- "5"
- "6" - "6"
- "7" - "7"
- "8" - "8"

View File

@ -39,39 +39,40 @@ module.exports = function (grunt) {
all: { src: ['test/*.js'] } all: { src: ['test/*.js'] }
}, },
jsdoc : { // jsdoc : {
dist : { // dist : {
src: ['src/**/*.js'], // src: ['src/**/*.js'],
options: { // options: {
private: true, // private: true,
destination: 'doc' // destination: 'doc'
} // }
} // }
}, // },
clean: { clean: {
test: ['test/sandbox'], test: ['test/sandbox'],
dist: ['dist'] dist: ['dist']
}, },
yuidoc: { // yuidoc: {
compile: { // compile: {
name: '<%= pkg.name %>', // name: '<%= pkg.name %>',
description: '<%= pkg.description %>', // description: '<%= pkg.description %>',
version: '<%= pkg.version %>', // version: '<%= pkg.version %>',
url: '<%= pkg.homepage %>', // url: '<%= pkg.homepage %>',
options: { // options: {
paths: 'src/', // paths: 'src/',
outdir: 'docs/' // outdir: 'docs/'
} // }
} // }
}, // },
jshint: { jshint: {
options: { options: {
laxcomma: true, laxcomma: true,
expr: true, expr: true,
eqnull: true eqnull: true,
esversion: 6
}, },
all: ['Gruntfile.js', 'dist/cli/**/*.js', 'test/*.js'] all: ['Gruntfile.js', 'dist/cli/**/*.js', 'test/*.js']
} }
@ -82,22 +83,22 @@ module.exports = function (grunt) {
grunt.loadNpmTasks('grunt-contrib-coffee'); grunt.loadNpmTasks('grunt-contrib-coffee');
grunt.loadNpmTasks('grunt-contrib-copy'); grunt.loadNpmTasks('grunt-contrib-copy');
grunt.loadNpmTasks('grunt-simple-mocha'); grunt.loadNpmTasks('grunt-simple-mocha');
grunt.loadNpmTasks('grunt-contrib-yuidoc'); //grunt.loadNpmTasks('grunt-contrib-yuidoc');
grunt.loadNpmTasks('grunt-jsdoc'); //grunt.loadNpmTasks('grunt-jsdoc');
grunt.loadNpmTasks('grunt-contrib-jshint'); grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-clean'); grunt.loadNpmTasks('grunt-contrib-clean');
// Use 'grunt test' for local testing // Use 'grunt test' for local testing
grunt.registerTask('test', 'Test the HackMyResume application.', grunt.registerTask('test', 'Test the HackMyResume application.',
function( config ) { function( config ) {
grunt.task.run(['clean:test','build','jshint','simplemocha:all']); grunt.task.run(['clean:test','build',/*'jshint',*/'simplemocha:all']);
}); });
// Use 'grunt document' to build docs // Use 'grunt document' to build docs
grunt.registerTask('document', 'Generate HackMyResume documentation.', // grunt.registerTask('document', 'Generate HackMyResume documentation.',
function( config ) { // function( config ) {
grunt.task.run( ['jsdoc'] ); // grunt.task.run( ['jsdoc'] );
}); // });
// Use 'grunt build' to build HMR // Use 'grunt build' to build HMR
grunt.registerTask('build', 'Build the HackMyResume application.', grunt.registerTask('build', 'Build the HackMyResume application.',

50
dist/cli/error.js vendored
View File

@ -1,11 +1,11 @@
/**
Error-handling routines for HackMyResume.
@module cli/error
@license MIT. See LICENSE.md for details.
*/
(function() { (function() {
/**
Error-handling routines for HackMyResume.
@module cli/error
@license MIT. See LICENSE.md for details.
*/
/** Error handler for HackMyResume. All errors are handled here.
@class ErrorHandler */
var ErrorHandler, FCMD, FS, HMSTATUS, M2C, PATH, PKG, SyntaxErrorEx, WRAP, YAML, _defaultLog, assembleError, chalk, extend, printf; var ErrorHandler, FCMD, FS, HMSTATUS, M2C, PATH, PKG, SyntaxErrorEx, WRAP, YAML, _defaultLog, assembleError, chalk, extend, printf;
HMSTATUS = require('../core/status-codes'); HMSTATUS = require('../core/status-codes');
@ -34,11 +34,6 @@ Error-handling routines for HackMyResume.
require('string.prototype.startswith'); require('string.prototype.startswith');
/** Error handler for HackMyResume. All errors are handled here.
@class ErrorHandler
*/
ErrorHandler = module.exports = { ErrorHandler = module.exports = {
init: function(debug, assert, silent) { init: function(debug, assert, silent) {
this.debug = debug; this.debug = debug;
@ -49,14 +44,20 @@ Error-handling routines for HackMyResume.
}, },
err: function(ex, shouldExit) { err: function(ex, shouldExit) {
var o, objError, stack, stackTrace; var o, objError, stack, stackTrace;
// Short-circuit logging output if --silent is on
o = this.silent ? function() {} : _defaultLog; o = this.silent ? function() {} : _defaultLog;
if (ex.pass) { if (ex.pass) {
// Special case; can probably be removed.
throw ex; throw ex;
} }
// Load error messages
this.msgs = this.msgs || require('./msg').errors; this.msgs = this.msgs || require('./msg').errors;
// Handle packaged HMR exceptions
if (ex.fluenterror) { if (ex.fluenterror) {
// Output the error message
objError = assembleError.call(this, ex); objError = assembleError.call(this, ex);
o(this['format_' + objError.etype](objError.msg)); o(this['format_' + objError.etype](objError.msg));
// Output the stack (sometimes)
if (objError.withStack) { if (objError.withStack) {
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));
@ -72,6 +73,7 @@ Error-handling routines for HackMyResume.
return process.exit(ex.fluenterror); return process.exit(ex.fluenterror);
} }
} else { } else {
// Handle raw exceptions
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) {
@ -113,14 +115,16 @@ Error-handling routines for HackMyResume.
quit = false; quit = false;
break; break;
case HMSTATUS.resumeNotFound: case HMSTATUS.resumeNotFound:
msg = M2C(this.msgs.resumeNotFound.msg, 'yellow'); //msg = M2C( this.msgs.resumeNotFound.msg, 'yellow' );
msg += M2C(FS.readFileSync(PATH.resolve(__dirname, 'help/' + ex.verb + '.txt'), 'utf8'), 'white', 'yellow');
break; break;
case HMSTATUS.missingCommand: case HMSTATUS.missingCommand:
msg = M2C(this.msgs.missingCommand.msg + " (", 'yellow'); // msg = M2C( this.msgs.missingCommand.msg + " (", 'yellow');
msg += Object.keys(FCMD.verbs).map(function(v, idx, ar) { // msg += Object.keys( FCMD.verbs ).map( (v, idx, ar) ->
return (idx === ar.length - 1 ? chalk.yellow('or ') : '') + chalk.yellow.bold(v.toUpperCase()); // return ( if idx == ar.length - 1 then chalk.yellow('or ') else '') +
}).join(chalk.yellow(', ')) + chalk.yellow(").\n\n"); // chalk.yellow.bold(v.toUpperCase());
msg += chalk.gray(FS.readFileSync(PATH.resolve(__dirname, '../cli/use.txt'), 'utf8')); // ).join( chalk.yellow(', ')) + chalk.yellow(").\n\n");
msg += M2C(FS.readFileSync(PATH.resolve(__dirname, 'help/use.txt'), 'utf8'), 'white', 'yellow');
break; break;
case HMSTATUS.invalidCommand: case HMSTATUS.invalidCommand:
msg = printf(M2C(this.msgs.invalidCommand.msg, 'yellow'), ex.attempted); msg = printf(M2C(this.msgs.invalidCommand.msg, 'yellow'), ex.attempted);
@ -228,6 +232,7 @@ Error-handling routines for HackMyResume.
etype = 'error'; etype = 'error';
break; break;
case HMSTATUS.createError: case HMSTATUS.createError:
// inner.code could be EPERM, EACCES, etc
msg = printf(M2C(this.msgs.createError.msg), ex.inner.path); msg = printf(M2C(this.msgs.createError.msg), ex.inner.path);
etype = 'error'; etype = 'error';
break; break;
@ -261,15 +266,20 @@ Error-handling routines for HackMyResume.
break; break;
case HMSTATUS.unknownSchema: case HMSTATUS.unknownSchema:
msg = M2C(this.msgs.unknownSchema.msg[0]); msg = M2C(this.msgs.unknownSchema.msg[0]);
//msg += "\n" + M2C( @msgs.unknownSchema.msg[1], 'yellow' )
etype = 'error'; etype = 'error';
break; break;
case HMSTATUS.themeHelperLoad: case HMSTATUS.themeHelperLoad:
msg = printf(M2C(this.msgs.themeHelperLoad.msg), ex.glob); msg = printf(M2C(this.msgs.themeHelperLoad.msg), ex.glob);
etype = 'error'; etype = 'error';
break;
case HMSTATUS.invalidSchemaVersion:
msg = printf(M2C(this.msgs.invalidSchemaVersion.msg), ex.data);
etype = 'error';
} }
return { return {
msg: msg, msg: msg, // The error message to display
withStack: withStack, withStack: withStack, // Whether to include the stack
quit: quit, quit: quit,
etype: etype etype: etype
}; };

25
dist/cli/help/analyze.txt vendored Normal file
View File

@ -0,0 +1,25 @@
**analyze** | Analyze a resume for statistical insight
Usage:
**hackmyresume ANALYZE <resume>**
The ANALYZE command evaluates the specified resume(s) for
coverage, duration, gaps, keywords, and other metrics.
This command can be run against multiple resumes. Each
will be analyzed in turn.
Parameters:
**<resume>**
Path to a FRESH or JRS resume. Multiple resumes can be
specified, separated by spaces.
hackmyresume ANALYZE resume.json
hackmyresume ANALYZE r1.json r2.json r3.json
Options:
**None.**

69
dist/cli/help/build.txt vendored Normal file
View File

@ -0,0 +1,69 @@
**build** | Generate themed resumes in multiple formats
Usage:
**hackmyresume BUILD <resume> TO <target> [--theme]**
**[--pdf] [--no-escape] [--private]**
The BUILD command generates themed resumes and CVs in
multiple formats. Use it to create outbound resumes in
specific formats such HTML, MS Word, and PDF.
Parameters:
**<resume>**
Path to a FRESH or JRS resume (*.json) containing your
resume data. Multiple resumes may be specified.
If multiple resumes are specified, they will be merged
into a single resume prior to transformation.
**<target>**
Path to the desired output resume. Multiple resumes
may be specified. The file extension will determine
the format.
.all Generate all supported formats
.html HTML 5
.doc MS Word
.pdf Adobe Acrobat PDF
.txt plain text
.md Markdown
.png PNG Image
.latex LaTeX
Note: not all formats are supported by all themes!
Check the theme's documentation for details or use
the .all extension to build all available formats.
Options:
**--theme -t <theme-name-or-path>**
Path to a FRESH or JSON Resume theme OR the name of a
built-in theme. Valid theme names are 'modern',
'positive', 'compact', 'awesome', and 'basis'.
**--pdf -p <engine>**
Specify the PDF engine to use. Legal values are
'none', 'wkhtmltopdf', 'phantom', or 'weasyprint'.
**--no-escape**
Disable escaping / encoding of resume data during
resume generation. Handlebars themes only.
**--private**
Include resume fields marked as private.
Notes:
The BUILD command can be run against multiple source as well
as multiple target resumes. If multiple source resumes are
provided, they will be merged into a single source resume
before generation. If multiple output resumes are provided,
each will be generated in turn.

33
dist/cli/help/convert.txt vendored Normal file
View File

@ -0,0 +1,33 @@
**convert** | Convert resumes between FRESH and JRS formats
Usage:
**hackmyresume CONVERT <resume> TO <target> [--format]**
The CONVERT command converts one or more resume documents
between the FRESH Resume Schema and JSON Resume formats.
Parameters:
**<resume>**
Path to a FRESH or JRS resume. Multiple resumes can be
specified.
**<targets>**
The path of the converted resume. Multiple resumes can
be specified, one per provided input resume.
Options:
**--format -f <fmt>**
The desired format for the new resume(s). Valid values
are 'FRESH', 'JRS', or, to target the latest edge
version of the JSON Resume Schema, 'JRS@1'.
If this parameter is omitted, the destination format
will be inferred from the source resume's format. If
the source format is FRESH, the destination format
will be JSON Resume, and vice-versa.

23
dist/cli/help/help.txt vendored Normal file
View File

@ -0,0 +1,23 @@
**help** | View help on a specific HackMyResume command
Usage:
**hackmyresume HELP [<command>]**
The HELP command displays help information for a specific
HackMyResume command, including the HELP command itself.
Parameters:
**<command>**
The HackMyResume command to view help information for.
Must be BUILD, NEW, CONVERT, ANALYZE, VALIDATE, PEEK,
or HELP.
hackmyresume help convert
hackmyresume help help
Options:
**None.**

29
dist/cli/help/new.txt vendored Normal file
View File

@ -0,0 +1,29 @@
**new** | Create a new FRESH or JRS resume document
Usage:
**hackmyresume NEW <fileName> [--format]**
The NEW command generates a new resume document in FRESH
or JSON Resume format. This document can serve as an
official source of truth for your resume and career data
as well an input to tools like HackMyResume.
Parameters:
**<fileName>**
The filename (relative or absolute path) of the resume
to be created. Multiple resume paths can be specified,
and each will be created in turn.
hackmyresume NEW resume.json
hackmyresume NEW r1.json foo/r2.json ../r3.json
Options:
**--format -f <fmt>**
The desired format for the new resume(s). Valid values
are 'FRESH', 'JRS', or, to target the latest edge
version of the JSON Resume Schema, 'JRS@1'.

31
dist/cli/help/peek.txt vendored Normal file
View File

@ -0,0 +1,31 @@
**peek** | View portions of a resume from the command line
Usage:
**hackmyresume PEEK <resume> <at>**
The PEEK command displays a specific piece or part of the
resume without requiring the resume to be opened in an
editor.
Parameters:
**<resume>**
Path to a FRESH or JRS resume. Multiple resumes can be
specified, separated by spaces.
hackmyresume PEEK r1.json r2.json r3.json "employment.history[2]"
**<at>**
The resume property or field to be displayed. Can be
any valid resume path, for example:
education[0]
info.name
employment.history[3].start
Options:
**None.**

70
dist/cli/help/use.txt vendored Normal file
View File

@ -0,0 +1,70 @@
**HackMyResume** | A Swiss Army knife for resumes and CVs
Usage:
**hackmyresume [--version] [--help] [--silent] [--debug]**
**[--options] [--no-colors] <command> [<args>]**
Commands: (type "hackmyresume help COMMAND" for details)
**BUILD** Build your resume to the destination format(s).
**ANALYZE** Analyze your resume for keywords, gaps, and metrics.
**VALIDATE** Validate your resume for errors and typos.
**NEW** Create a new resume in FRESH or JSON Resume format.
**CONVERT** Convert your resume between FRESH and JSON Resume.
**PEEK** View a specific field or element on your resume.
**HELP** View help on a specific HackMyResume command.
Common Tasks:
Generate a resume in a specific format (HTML, Word, PDF, etc.)
**hackmyresume build rez.json to out/rez.html**
**hackmyresume build rez.json to out/rez.doc**
**hackmyresume build rez.json to out/rez.pdf**
**hackmyresume build rez.json to out/rez.txt**
**hackmyresume build rez.json to out/rez.md**
**hackmyresume build rez.json to out/rez.png**
**hackmyresume build rez.json to out/rez.tex**
Build a resume to ALL available formats:
**hackmyresume build rez.json to out/rez.all**
Build a resume with a specific theme:
**hackmyresume build rez.json to out/rez.all -t themeName**
Create a new empty resume:
**hackmyresume new rez.json**
Convert a resume between FRESH and JRS formats:
**hackmyresume convert rez.json converted.json**
Analyze a resume for important metrics
**hackmyresume analyze rez.json**
Find more resume themes:
**https://www.npmjs.com/search?q=jsonresume-theme**
**https://www.npmjs.com/search?q=fresh-theme**
**https://github.com/fresh-standard/fresh-themes**
Validate a resume's structure and syntax:
**hackmyresume validate resume.json**
View help on a specific command:
**hackmyresume help [build|convert|new|analyze|validate|peek|help]**
Submit a bug or request:
**https://githut.com/hacksalot/HackMyResume/issues**
HackMyResume is free and open source software published
under the MIT license. For more information, visit the
HackMyResume website or GitHub project page.

26
dist/cli/help/validate.txt vendored Normal file
View File

@ -0,0 +1,26 @@
**validate** | Validate a resume for correctness
Usage:
**hackmyresume VALIDATE <resume> [--assert]**
The VALIDATE command validates a FRESH or JRS document
against its governing schema, verifying that the resume
is correctly structured and formatted.
Parameters:
**<resume>**
Path to a FRESH or JRS resume. Multiple resumes can be
specified.
hackmyresume ANALYZE resume.json
hackmyresume ANALYZE r1.json r2.json r3.json
Options:
**--assert -a**
Tell HackMyResume to return a non-zero process exit
code if a resume fails to validate.

146
dist/cli/main.js vendored
View File

@ -1,11 +1,28 @@
/**
Definition of the `main` function.
@module cli/main
@license MIT. See LICENSE.md for details.
*/
(function() { (function() {
/**
Definition of the `main` function.
@module cli/main
@license MIT. See LICENSE.md for details.
*/
/* Invoke a HackMyResume verb. */
/* Success handler for verb invocations. Calls process.exit by default */
/* Init options prior to setting up command infrastructure. */
/* Massage command-line args and setup Commander.js. */
/*
Initialize HackMyResume options.
TODO: Options loading is a little hacky, for two reasons:
- Commander.js idiosyncracies
- Need to accept JSON inputs from the command line.
*/
/* Simple logging placeholder. */
/*
A callable implementation of the HackMyResume CLI. Encapsulates the command
line interface as a single method accepting a parameter array.
@alias module:cli/main.main
@param rawArgs {Array} An array of command-line parameters. Will either be
process.argv (in production) or custom parameters (in test).
*/
/* Split multiple command-line filenames by the 'TO' keyword */
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; 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');
@ -50,15 +67,6 @@ Definition of the `main` function.
_exitCallback = null; _exitCallback = null;
/*
A callable implementation of the HackMyResume CLI. Encapsulates the command
line interface as a single method accepting a parameter array.
@alias module:cli/main.main
@param rawArgs {Array} An array of command-line parameters. Will either be
process.argv (in production) or custom parameters (in test).
*/
main = module.exports = function(rawArgs, exitCallback) { main = module.exports = function(rawArgs, exitCallback) {
var args, initInfo, program; var args, initInfo, program;
initInfo = initialize(rawArgs, exitCallback); initInfo = initialize(rawArgs, exitCallback);
@ -66,32 +74,46 @@ Definition of the `main` function.
return; return;
} }
args = initInfo.args; args = initInfo.args;
// Create the top-level (application) command...
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;
program.command('new')["arguments"]('<sources...>').option('-f --format <fmt>', 'FRESH or JRS format', 'FRESH').alias('create').description('Create resume(s) in FRESH or JSON RESUME format.').action((function(sources) { // Create the NEW command
program.command('new').arguments('<sources...>').option('-f --format <fmt>', 'FRESH or JRS format', 'FRESH').alias('create').description('Create resume(s) in FRESH or JSON RESUME format.').action((function(sources) {
execute.call(this, sources, [], this.opts(), logMsg); execute.call(this, sources, [], this.opts(), logMsg);
})); }));
program.command('validate')["arguments"]('<sources...>').description('Validate a resume in FRESH or JSON RESUME format.').action(function(sources) { // Create the VALIDATE command
program.command('validate').arguments('<sources...>').description('Validate a resume in FRESH or JSON RESUME format.').action(function(sources) {
execute.call(this, sources, [], this.opts(), logMsg); execute.call(this, sources, [], this.opts(), logMsg);
}); });
program.command('convert').description('Convert a resume to/from FRESH or JSON RESUME format.').action(function() { // Create the CONVERT command
program.command('convert').description('Convert a resume to/from FRESH or JSON RESUME format.').option('-f --format <fmt>', 'FRESH or JRS format and optional version', void 0).action(function() {
var x; var x;
x = splitSrcDest.call(this); x = splitSrcDest.call(this);
execute.call(this, x.src, x.dst, this.opts(), logMsg); execute.call(this, x.src, x.dst, this.opts(), logMsg);
}); });
program.command('analyze')["arguments"]('<sources...>').option('--private', 'Include resume fields marked as private', false).description('Analyze one or more resumes.').action(function(sources) { // Create the ANALYZE command
program.command('analyze').arguments('<sources...>').option('--private', 'Include resume fields marked as private', false).description('Analyze one or more resumes.').action(function(sources) {
execute.call(this, sources, [], this.opts(), logMsg); execute.call(this, sources, [], this.opts(), logMsg);
}); });
program.command('peek')["arguments"]('<sources...>').description('Peek at a resume field or section').action(function(sources, sectionOrField) { // Create the PEEK command
program.command('peek').arguments('<sources...>').description('Peek at a resume field or section').action(function(sources, sectionOrField) {
var dst; var dst;
dst = sources && sources.length > 1 ? [sources.pop()] : []; dst = (sources && sources.length > 1) ? [sources.pop()] : [];
execute.call(this, sources, dst, this.opts(), logMsg); execute.call(this, sources, dst, this.opts(), logMsg);
}); });
// Create the BUILD command
program.command('build').alias('generate').option('-t --theme <theme>', 'Theme name or path').option('-n --no-prettify', 'Disable HTML prettification', true).option('-c --css <option>', 'CSS linking / embedding').option('-p --pdf <engine>', 'PDF generation engine').option('--no-sort', 'Sort resume sections by date', false).option('--tips', 'Display theme tips and warnings.', false).option('--private', 'Include resume fields marked as private', false).option('--no-escape', "Turn off encoding in Handlebars themes.", false).description('Generate resume to multiple formats').action(function(sources, targets, options) { program.command('build').alias('generate').option('-t --theme <theme>', 'Theme name or path').option('-n --no-prettify', 'Disable HTML prettification', true).option('-c --css <option>', 'CSS linking / embedding').option('-p --pdf <engine>', 'PDF generation engine').option('--no-sort', 'Sort resume sections by date', false).option('--tips', 'Display theme tips and warnings.', false).option('--private', 'Include resume fields marked as private', false).option('--no-escape', "Turn off encoding in Handlebars themes.", false).description('Generate resume to multiple formats').action(function(sources, targets, options) {
var x; var x;
x = splitSrcDest.call(this); x = splitSrcDest.call(this);
execute.call(this, x.src, x.dst, this.opts(), logMsg); execute.call(this, x.src, x.dst, this.opts(), logMsg);
}); });
// Create the HELP command
program.command('help').arguments('[command]').description('Get help on a HackMyResume command').action(function(cmd) {
var manPage;
cmd = cmd || 'use';
manPage = FS.readFileSync(PATH.join(__dirname, 'help/' + cmd + '.txt'), 'utf8');
_out.log(M2C(manPage, 'white', 'yellow.bold'));
});
program.parse(args); program.parse(args);
if (!program.args.length) { if (!program.args.length) {
throw { throw {
@ -100,9 +122,6 @@ Definition of the `main` function.
} }
}; };
/* Massage command-line args and setup Commander.js. */
initialize = function(ar, exitCallback) { initialize = function(ar, exitCallback) {
var o; var o;
_exitCallback = exitCallback || process.exit; _exitCallback = exitCallback || process.exit;
@ -125,6 +144,7 @@ Definition of the `main` function.
return null; return null;
} }
o.silent || logMsg(_title); o.silent || logMsg(_title);
// Emit debug prelude if --debug was specified
if (o.debug) { if (o.debug) {
_out.log(chalk.cyan('The -d or --debug switch was specified. DEBUG mode engaged.')); _out.log(chalk.cyan('The -d or --debug switch was specified. DEBUG mode engaged.'));
_out.log(''); _out.log('');
@ -132,25 +152,33 @@ Definition of the `main` function.
_out.log(chalk.cyan(PAD(' Node.js:', 25, null, PAD.RIGHT)) + chalk.cyan.bold(process.version)); _out.log(chalk.cyan(PAD(' Node.js:', 25, null, PAD.RIGHT)) + chalk.cyan.bold(process.version));
_out.log(chalk.cyan(PAD(' HackMyResume:', 25, null, PAD.RIGHT)) + chalk.cyan.bold('v' + PKG.version)); _out.log(chalk.cyan(PAD(' HackMyResume:', 25, null, PAD.RIGHT)) + chalk.cyan.bold('v' + PKG.version));
_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(chalk.cyan(PAD(' fresh-themes:',25, null, PAD.RIGHT)) + chalk.cyan.bold( PKG.dependencies['fresh-themes'] ))
//_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); _err.init(o.debug, o.assert, o.silent);
if (o.verb && !HMR.verbs[o.verb] && !HMR.alias[o.verb]) { // Handle invalid verbs here (a bit easier here than in commander.js)...
if (o.verb && !HMR.verbs[o.verb] && !HMR.alias[o.verb] && o.verb !== 'help') {
_err.err({ _err.err({
fluenterror: HMSTATUS.invalidCommand, fluenterror: HMSTATUS.invalidCommand,
quit: true, quit: true,
attempted: o.orgVerb attempted: o.orgVerb
}, true); }, true);
} }
// Override the .missingArgument behavior
Command.prototype.missingArgument = function(name) { Command.prototype.missingArgument = function(name) {
_err.err({ if (this.name() !== 'help') {
fluenterror: this.name() !== 'new' ? HMSTATUS.resumeNotFound : HMSTATUS.createNameMissing _err.err({
}, true); verb: this.name(),
fluenterror: HMSTATUS.resumeNotFound
}, true);
}
}; };
// Override the .helpInformation behavior
Command.prototype.helpInformation = function() { Command.prototype.helpInformation = function() {
var manPage; var manPage;
manPage = FS.readFileSync(PATH.join(__dirname, 'use.txt'), 'utf8'); manPage = FS.readFileSync(PATH.join(__dirname, 'help/use.txt'), 'utf8');
return chalk.green.bold(manPage); return M2C(manPage, 'white', 'yellow');
}; };
return { return {
args: o.args, args: o.args,
@ -158,17 +186,16 @@ Definition of the `main` function.
}; };
}; };
/* Init options prior to setting up command infrastructure. */
initOptions = function(ar) { initOptions = function(ar) {
oVerb; oVerb;
/* jshint ignore:end */
var args, cleanArgs, inf, isAssert, isDebug, isMono, isNoEscape, isSilent, oJSON, oVerb, optStr, optsIdx, verb, vidx; var args, cleanArgs, inf, isAssert, isDebug, isMono, isNoEscape, isSilent, oJSON, oVerb, optStr, optsIdx, verb, vidx;
verb = ''; verb = '';
args = ar.slice(); args = ar.slice();
cleanArgs = args.slice(2); cleanArgs = args.slice(2);
oJSON; oJSON;
if (cleanArgs.length) { if (cleanArgs.length) {
// Support case-insensitive sub-commands (build, generate, validate, etc)
vidx = _.findIndex(cleanArgs, function(v) { vidx = _.findIndex(cleanArgs, function(v) {
return v[0] !== '-'; return v[0] !== '-';
}); });
@ -176,6 +203,7 @@ Definition of the `main` function.
oVerb = cleanArgs[vidx]; oVerb = cleanArgs[vidx];
verb = args[vidx + 2] = oVerb.trim().toLowerCase(); verb = args[vidx + 2] = oVerb.trim().toLowerCase();
} }
// Remove --options --opts -o and process separately
optsIdx = _.findIndex(cleanArgs, function(v) { optsIdx = _.findIndex(cleanArgs, function(v) {
return v === '-o' || v === '--options' || v === '--opts'; return v === '-o' || v === '--options' || v === '--opts';
}); });
@ -183,12 +211,11 @@ Definition of the `main` function.
optStr = cleanArgs[optsIdx + 1]; optStr = cleanArgs[optsIdx + 1];
args.splice(optsIdx + 2, 2); args.splice(optsIdx + 2, 2);
if (optStr && (optStr = optStr.trim())) { if (optStr && (optStr = optStr.trim())) {
//var myJSON = JSON.parse(optStr);
if (optStr[0] === '{') { if (optStr[0] === '{') {
// TODO: remove use of evil(). - hacksalot
/* jshint ignore:start */ /* jshint ignore:start */
oJSON = eval('(' + optStr + ')'); oJSON = eval('(' + optStr + ')'); // jshint ignore:line <-- no worky
/* jshint ignore:end */
} else { } else {
inf = safeLoadJSON(optStr); inf = safeLoadJSON(optStr);
if (!inf.ex) { if (!inf.ex) {
@ -200,6 +227,7 @@ Definition of the `main` function.
} }
} }
} }
// Grab the --debug flag, --silent, --assert and --no-color flags
isDebug = _.some(args, function(v) { isDebug = _.some(args, function(v) {
return v === '-d' || v === '--debug'; return v === '-d' || v === '--debug';
}); });
@ -228,36 +256,35 @@ Definition of the `main` function.
}; };
}; };
/* Invoke a HackMyResume verb. */
execute = function(src, dst, opts, log) { execute = function(src, dst, opts, log) {
var prom, v; var prom, v;
// Create the verb
v = new HMR.verbs[this.name()](); v = new HMR.verbs[this.name()]();
// Initialize command-specific options
loadOptions.call(this, opts, this.parent.jsonArgs); loadOptions.call(this, opts, this.parent.jsonArgs);
// Set up error/output handling
_opts.errHandler = v; _opts.errHandler = v;
_out.init(_opts); _out.init(_opts);
// Hook up event notifications
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 _err.err.apply(_err, arguments); return _err.err.apply(_err, arguments);
}); });
// Invoke the verb using promise syntax
prom = v.invoke.call(v, src, dst, _opts, log); prom = v.invoke.call(v, src, dst, _opts, log);
prom.then(executeSuccess, executeFail); prom.then(executeSuccess, executeFail);
}; };
/* Success handler for verb invocations. Calls process.exit by default */
executeSuccess = function(obj) {}; executeSuccess = function(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 */ /* Failure handler for verb invocations. Calls process.exit by default */
executeFail = function(err) { executeFail = function(err) {
var finalErrorCode, msgs; var finalErrorCode, msgs;
console.dir(err); //console.dir err
finalErrorCode = -1; finalErrorCode = -1;
if (err) { if (err) {
if (err.fluenterror) { if (err.fluenterror) {
@ -278,19 +305,16 @@ Definition of the `main` function.
_exitCallback(finalErrorCode); _exitCallback(finalErrorCode);
}; };
/*
Initialize HackMyResume options.
TODO: Options loading is a little hacky, for two reasons:
- Commander.js idiosyncracies
- Need to accept JSON inputs from the command line.
*/
loadOptions = function(o, cmdO) { loadOptions = function(o, cmdO) {
// o and this.opts() seem to be the same (command-specific options)
// Load the specified options file (if any) and apply options
if (cmdO) { if (cmdO) {
o = EXTEND(true, o, cmdO); o = EXTEND(true, o, cmdO);
} }
// Merge in command-line options
o = EXTEND(true, o, this.opts()); o = EXTEND(true, o, this.opts());
// Kludge parent-level options until piping issue is resolved
if (this.parent.silent !== void 0 && this.parent.silent !== null) { if (this.parent.silent !== void 0 && this.parent.silent !== null) {
o.silent = this.parent.silent; o.silent = this.parent.silent;
} }
@ -310,9 +334,6 @@ Definition of the `main` function.
EXTEND(true, _opts, o); EXTEND(true, _opts, o);
}; };
/* Split multiple command-line filenames by the 'TO' keyword */
splitSrcDest = function() { splitSrcDest = function() {
var params, splitAt; var params, splitAt;
params = this.parent.args.filter(function(j) { params = this.parent.args.filter(function(j) {
@ -320,13 +341,17 @@ Definition of the `main` function.
}); });
if (params.length === 0) { if (params.length === 0) {
throw { throw {
//tmpName = @name()
fluenterror: HMSTATUS.resumeNotFound, fluenterror: HMSTATUS.resumeNotFound,
verb: this.name(),
quit: true quit: true
}; };
} }
// Find the TO keyword, if any
splitAt = _.findIndex(params, function(p) { splitAt = _.findIndex(params, function(p) {
return p.toLowerCase() === 'to'; return p.toLowerCase() === 'to';
}); });
// TO can't be the last keyword
if (splitAt === params.length - 1 && splitAt !== -1) { if (splitAt === params.length - 1 && splitAt !== -1) {
logMsg(chalk.yellow('Please ') + chalk.yellow.bold('specify an output file') + chalk.yellow(' for this operation or ') + chalk.yellow.bold('omit the TO keyword') + chalk.yellow('.')); logMsg(chalk.yellow('Please ') + chalk.yellow.bold('specify an output file') + chalk.yellow(' for this operation or ') + chalk.yellow.bold('omit the TO keyword') + chalk.yellow('.'));
return; return;
@ -337,9 +362,6 @@ Definition of the `main` function.
}; };
}; };
/* Simple logging placeholder. */
logMsg = function() { logMsg = function() {
return _opts.silent || console.log.apply(console.log, arguments); return _opts.silent || console.log.apply(console.log, arguments);
}; };

12
dist/cli/msg.js vendored
View File

@ -1,11 +1,9 @@
/**
Message-handling routines for HackMyResume.
@module cli/msg
@license MIT. See LICENSE.md for details.
*/
(function() { (function() {
/**
Message-handling routines for HackMyResume.
@module cli/msg
@license MIT. See LICENSE.md for details.
*/
var PATH, YAML; var PATH, YAML;
PATH = require('path'); PATH = require('path');

2
dist/cli/msg.yml vendored
View File

@ -137,3 +137,5 @@ errors:
An error occurred while attempting to load the '%s' theme helper. Is the An error occurred while attempting to load the '%s' theme helper. Is the
theme correctly installed? theme correctly installed?
dummy: dontcare dummy: dontcare
invalidSchemaVersion:
msg: "'%s' is not recognized as a valid schema version."

40
dist/cli/out.js vendored
View File

@ -1,11 +1,9 @@
/**
Output routines for HackMyResume.
@license MIT. See LICENSE.md for details.
@module cli/out
*/
(function() { (function() {
/**
Output routines for HackMyResume.
@license MIT. See LICENSE.md for details.
@module cli/out
*/
var 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');
@ -34,29 +32,26 @@ Output routines for HackMyResume.
dbgStyle = 'cyan'; dbgStyle = 'cyan';
module.exports = OutputHandler = class OutputHandler {
/** A stateful output module. All HMR console output handled here. */ constructor(opts) {
module.exports = OutputHandler = (function() {
function OutputHandler(opts) {
this.init(opts); this.init(opts);
return; return;
} }
OutputHandler.prototype.init = function(opts) { init(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;
}; }
OutputHandler.prototype.log = function(msg) { log(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);
}; }
OutputHandler.prototype["do"] = function(evt) { do(evt) {
var L, WRAP, adj, info, msg, msgs, numFormats, output, rawTpl, sty, style, suffix, template, that, themeName, tot; 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() {
@ -65,6 +60,9 @@ 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());
//when HME.beforeCreate
//L( M2C( this.msgs.beforeCreate.msg, 'green' ), evt.fmt, evt.file )
//break;
case HME.afterCreate: case HME.afterCreate:
L(M2C(this.msgs.beforeCreate.msg, evt.isError ? 'red' : 'green'), evt.fmt, evt.file); L(M2C(this.msgs.beforeCreate.msg, evt.isError ? 'red' : 'green'), evt.fmt, evt.file);
break; break;
@ -166,11 +164,13 @@ Output routines for HackMyResume.
break; break;
case HME.afterPeek: case HME.afterPeek:
sty = evt.error ? 'red' : (evt.target !== void 0 ? 'green' : 'yellow'); sty = evt.error ? 'red' : (evt.target !== void 0 ? 'green' : '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 the key was present, print it
if (evt.target !== void 0 && !evt.error) { if (evt.target !== void 0 && !evt.error) {
return console.dir(evt.target, { return console.dir(evt.target, {
depth: null, depth: null,
@ -182,11 +182,9 @@ Output routines for HackMyResume.
return L(chalk.red(evt.error.inner.inner)); return L(chalk.red(evt.error.inner.inner));
} }
} }
}; }
return OutputHandler; };
})();
}).call(this); }).call(this);

52
dist/cli/use.txt vendored
View File

@ -1,52 +0,0 @@
Usage:
hackmyresume <command> <sources> [TO <targets>] [<options>]
Available commands:
BUILD Build your resume to the destination format(s).
ANALYZE Analyze your resume for keywords, gaps, and metrics.
VALIDATE Validate your resume for errors and typos.
CONVERT Convert your resume between FRESH and JSON Resume.
NEW Create a new resume in FRESH or JSON Resume format.
PEEK View a specific field or element on your resume.
Available options:
--theme -t Path to a FRESH or JSON Resume theme.
--pdf -p Specify the PDF engine to use (wkhtmltopdf or phantom).
--options -o Load options from an external JSON file.
--format -f The format (FRESH or JSON Resume) to use.
--debug -d Emit extended debugging info.
--assert -a Treat resume validation warnings as errors.
--private Include resume fields marked as private
--no-colors Disable terminal colors.
--tips Display theme messages and tips.
--help -h Display help documentation.
--version -v Display the current version.
Not all options are supported for all commands. For example, the
--theme option is only supported for the BUILD command.
Examples:
hackmyresume BUILD resume.json TO out/resume.all --theme modern
hackmyresume ANALYZE resume.json
hackmyresume NEW my-new-resume.json --format JRS
hackmyresume CONVERT resume-fresh.json TO resume-jrs.json
hackmyresume VALIDATE resume.json
hackmyresume PEEK resume.json employment[2].summary
Tips:
- You can specify multiple sources and/or targets for all commands.
- You can use any FRESH or JSON Resume theme with HackMyResume.
- Specify a file extension of .all to generate your resume to all
available formats supported by the theme. (BUILD command.)
- The --theme parameter can specify either the name of a preinstalled
theme, or the path to a local FRESH or JSON Resume theme.
- Visit https://www.npmjs.com/search?q=jsonresume-theme for a full
listing of all available JSON Resume themes.
- Visit https://github.com/fluentdesk/fresh-themes for a complete
listing of all available FRESH themes.
- Report bugs to https://githut.com/hacksalot/HackMyResume/issues.

View File

@ -1,113 +0,0 @@
/**
Definition of the AbstractResume class.
@license MIT. See LICENSE.md for details.
@module core/abstract-resume
*/
(function() {
var AbstractResume, FluentDate, _, __;
_ = require('underscore');
__ = require('lodash');
FluentDate = require('./fluent-date');
AbstractResume = (function() {
function AbstractResume() {}
/**
Compute the total duration of the work history.
@returns The total duration of the sheet's work history, that is, the number
of years between the start date of the earliest job on the resume and the
*latest end date of all jobs in the work history*. This last condition is for
sheets that have overlapping jobs.
*/
AbstractResume.prototype.duration = function(collKey, startKey, endKey, unit) {
var firstDate, hist, lastDate, new_e;
unit = unit || 'years';
hist = __.get(this, collKey);
if (!hist || !hist.length) {
return 0;
}
new_e = hist.map(function(job) {
var obj;
obj = _.pick(job, [startKey, endKey]);
if (!_.has(obj, endKey)) {
obj[endKey] = 'current';
}
if (obj && (obj[startKey] || obj[endKey])) {
obj = _.pairs(obj);
obj[0][1] = FluentDate.fmt(obj[0][1]);
if (obj.length > 1) {
obj[1][1] = FluentDate.fmt(obj[1][1]);
}
}
return obj;
});
new_e = _.filter(_.flatten(new_e, true), function(v) {
return v && v.length && v[0] && v[0].length;
});
if (!new_e || !new_e.length) {
return 0;
}
new_e = _.sortBy(new_e, function(elem) {
return elem[1].unix();
});
firstDate = _.first(new_e)[1];
lastDate = _.last(new_e)[1];
return lastDate.diff(firstDate, unit);
};
/**
Removes ignored or private fields from a resume object
@returns an object with the following structure:
{
scrubbed: the processed resume object
ignoreList: an array of ignored nodes that were removed
privateList: an array of private nodes that were removed
}
*/
AbstractResume.prototype.scrubResume = function(rep, opts) {
var ignoreList, includePrivates, privateList, scrubbed, traverse;
traverse = require('traverse');
ignoreList = [];
privateList = [];
includePrivates = opts && opts["private"];
scrubbed = traverse(rep).map(function() {
if (!this.isLeaf) {
if (this.node.ignore === true || this.node.ignore === 'true') {
ignoreList.push(this.node);
this["delete"]();
} else if ((this.node["private"] === true || this.node["private"] === 'true') && !includePrivates) {
privateList.push(this.node);
this["delete"]();
}
}
if (_.isArray(this.node)) {
this.after(function() {
this.update(_.compact(this.node));
});
}
});
return {
scrubbed: scrubbed,
ingoreList: ignoreList,
privateList: privateList
};
};
return AbstractResume;
})();
module.exports = AbstractResume;
}).call(this);
//# sourceMappingURL=abstract-resume.js.map

View File

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

View File

@ -1,20 +1,20 @@
/*
Event code definitions.
@module core/default-options
@license MIT. See LICENSE.md for details.
*/
(function() { (function() {
/*
Event code definitions.
@module core/default-options
@license MIT. See LICENSE.md for details.
*/
module.exports = { module.exports = {
theme: 'modern', theme: 'modern',
prettify: { prettify: {
indent_size: 2, indent_size: 2,
unformatted: ['em', 'strong'], unformatted: ['em', 'strong'],
max_char: 80 max_char: 80 // ← See lib/html.js in above-linked repo
} }
}; };
// wrap_line_length: 120, ← Don't use this
}).call(this); }).call(this);
//# sourceMappingURL=default-options.js.map //# sourceMappingURL=default-options.js.map

View File

@ -1,11 +1,9 @@
/*
Event code definitions.
@module core/event-codes
@license MIT. See LICENSE.md for details.
*/
(function() { (function() {
/*
Event code definitions.
@module core/event-codes
@license MIT. See LICENSE.md for details.
*/
module.exports = { module.exports = {
error: -1, error: -1,
success: 0, success: 0,

View File

@ -1,18 +1,15 @@
/**
The HackMyResume date representation.
@license MIT. See LICENSE.md for details.
@module core/fluent-date
*/
(function() { (function() {
/**
The HackMyResume date representation.
@license MIT. See LICENSE.md for details.
@module core/fluent-date
*/
var FluentDate, abbr, moment, months; var FluentDate, abbr, moment, months;
moment = require('moment'); moment = require('moment');
require('../utils/string'); require('../utils/string');
/** /**
Create a FluentDate from a string or Moment date object. There are a few date Create a FluentDate from a string or Moment date object. There are a few date
formats to be aware of here. formats to be aware of here.
@ -28,20 +25,17 @@ The HackMyResume date representation.
deprecation warnings, it's recommended to either a) explicitly specify the date deprecation warnings, it's recommended to either a) explicitly specify the date
format or b) use an ISO format. For clarity, we handle these cases explicitly. format or b) use an ISO format. For clarity, we handle these cases explicitly.
@class FluentDate @class FluentDate
*/ */
FluentDate = class FluentDate {
FluentDate = (function() { constructor(dt) {
function FluentDate(dt) {
this.rep = this.fmt(dt); this.rep = this.fmt(dt);
} }
FluentDate.isCurrent = function(dt) { static isCurrent(dt) {
return !dt || (String.is(dt) && /^(present|now|current)$/.test(dt)); return !dt || (String.is(dt) && /^(present|now|current)$/.test(dt));
}; }
return FluentDate; };
})();
months = {}; months = {};
@ -64,20 +58,20 @@ The HackMyResume date representation.
throws = (throws === void 0 || throws === null) || throws; throws = (throws === void 0 || throws === null) || throws;
if (typeof dt === 'string' || dt instanceof String) { if (typeof dt === 'string' || dt instanceof String) {
dt = dt.toLowerCase().trim(); dt = dt.toLowerCase().trim();
if (/^(present|now|current)$/.test(dt)) { if (/^(present|now|current)$/.test(dt)) { // "Present", "Now"
return moment(); return moment();
} else if (/^\D+\s+\d{4}$/.test(dt)) { } else if (/^\D+\s+\d{4}$/.test(dt)) { // "Mar 2015"
parts = dt.split(' '); parts = dt.split(' ');
month = months[parts[0]] || abbr[parts[0]]; month = months[parts[0]] || abbr[parts[0]];
temp = parts[1] + '-' + ((ref = month < 10) != null ? ref : '0' + { temp = parts[1] + '-' + ((ref = month < 10) != null ? ref : '0' + {
month: month.toString() month: month.toString()
}); });
return moment(temp, 'YYYY-MM'); return moment(temp, 'YYYY-MM');
} else if (/^\d{4}-\d{1,2}$/.test(dt)) { } else if (/^\d{4}-\d{1,2}$/.test(dt)) { // "2015-03", "1998-4"
return moment(dt, 'YYYY-MM'); return moment(dt, 'YYYY-MM');
} else if (/^\s*\d{4}\s*$/.test(dt)) { } else if (/^\s*\d{4}\s*$/.test(dt)) { // "2015"
return moment(dt, 'YYYY'); return moment(dt, 'YYYY');
} else if (/^\s*$/.test(dt)) { } else if (/^\s*$/.test(dt)) { // "", " "
return moment(); return moment();
} else { } else {
mt = moment(dt); mt = moment(dt);

View File

@ -1,14 +1,17 @@
/**
Definition of the FRESHResume class.
@license MIT. See LICENSE.md for details.
@module core/fresh-resume
*/
(function() { (function() {
var AbstractResume, CONVERTER, FS, FluentDate, FreshResume, JRSResume, MD, PATH, XML, _, __, _parseDates, extend, moment, validator, /**
extend1 = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, Definition of the FRESHResume class.
hasProp = {}.hasOwnProperty; @license MIT. See LICENSE.md for details.
@module core/fresh-resume
*/
/**
Convert human-friendly dates into formal Moment.js dates for all collections.
We don't want to lose the raw textual date as entered by the user, so we store
the Moment-ified date as a separate property with a prefix of .safe. For ex:
job.startDate is the date as entered by the user. job.safeStartDate is the
parsed Moment.js date that we actually use in processing.
*/
var CONVERTER, FS, FluentDate, FreshResume, JRSResume, MD, PATH, XML, _, __, _parseDates, extend, moment, validator;
FS = require('fs'); FS = require('fs');
@ -34,33 +37,20 @@ Definition of the FRESHResume class.
FluentDate = require('./fluent-date'); FluentDate = require('./fluent-date');
AbstractResume = require('./abstract-resume');
/** /**
A FRESH resume or CV. FRESH resumes are backed by JSON, and each FreshResume A FRESH resume or CV. FRESH resumes are backed by JSON, and each FreshResume
object is an instantiation of that JSON decorated with utility methods. object is an instantiation of that JSON decorated with utility methods.
@constructor @constructor
*/ */
FreshResume = class FreshResume { // extends AbstractResume
FreshResume = (function(superClass) {
extend1(FreshResume, superClass);
function FreshResume() {
return FreshResume.__super__.constructor.apply(this, arguments);
}
/** Initialize the the FreshResume from JSON string data. */ /** Initialize the the FreshResume from JSON string data. */
parse(stringData, opts) {
FreshResume.prototype.parse = function(stringData, opts) {
var ref; var ref;
this.imp = (ref = this.imp) != null ? ref : { this.imp = (ref = this.imp) != null ? ref : {
raw: stringData raw: stringData
}; };
return this.parseJSON(JSON.parse(stringData), opts); return this.parseJSON(JSON.parse(stringData), opts);
}; }
/** /**
Initialize the FreshResume from JSON. Initialize the FreshResume from JSON.
@ -70,19 +60,22 @@ Definition of the FRESHResume class.
@param rep {Object} The raw JSON representation. @param rep {Object} The raw JSON representation.
@param opts {Object} Resume loading and parsing options. @param opts {Object} Resume loading and parsing options.
{ {
date: Perform safe date conversion. date: Perform safe date conversion.
sort: Sort resume items by date. sort: Sort resume items by date.
compute: Prepare computed resume totals. compute: Prepare computed resume totals.
} }
*/ */
parseJSON(rep, opts) {
FreshResume.prototype.parseJSON = function(rep, opts) { var ignoreList, privateList, ref, scrubbed, scrubber;
var ignoreList, privateList, ref, ref1, scrubbed;
if (opts && opts.privatize) { if (opts && opts.privatize) {
ref = this.scrubResume(rep, opts), scrubbed = ref.scrubbed, ignoreList = ref.ignoreList, privateList = ref.privateList; // Ignore any element with the 'ignore: true' or 'private: true' designator.
scrubber = require('../utils/resume-scrubber');
({scrubbed, ignoreList, privateList} = scrubber.scrubResume(rep, opts));
} }
// Now apply the resume representation onto this object
extend(true, this, opts && opts.privatize ? scrubbed : rep); extend(true, this, opts && opts.privatize ? scrubbed : rep);
if (!((ref1 = this.imp) != null ? ref1.processed : void 0)) { if (!((ref = this.imp) != null ? ref.processed : void 0)) {
// Set up metadata TODO: Clean up metadata on the object model.
opts = opts || {}; opts = opts || {};
if (opts.imp === void 0 || opts.imp) { if (opts.imp === void 0 || opts.imp) {
this.imp = this.imp || {}; this.imp = this.imp || {};
@ -92,6 +85,7 @@ Definition of the FRESHResume class.
} }
} }
this.imp.processed = true; this.imp.processed = true;
// Parse dates, sort dates, and calculate computed values
(opts.date === void 0 || opts.date) && _parseDates.call(this); (opts.date === void 0 || opts.date) && _parseDates.call(this);
(opts.sort === void 0 || opts.sort) && this.sort(); (opts.sort === void 0 || opts.sort) && this.sort();
(opts.compute === void 0 || opts.compute) && (this.computed = { (opts.compute === void 0 || opts.compute) && (this.computed = {
@ -100,34 +94,43 @@ Definition of the FRESHResume class.
}); });
} }
return this; return this;
}; }
/** Save the sheet to disk (for environments that have disk access). */ /** Save the sheet to disk (for environments that have disk access). */
save(filename) {
FreshResume.prototype.save = function(filename) {
this.imp.file = filename || this.imp.file; this.imp.file = filename || this.imp.file;
FS.writeFileSync(this.imp.file, this.stringify(), 'utf8'); FS.writeFileSync(this.imp.file, this.stringify(), 'utf8');
return this; return this;
}; }
/** /**
Save the sheet to disk in a specific format, either FRESH or JSON Resume. Save the sheet to disk in a specific format, either FRESH or JSON Resume.
*/ */
saveAs(filename, format) {
FreshResume.prototype.saveAs = function(filename, format) { var newRep, parts, safeFormat, useEdgeSchema;
var newRep; // If format isn't specified, default to FRESH
if (format !== 'JRS') { safeFormat = (format && format.trim()) || 'FRESH';
// Validate against the FRESH version regex
// freshVersionReg = require '../utils/fresh-version-regex'
// if (not freshVersionReg().test( safeFormat ))
// throw badVer: safeFormat
parts = safeFormat.split('@');
if (parts[0] === 'FRESH') {
this.imp.file = filename || this.imp.file; this.imp.file = filename || this.imp.file;
FS.writeFileSync(this.imp.file, this.stringify(), 'utf8'); FS.writeFileSync(this.imp.file, this.stringify(), 'utf8');
} else { } else if (parts[0] === 'JRS') {
newRep = CONVERTER.toJRS(this); useEdgeSchema = parts.length > 1 ? parts[1] === '1' : false;
newRep = CONVERTER.toJRS(this, {
edge: useEdgeSchema
});
FS.writeFileSync(filename, JRSResume.stringify(newRep), 'utf8'); FS.writeFileSync(filename, JRSResume.stringify(newRep), 'utf8');
} else {
throw {
badVer: safeFormat
};
} }
return this; return this;
}; }
/** /**
Duplicate this FreshResume instance. Duplicate this FreshResume instance.
@ -135,47 +138,40 @@ Definition of the FRESHResume class.
and then passes the result into a new FreshResume instance via .parseJSON. and then passes the result into a new FreshResume instance via .parseJSON.
We do it this way to create a true clone of the object without re-running any We do it this way to create a true clone of the object without re-running any
of the associated processing. of the associated processing.
*/ */
dupe() {
FreshResume.prototype.dupe = function() {
var jso, rnew; var jso, rnew;
jso = extend(true, {}, this); jso = extend(true, {}, this);
rnew = new FreshResume(); rnew = new FreshResume();
rnew.parseJSON(jso, {}); rnew.parseJSON(jso, {});
return rnew; return rnew;
}; }
/** /**
Convert this object to a JSON string, sanitizing meta-properties along the Convert this object to a JSON string, sanitizing meta-properties along the
way. way.
*/ */
stringify() {
FreshResume.prototype.stringify = function() {
return FreshResume.stringify(this); return FreshResume.stringify(this);
}; }
/** /**
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).
TODO: Move this out of FRESHResume. TODO: Move this out of FRESHResume.
*/ */
transformStrings(filt, transformer) {
FreshResume.prototype.transformStrings = function(filt, transformer) {
var ret, trx; var ret, trx;
ret = this.dupe(); ret = this.dupe();
trx = require('../utils/string-transformer'); trx = require('../utils/string-transformer');
return trx(ret, filt, transformer); return trx(ret, filt, transformer);
}; }
/** /**
Create a copy of this resume in which all fields have been interpreted as Create a copy of this resume in which all fields have been interpreted as
Markdown. Markdown.
*/ */
markdownify() {
FreshResume.prototype.markdownify = function() {
var MDIN, trx; var MDIN, trx;
MDIN = function(txt) { MDIN = function(txt) {
return MD(txt || '').replace(/^\s*<p>|<\/p>\s*$/gi, ''); return MD(txt || '').replace(/^\s*<p>|<\/p>\s*$/gi, '');
@ -187,42 +183,49 @@ Definition of the FRESHResume class.
return MDIN(val); return MDIN(val);
}; };
return this.transformStrings(['skills', 'url', 'start', 'end', 'date'], trx); return this.transformStrings(['skills', 'url', 'start', 'end', 'date'], trx);
}; }
/** /**
Create a copy of this resume in which all fields have been interpreted as Create a copy of this resume in which all fields have been interpreted as
Markdown. Markdown.
*/ */
xmlify() {
FreshResume.prototype.xmlify = function() {
var trx; var trx;
trx = function(key, val) { trx = function(key, val) {
return XML(val); return XML(val);
}; };
return this.transformStrings([], trx); return this.transformStrings([], trx);
}; }
/** Return the resume format. */ /** Return the resume format. */
format() {
FreshResume.prototype.format = function() {
return 'FRESH'; return 'FRESH';
}; }
/** /**
Return internal metadata. Create if it doesn't exist. Return internal metadata. Create if it doesn't exist.
*/ */
i() {
FreshResume.prototype.i = function() {
return this.imp = this.imp || {}; return this.imp = this.imp || {};
}; }
/**
Return a unique list of all skills declared in the resume.
*/
// TODO: Several problems here:
// 1) Confusing name. Easily confused with the keyword-inspector module, which
// parses resume body text looking for these same keywords. This should probably
// be renamed.
/** Return a unique list of all keywords across all skills. */ // 2) Doesn't bother trying to integrate skills.list with skills.sets if they
// happen to declare different skills, and if skills.sets declares ONE skill and
// skills.list declared 50, only 1 skill will be registered.
FreshResume.prototype.keywords = function() { // 3) In the future, skill.sets should only be able to use skills declared in
// skills.list. That is, skills.list is the official record of a candidate's
// declared skills. skills.sets is just a way of grouping those into skillsets
// for easier consumption.
keywords() {
var flatSkills; var flatSkills;
flatSkills = []; flatSkills = [];
if (this.skills) { if (this.skills) {
@ -240,19 +243,17 @@ Definition of the FRESHResume class.
flatSkills = _.uniq(flatSkills); flatSkills = _.uniq(flatSkills);
} }
return flatSkills; return flatSkills;
}; }
/** /**
Reset the sheet to an empty state. TODO: refactor/review Reset the sheet to an empty state. TODO: refactor/review
*/ */
clear(clearMeta) {
FreshResume.prototype.clear = function(clearMeta) {
clearMeta = ((clearMeta === void 0) && true) || clearMeta; clearMeta = ((clearMeta === void 0) && true) || clearMeta;
if (clearMeta) { if (clearMeta) {
delete this.imp; delete this.imp;
} }
delete this.computed; delete this.computed; // Don't use Object.keys() here
delete this.employment; delete this.employment;
delete this.service; delete this.service;
delete this.education; delete this.education;
@ -262,14 +263,12 @@ Definition of the FRESHResume class.
delete this.interests; delete this.interests;
delete this.skills; delete this.skills;
return delete this.social; return delete this.social;
}; }
/** /**
Get a safe count of the number of things in a section. Get a safe count of the number of things in a section.
*/ */
count(obj) {
FreshResume.prototype.count = function(obj) {
if (!obj) { if (!obj) {
return 0; return 0;
} }
@ -280,14 +279,11 @@ Definition of the FRESHResume class.
return obj.sets.length; return obj.sets.length;
} }
return obj.length || 0; return obj.length || 0;
}; }
add(moniker) {
/** Add work experience to the sheet. */
FreshResume.prototype.add = function(moniker) {
var defSheet, newObject; var defSheet, newObject;
defSheet = FreshResume["default"](); defSheet = FreshResume.default();
newObject = defSheet[moniker].history ? $.extend(true, {}, defSheet[moniker].history[0]) : moniker === 'skills' ? $.extend(true, {}, defSheet.skills.sets[0]) : $.extend(true, {}, defSheet[moniker][0]); newObject = defSheet[moniker].history ? $.extend(true, {}, defSheet[moniker].history[0]) : moniker === 'skills' ? $.extend(true, {}, defSheet.skills.sets[0]) : $.extend(true, {}, defSheet[moniker][0]);
this[moniker] = this[moniker] || []; this[moniker] = this[moniker] || [];
if (this[moniker].history) { if (this[moniker].history) {
@ -298,63 +294,53 @@ Definition of the FRESHResume class.
this[moniker].push(newObject); this[moniker].push(newObject);
} }
return newObject; return newObject;
}; }
/** /**
Determine if the sheet includes a specific social profile (eg, GitHub). Determine if the sheet includes a specific social profile (eg, GitHub).
*/ */
hasProfile(socialNetwork) {
FreshResume.prototype.hasProfile = function(socialNetwork) {
socialNetwork = socialNetwork.trim().toLowerCase(); socialNetwork = socialNetwork.trim().toLowerCase();
return this.social && _.some(this.social, function(p) { return this.social && _.some(this.social, function(p) {
return p.network.trim().toLowerCase() === socialNetwork; return p.network.trim().toLowerCase() === socialNetwork;
}); });
}; }
/** Return the specified network profile. */ /** Return the specified network profile. */
getProfile(socialNetwork) {
FreshResume.prototype.getProfile = function(socialNetwork) {
socialNetwork = socialNetwork.trim().toLowerCase(); socialNetwork = socialNetwork.trim().toLowerCase();
return this.social && _.find(this.social, function(sn) { return this.social && _.find(this.social, function(sn) {
return sn.network.trim().toLowerCase() === socialNetwork; return 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.
*/ */
getProfiles(socialNetwork) {
FreshResume.prototype.getProfiles = function(socialNetwork) {
socialNetwork = socialNetwork.trim().toLowerCase(); socialNetwork = socialNetwork.trim().toLowerCase();
return this.social && _.filter(this.social, function(sn) { return this.social && _.filter(this.social, function(sn) {
return sn.network.trim().toLowerCase() === socialNetwork; return sn.network.trim().toLowerCase() === socialNetwork;
}); });
}; }
/** Determine if the sheet includes a specific skill. */ /** Determine if the sheet includes a specific skill. */
hasSkill(skill) {
FreshResume.prototype.hasSkill = function(skill) {
skill = skill.trim().toLowerCase(); skill = skill.trim().toLowerCase();
return this.skills && _.some(this.skills, function(sk) { return this.skills && _.some(this.skills, function(sk) {
return sk.keywords && _.some(sk.keywords, function(kw) { return sk.keywords && _.some(sk.keywords, function(kw) {
return kw.trim().toLowerCase() === skill; return kw.trim().toLowerCase() === skill;
}); });
}); });
}; }
/** Validate the sheet against the FRESH Resume schema. */ /** Validate the sheet against the FRESH Resume schema. */
isValid(info) {
FreshResume.prototype.isValid = function(info) {
var ret, schemaObj, validate; var ret, schemaObj, validate;
schemaObj = require('fresh-resume-schema'); schemaObj = require('fresh-resume-schema');
validator = require('is-my-json-valid'); validator = require('is-my-json-valid');
validate = validator(schemaObj, { validate = validator(schemaObj, { // See Note [1].
formats: { formats: {
date: /^\d{4}(?:-(?:0[0-9]{1}|1[0-2]{1})(?:-[0-9]{2})?)?$/ date: /^\d{4}(?:-(?:0[0-9]{1}|1[0-2]{1})(?:-[0-9]{2})?)?$/
} }
@ -365,19 +351,19 @@ Definition of the FRESHResume class.
this.imp.validationErrors = validate.errors; this.imp.validationErrors = validate.errors;
} }
return ret; return ret;
}; }
FreshResume.prototype.duration = function(unit) {
return FreshResume.__super__.duration.call(this, 'employment.history', 'start', 'end', unit);
};
duration(unit) {
var inspector;
inspector = require('../inspectors/duration-inspector');
return inspector.run(this, '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().
*/ */
sort() {
FreshResume.prototype.sort = function() {
var byDateDesc, sortSection; var byDateDesc, sortSection;
byDateDesc = function(a, b) { byDateDesc = function(a, b) {
if (a.safe.start.isBefore(b.safe.start)) { if (a.safe.start.isBefore(b.safe.start)) {
@ -411,30 +397,24 @@ Definition of the FRESHResume class.
return (a.safe.date.isAfter(b.safe.date) && -1) || 0; return (a.safe.date.isAfter(b.safe.date) && -1) || 0;
} }
}); });
}; }
return FreshResume;
})(AbstractResume);
};
/** /**
Get the default (starter) sheet. Get the default (starter) sheet.
*/ */
FreshResume.default = function() {
FreshResume["default"] = function() {
return new FreshResume().parseJSON(require('fresh-resume-starter').fresh); return new FreshResume().parseJSON(require('fresh-resume-starter').fresh);
}; };
/** /**
Convert the supplied FreshResume to a JSON string, sanitizing meta-properties Convert the supplied FreshResume to a JSON string, sanitizing meta-properties
along the way. along the way.
*/ */
FreshResume.stringify = function(obj) { FreshResume.stringify = function(obj) {
var replacer; var replacer;
replacer = function(key, value) { replacer = function(key, value) { // Exclude these keys from stringification
var exKeys; var exKeys;
exKeys = ['imp', 'warnings', 'computed', 'filt', 'ctrl', 'index', 'safe', 'result', 'isModified', 'htmlPreview', 'display_progress_bar']; exKeys = ['imp', 'warnings', 'computed', 'filt', 'ctrl', 'index', 'safe', 'result', 'isModified', 'htmlPreview', 'display_progress_bar'];
if (_.some(exKeys, function(val) { if (_.some(exKeys, function(val) {
@ -448,19 +428,11 @@ Definition of the FRESHResume class.
return JSON.stringify(obj, replacer, 2); return JSON.stringify(obj, replacer, 2);
}; };
/**
Convert human-friendly dates into formal Moment.js dates for all collections.
We don't want to lose the raw textual date as entered by the user, so we store
the Moment-ified date as a separate property with a prefix of .safe. For ex:
job.startDate is the date as entered by the user. job.safeStartDate is the
parsed Moment.js date that we actually use in processing.
*/
_parseDates = function() { _parseDates = function() {
var _fmt, replaceDatesInObject, that; var _fmt, replaceDatesInObject, that;
_fmt = require('./fluent-date').fmt; _fmt = require('./fluent-date').fmt;
that = this; that = this;
// TODO: refactor recursion
replaceDatesInObject = function(obj) { replaceDatesInObject = function(obj) {
if (!obj) { if (!obj) {
return; return;
@ -492,11 +464,15 @@ Definition of the FRESHResume class.
}); });
}; };
/** 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
// in addition to YYYY-MM-DD. The original regex:
// /^\d{4}-(?:0[0-9]{1}|1[0-2]{1})-[0-9]{2}$/
}).call(this); }).call(this);
//# sourceMappingURL=fresh-resume.js.map //# sourceMappingURL=fresh-resume.js.map

View File

@ -1,11 +1,12 @@
/**
Definition of the FRESHTheme class.
@module core/fresh-theme
@license MIT. See LICENSE.md for details.
*/
(function() { (function() {
/**
Definition of the FRESHTheme class.
@module core/fresh-theme
@license MIT. See LICENSE.md for details.
*/
/* Load and parse theme source files. */
/* Load a single theme file. */
/* Return a more friendly name for certain formats. */
var EXTEND, FRESHTheme, FS, HMSTATUS, PATH, READFILES, _, _load, _loadOne, friendlyName, 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');
@ -30,72 +31,78 @@ Definition of the FRESHTheme class.
READFILES = require('recursive-readdir-sync'); READFILES = require('recursive-readdir-sync');
/* A representation of a FRESH theme asset. /* A representation of a FRESH theme asset.
@class FRESHTheme @class FRESHTheme */
*/ FRESHTheme = class FRESHTheme {
constructor() {
FRESHTheme = (function() {
function FRESHTheme() {
this.baseFolder = 'src'; this.baseFolder = 'src';
return; return;
} }
/* Open and parse the specified theme. */ /* Open and parse the specified theme. */
open(themeFolder) {
FRESHTheme.prototype.open = function(themeFolder) {
var cached, formatsHash, pathInfo, that, themeFile, themeInfo; var cached, formatsHash, pathInfo, that, themeFile, themeInfo;
this.folder = themeFolder; this.folder = themeFolder;
// 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
formatsHash = {}; formatsHash = {};
// 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: themeInfo.ex.op === 'parse' ? HMSTATUS.parseError : HMSTATUS.readError, fluenterror: themeInfo.ex.op === 'parse' ? HMSTATUS.parseError : HMSTATUS.readError
inner: themeInfo.ex.inner
}; };
({
inner: themeInfo.ex.inner
});
} }
that = this; that = this;
// Move properties from the theme JSON file to the theme object
EXTEND(true, this, themeInfo.json); EXTEND(true, this, themeInfo.json);
// Check for an "inherits" entry in the theme JSON.
if (this.inherits) { if (this.inherits) {
cached = {}; cached = {};
_.each(this.inherits, function(th, key) { _.each(this.inherits, function(th, key) {
var d, themePath, themesFolder; var d, themePath, themesObj;
themesFolder = require.resolve('fresh-themes'); // First, see if this is one of the predefined FRESH themes. There are
d = parsePath(themeFolder).dirname; // only a handful of these, but they may change over time, so we need to
themePath = PATH.join(d, th); // query the official source of truth: the fresh-themes repository, which
// mounts the themes conveniently by name to the module object, and which
// is embedded locally inside the HackMyResume installation.
// TODO: merge this code with
themesObj = require('fresh-themes');
if (_.has(themesObj.themes, th)) {
themePath = PATH.join(parsePath(require.resolve('fresh-themes')).dirname, '/themes/', th);
} else {
d = parsePath(th).dirname;
themePath = PATH.join(d, th);
}
cached[th] = cached[th] || new FRESHTheme().open(themePath); cached[th] = cached[th] || new FRESHTheme().open(themePath);
return formatsHash[key] = cached[th].getFormat(key); return formatsHash[key] = cached[th].getFormat(key);
}); });
} }
// Load theme files
formatsHash = _load.call(this, formatsHash); formatsHash = _load.call(this, formatsHash);
// Cache
this.formats = formatsHash; this.formats = formatsHash;
// Set the official theme name
this.name = parsePath(this.folder).name; this.name = parsePath(this.folder).name;
return this; return this;
}; }
/* Determine if the theme supports the specified output format. */ /* Determine if the theme supports the specified output format. */
hasFormat(fmt) {
FRESHTheme.prototype.hasFormat = function(fmt) {
return _.has(this.formats, fmt); return _.has(this.formats, fmt);
}; }
/* Determine if the theme supports the specified output format. */ /* Determine if the theme supports the specified output format. */
getFormat(fmt) {
FRESHTheme.prototype.getFormat = function(fmt) {
return this.formats[fmt]; return this.formats[fmt];
}; }
return FRESHTheme; };
})();
/* Load and parse theme source files. */
_load = function(formatsHash) { _load = function(formatsHash) {
var copyOnly, fmts, jsFiles, major, that, tplFolder; var copyOnly, fmts, jsFiles, major, that, tplFolder;
@ -103,12 +110,19 @@ Definition of the FRESHTheme class.
major = false; major = false;
tplFolder = PATH.join(this.folder, this.baseFolder); tplFolder = PATH.join(this.folder, this.baseFolder);
copyOnly = ['.ttf', '.otf', '.png', '.jpg', '.jpeg', '.pdf']; copyOnly = ['.ttf', '.otf', '.png', '.jpg', '.jpeg', '.pdf'];
// 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
// the formatsHash object.
fmts = READFILES(tplFolder).map(function(absPath) { fmts = READFILES(tplFolder).map(function(absPath) {
return _loadOne.call(this, absPath, formatsHash, tplFolder); return _loadOne.call(this, absPath, formatsHash, tplFolder);
}, this); }, this);
// Now, get all the CSS files...
this.cssFiles = fmts.filter(function(fmt) { this.cssFiles = fmts.filter(function(fmt) {
return fmt && (fmt.ext === 'css'); return fmt && (fmt.ext === 'css');
}); });
// For each CSS file, get its corresponding HTML file. It's possible that
// a theme can have a CSS file but *no* HTML file, as when a theme author
// creates a pure CSS override of an existing theme.
this.cssFiles.forEach(function(cssf) { this.cssFiles.forEach(function(cssf) {
var idx; var idx;
idx = _.findIndex(fmts, function(fmt) { idx = _.findIndex(fmts, function(fmt) {
@ -120,6 +134,8 @@ Definition of the FRESHTheme class.
return fmts[idx].cssPath = cssf.path; return fmts[idx].cssPath = cssf.path;
} else { } else {
if (that.inherits) { if (that.inherits) {
// Found a CSS file without an HTML file in a theme that inherits
// from another theme. This is the override CSS file.
return that.overrides = { return that.overrides = {
file: cssf.path, file: cssf.path,
data: cssf.data data: cssf.data
@ -127,6 +143,7 @@ Definition of the FRESHTheme class.
} }
} }
}); });
// Now, save all the javascript file paths to a theme property.
jsFiles = fmts.filter(function(fmt) { jsFiles = fmts.filter(function(fmt) {
return fmt && (fmt.ext === 'js'); return fmt && (fmt.ext === 'js');
}); });
@ -136,9 +153,6 @@ Definition of the FRESHTheme class.
return formatsHash; return formatsHash;
}; };
/* Load a single theme file. */
_loadOne = function(absPath, formatsHash, tplFolder) { _loadOne = function(absPath, formatsHash, tplFolder) {
var absPathSafe, act, defFormats, idx, isPrimary, obj, outFmt, pathInfo, portion, ref, ref1, reg, res; var absPathSafe, act, defFormats, idx, isPrimary, obj, outFmt, pathInfo, portion, ref, ref1, reg, res;
pathInfo = parsePath(absPath); pathInfo = parsePath(absPath);
@ -149,6 +163,8 @@ Definition of the FRESHTheme class.
outFmt = ''; outFmt = '';
act = 'copy'; act = 'copy';
isPrimary = false; isPrimary = false;
// If this is an "explicit" theme, all files of importance are specified in
// the "transform" section of the theme.json file.
if (this.explicit) { if (this.explicit) {
outFmt = _.find(Object.keys(this.formats), function(fmtKey) { outFmt = _.find(Object.keys(this.formats), function(fmtKey) {
var fmtVal; var fmtVal;
@ -164,6 +180,9 @@ Definition of the FRESHTheme class.
} }
} }
if (!outFmt) { if (!outFmt) {
// If this file lives in a specific format folder within the theme,
// such as "/latex" or "/html", then that format is the implicit output
// format for all files within the folder
portion = pathInfo.dirname.replace(tplFolder, ''); portion = pathInfo.dirname.replace(tplFolder, '');
if (portion && portion.trim()) { if (portion && portion.trim()) {
if (portion[1] === '_') { if (portion[1] === '_') {
@ -199,13 +218,16 @@ Definition of the FRESHTheme class.
return form.name === outFmt && pathInfo.extname !== '.css'; return form.name === outFmt && pathInfo.extname !== '.css';
}); });
} }
// Make sure we have a valid formatsHash
formatsHash[outFmt] = formatsHash[outFmt] || { formatsHash[outFmt] = formatsHash[outFmt] || {
outFormat: outFmt, outFormat: outFmt,
files: [] files: []
}; };
// Move symlink descriptions from theme.json to the format
if ((ref = this.formats) != null ? (ref1 = ref[outFmt]) != null ? ref1.symLinks : void 0 : void 0) { if ((ref = this.formats) != null ? (ref1 = ref[outFmt]) != null ? ref1.symLinks : void 0 : void 0) {
formatsHash[outFmt].symLinks = this.formats[outFmt].symLinks; formatsHash[outFmt].symLinks = this.formats[outFmt].symLinks;
} }
// Create the file representation object
obj = { obj = {
action: act, action: act,
primary: isPrimary, primary: isPrimary,
@ -214,16 +236,15 @@ Definition of the FRESHTheme class.
ext: pathInfo.extname.slice(1), ext: pathInfo.extname.slice(1),
title: friendlyName(outFmt), title: friendlyName(outFmt),
pre: outFmt, pre: outFmt,
// outFormat: outFmt || pathInfo.name,
data: FS.readFileSync(absPath, 'utf8'), data: FS.readFileSync(absPath, 'utf8'),
css: null css: null
}; };
// Add this file to the list of files for this format type.
formatsHash[outFmt].files.push(obj); formatsHash[outFmt].files.push(obj);
return obj; return obj;
}; };
/* Return a more friendly name for certain formats. */
friendlyName = function(val) { friendlyName = function(val) {
var friendly; var friendly;
val = (val && val.trim().toLowerCase()) || ''; val = (val && val.trim().toLowerCase()) || '';

View File

@ -1,14 +1,17 @@
/**
Definition of the JRSResume class.
@license MIT. See LICENSE.md for details.
@module core/jrs-resume
*/
(function() { (function() {
var AbstractResume, CONVERTER, FS, JRSResume, MD, PATH, _, _parseDates, extend, moment, validator, /**
extend1 = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, Definition of the JRSResume class.
hasProp = {}.hasOwnProperty; @license MIT. See LICENSE.md for details.
@module core/jrs-resume
*/
/**
Convert human-friendly dates into formal Moment.js dates for all collections.
We don't want to lose the raw textual date as entered by the user, so we store
the Moment-ified date as a separate property with a prefix of .safe. For ex:
job.startDate is the date as entered by the user. job.safeStartDate is the
parsed Moment.js date that we actually use in processing.
*/
var CONVERTER, FS, JRSResume, MD, PATH, _, _parseDates, extend, moment, validator;
FS = require('fs'); FS = require('fs');
@ -26,150 +29,247 @@ Definition of the JRSResume class.
moment = require('moment'); moment = require('moment');
AbstractResume = require('./abstract-resume'); JRSResume = (function() {
/** Reset the sheet to an empty state. */
/**
A JRS resume or CV. JRS resumes are backed by JSON, and each JRSResume object
is an instantiation of that JSON decorated with utility methods.
@class JRSResume
*/
JRSResume = (function(superClass) {
var clear; var clear;
extend1(JRSResume, superClass);
function JRSResume() {
return JRSResume.__super__.constructor.apply(this, arguments);
}
/** Initialize the the JSResume from string. */
JRSResume.prototype.parse = function(stringData, opts) {
var ref;
this.imp = (ref = this.imp) != null ? ref : {
raw: stringData
};
return this.parseJSON(JSON.parse(stringData), opts);
};
/** /**
Initialize the JRSResume object from JSON. A JRS resume or CV. JRS resumes are backed by JSON, and each JRSResume object
Open and parse the specified JRS resume. Merge the JSON object model onto is an instantiation of that JSON decorated with utility methods.
this Sheet instance with extend() and convert sheet dates to a safe & @class JRSResume
consistent format. Then sort each section by startDate descending. */
@param rep {Object} The raw JSON representation. class JRSResume { // extends AbstractResume
@param opts {Object} Resume loading and parsing options. /** Initialize the the JSResume from string. */
{ parse(stringData, opts) {
var ref;
this.imp = (ref = this.imp) != null ? ref : {
raw: stringData
};
return this.parseJSON(JSON.parse(stringData), opts);
}
/**
Initialize the JRSResume object from JSON.
Open and parse the specified JRS resume. Merge the JSON object model onto
this Sheet instance with extend() and convert sheet dates to a safe &
consistent format. Then sort each section by startDate descending.
@param rep {Object} The raw JSON representation.
@param opts {Object} Resume loading and parsing options.
{
date: Perform safe date conversion. date: Perform safe date conversion.
sort: Sort resume items by date. sort: Sort resume items by date.
compute: Prepare computed resume totals. compute: Prepare computed resume totals.
}
*/
JRSResume.prototype.parseJSON = function(rep, opts) {
var ignoreList, privateList, ref, ref1, scrubbed;
opts = opts || {};
if (opts.privatize) {
ref = this.scrubResume(rep, opts), scrubbed = ref.scrubbed, ignoreList = ref.ignoreList, privateList = ref.privateList;
} }
extend(true, this, opts.privatize ? scrubbed : rep); */
if (!((ref1 = this.imp) != null ? ref1.processed : void 0)) { parseJSON(rep, opts) {
var ignoreList, privateList, ref, scrubbed, scrubber;
opts = opts || {}; opts = opts || {};
if (opts.imp === void 0 || opts.imp) { if (opts.privatize) {
this.imp = this.imp || {}; scrubber = require('../utils/resume-scrubber');
this.imp.title = (opts.title || this.imp.title) || this.basics.name; // Ignore any element with the 'ignore: true' or 'private: true' designator.
if (!this.imp.raw) { ({scrubbed, ignoreList, privateList} = scrubber.scrubResume(rep, opts));
this.imp.raw = JSON.stringify(rep);
}
} }
this.imp.processed = true; // Extend resume properties onto ourself.
extend(true, this, opts.privatize ? scrubbed : rep);
if (!((ref = this.imp) != null ? ref.processed : void 0)) {
// Set up metadata TODO: Clean up metadata on the object model.
opts = opts || {};
if (opts.imp === void 0 || opts.imp) {
this.imp = this.imp || {};
this.imp.title = (opts.title || this.imp.title) || this.basics.name;
if (!this.imp.raw) {
this.imp.raw = JSON.stringify(rep);
}
}
this.imp.processed = true;
}
// Parse dates, sort dates, and calculate computed values
(opts.date === void 0 || opts.date) && _parseDates.call(this);
(opts.sort === void 0 || opts.sort) && this.sort();
if (opts.compute === void 0 || opts.compute) {
this.basics.computed = {
numYears: this.duration(),
keywords: this.keywords()
};
}
return this;
} }
(opts.date === void 0 || opts.date) && _parseDates.call(this);
(opts.sort === void 0 || opts.sort) && this.sort();
if (opts.compute === void 0 || opts.compute) {
this.basics.computed = {
numYears: this.duration(),
keywords: this.keywords()
};
}
return this;
};
/** Save the sheet to disk (for environments that have disk access). */
/** Save the sheet to disk (for environments that have disk access). */ save(filename) {
JRSResume.prototype.save = function(filename) {
this.imp.file = filename || this.imp.file;
FS.writeFileSync(this.imp.file, this.stringify(this), 'utf8');
return this;
};
/** Save the sheet to disk in a specific format, either FRESH or JRS. */
JRSResume.prototype.saveAs = function(filename, format) {
var newRep, stringRep;
if (format === 'JRS') {
this.imp.file = filename || this.imp.file; this.imp.file = filename || this.imp.file;
FS.writeFileSync(this.imp.file, this.stringify(), 'utf8'); FS.writeFileSync(this.imp.file, this.stringify(this), 'utf8');
} else { return this;
newRep = CONVERTER.toFRESH(this);
stringRep = CONVERTER.toSTRING(newRep);
FS.writeFileSync(filename, stringRep, 'utf8');
} }
return this;
};
/** Save the sheet to disk in a specific format, either FRESH or JRS. */
saveAs(filename, format) {
var newRep, stringRep;
if (format === 'JRS') {
this.imp.file = filename || this.imp.file;
FS.writeFileSync(this.imp.file, this.stringify(), 'utf8');
} else {
newRep = CONVERTER.toFRESH(this);
stringRep = CONVERTER.toSTRING(newRep);
FS.writeFileSync(filename, stringRep, 'utf8');
}
return this;
}
/** Return the resume format. */ /** Return the resume format. */
format() {
return 'JRS';
}
JRSResume.prototype.format = function() { stringify() {
return 'JRS'; return JRSResume.stringify(this);
}; }
JRSResume.prototype.stringify = function() { /** Return a unique list of all keywords across all skills. */
return JRSResume.stringify(this); keywords() {
}; var flatSkills;
flatSkills = [];
if (this.skills && this.skills.length) {
this.skills.forEach(function(s) {
return flatSkills = _.union(flatSkills, s.keywords);
});
}
return flatSkills;
}
/**
Return internal metadata. Create if it doesn't exist.
JSON Resume v0.0.0 doesn't allow additional properties at the root level,
so tuck this into the .basic sub-object.
*/
i() {
var ref;
return this.imp = (ref = this.imp) != null ? ref : {};
}
/** Return a unique list of all keywords across all skills. */ /** Add work experience to the sheet. */
add(moniker) {
var defSheet, newObject;
defSheet = JRSResume.default();
newObject = $.extend(true, {}, defSheet[moniker][0]);
this[moniker] = this[moniker] || [];
this[moniker].push(newObject);
return newObject;
}
JRSResume.prototype.keywords = function() { /** Determine if the sheet includes a specific social profile (eg, GitHub). */
var flatSkills; hasProfile(socialNetwork) {
flatSkills = []; socialNetwork = socialNetwork.trim().toLowerCase();
if (this.skills && this.skills.length) { return this.basics.profiles && _.some(this.basics.profiles, function(p) {
this.skills.forEach(function(s) { return p.network.trim().toLowerCase() === socialNetwork;
return flatSkills = _.union(flatSkills, s.keywords);
}); });
} }
return flatSkills;
/** Determine if the sheet includes a specific skill. */
hasSkill(skill) {
skill = skill.trim().toLowerCase();
return this.skills && _.some(this.skills, function(sk) {
return sk.keywords && _.some(sk.keywords, function(kw) {
return kw.trim().toLowerCase() === skill;
});
});
}
/** Validate the sheet against the JSON Resume schema. */
isValid() { // TODO: ↓ fix this path ↓
var ret, schema, schemaObj, temp, validate;
schema = FS.readFileSync(PATH.join(__dirname, 'resume.json'), 'utf8');
schemaObj = JSON.parse(schema);
validator = require('is-my-json-valid');
validate = validator(schemaObj, { // Note [1]
formats: {
date: /^\d{4}(?:-(?:0[0-9]{1}|1[0-2]{1})(?:-[0-9]{2})?)?$/
}
});
temp = this.imp;
delete this.imp;
ret = validate(this);
this.imp = temp;
if (!ret) {
this.imp = this.imp || {};
this.imp.validationErrors = validate.errors;
}
return ret;
}
duration(unit) {
var inspector;
inspector = require('../inspectors/duration-inspector');
return inspector.run(this, 'work', 'startDate', 'endDate', unit);
}
/**
Sort dated things on the sheet by start date descending. Assumes that dates
on the sheet have been processed with _parseDates().
*/
sort() {
var byDateDesc;
byDateDesc = function(a, b) {
if (a.safeStartDate.isBefore(b.safeStartDate)) {
return 1;
} else {
return (a.safeStartDate.isAfter(b.safeStartDate) && -1) || 0;
}
};
this.work && this.work.sort(byDateDesc);
this.education && this.education.sort(byDateDesc);
this.volunteer && this.volunteer.sort(byDateDesc);
this.awards && this.awards.sort(function(a, b) {
if (a.safeDate.isBefore(b.safeDate)) {
return 1;
} else {
return (a.safeDate.isAfter(b.safeDate) && -1) || 0;
}
});
return this.publications && this.publications.sort(function(a, b) {
if (a.safeReleaseDate.isBefore(b.safeReleaseDate)) {
return 1;
} else {
return (a.safeReleaseDate.isAfter(b.safeReleaseDate) && -1) || 0;
}
});
}
dupe() {
var rnew;
rnew = new JRSResume();
rnew.parse(this.stringify(), {});
return rnew;
}
/**
Create a copy of this resume in which all fields have been interpreted as
Markdown.
*/
harden() {
var HD, HDIN, ret, transformer;
ret = this.dupe();
HD = function(txt) {
return '@@@@~' + txt + '~@@@@';
};
HDIN = function(txt) {
//return MD(txt || '' ).replace(/^\s*<p>|<\/p>\s*$/gi, '');
return HD(txt);
};
transformer = require('../utils/string-transformer');
return transformer(ret, ['skills', 'url', 'website', 'startDate', 'endDate', 'releaseDate', 'date', 'phone', 'email', 'address', 'postalCode', 'city', 'country', 'region', 'safeStartDate', 'safeEndDate'], function(key, val) {
return HD(val);
});
}
}; };
/**
Return internal metadata. Create if it doesn't exist.
JSON Resume v0.0.0 doesn't allow additional properties at the root level,
so tuck this into the .basic sub-object.
*/
JRSResume.prototype.i = function() {
var ref;
return this.imp = (ref = this.imp) != null ? ref : {};
};
/** Reset the sheet to an empty state. */
clear = function(clearMeta) { clear = function(clearMeta) {
clearMeta = ((clearMeta === void 0) && true) || clearMeta; clearMeta = ((clearMeta === void 0) && true) || clearMeta;
if (clearMeta) { if (clearMeta) {
delete this.imp; delete this.imp;
} }
delete this.basics.computed; delete this.basics.computed; // Don't use Object.keys() here
delete this.work; delete this.work;
delete this.volunteer; delete this.volunteer;
delete this.education; delete this.education;
@ -180,183 +280,22 @@ Definition of the JRSResume class.
return delete this.basics.profiles; return delete this.basics.profiles;
}; };
/** Add work experience to the sheet. */
JRSResume.prototype.add = function(moniker) {
var defSheet, newObject;
defSheet = JRSResume["default"]();
newObject = $.extend(true, {}, defSheet[moniker][0]);
this[moniker] = this[moniker] || [];
this[moniker].push(newObject);
return newObject;
};
/** Determine if the sheet includes a specific social profile (eg, GitHub). */
JRSResume.prototype.hasProfile = function(socialNetwork) {
socialNetwork = socialNetwork.trim().toLowerCase();
return this.basics.profiles && _.some(this.basics.profiles, function(p) {
return p.network.trim().toLowerCase() === socialNetwork;
});
};
/** Determine if the sheet includes a specific skill. */
JRSResume.prototype.hasSkill = function(skill) {
skill = skill.trim().toLowerCase();
return this.skills && _.some(this.skills, function(sk) {
return sk.keywords && _.some(sk.keywords, function(kw) {
return kw.trim().toLowerCase() === skill;
});
});
};
/** Validate the sheet against the JSON Resume schema. */
JRSResume.prototype.isValid = function() {
var ret, schema, schemaObj, temp, validate;
schema = FS.readFileSync(PATH.join(__dirname, 'resume.json'), 'utf8');
schemaObj = JSON.parse(schema);
validator = require('is-my-json-valid');
validate = validator(schemaObj, {
formats: {
date: /^\d{4}(?:-(?:0[0-9]{1}|1[0-2]{1})(?:-[0-9]{2})?)?$/
}
});
temp = this.imp;
delete this.imp;
ret = validate(this);
this.imp = temp;
if (!ret) {
this.imp = this.imp || {};
this.imp.validationErrors = validate.errors;
}
return ret;
};
JRSResume.prototype.duration = function(unit) {
return JRSResume.__super__.duration.call(this, 'work', 'startDate', 'endDate', unit);
};
/**
Sort dated things on the sheet by start date descending. Assumes that dates
on the sheet have been processed with _parseDates().
*/
JRSResume.prototype.sort = function() {
var byDateDesc;
byDateDesc = function(a, b) {
if (a.safeStartDate.isBefore(b.safeStartDate)) {
return 1;
} else {
return (a.safeStartDate.isAfter(b.safeStartDate) && -1) || 0;
}
};
this.work && this.work.sort(byDateDesc);
this.education && this.education.sort(byDateDesc);
this.volunteer && this.volunteer.sort(byDateDesc);
this.awards && this.awards.sort(function(a, b) {
if (a.safeDate.isBefore(b.safeDate)) {
return 1;
} else {
return (a.safeDate.isAfter(b.safeDate) && -1) || 0;
}
});
return this.publications && this.publications.sort(function(a, b) {
if (a.safeReleaseDate.isBefore(b.safeReleaseDate)) {
return 1;
} else {
return (a.safeReleaseDate.isAfter(b.safeReleaseDate) && -1) || 0;
}
});
};
JRSResume.prototype.dupe = function() {
var rnew;
rnew = new JRSResume();
rnew.parse(this.stringify(), {});
return rnew;
};
/**
Create a copy of this resume in which all fields have been interpreted as
Markdown.
*/
JRSResume.prototype.harden = function() {
var HD, HDIN, hardenStringsInObject, ret, that;
that = this;
ret = this.dupe();
HD = function(txt) {
return '@@@@~' + txt + '~@@@@';
};
HDIN = function(txt) {
return HD(txt);
};
hardenStringsInObject = function(obj, inline) {
if (!obj) {
return;
}
inline = inline === void 0 || inline;
if (Object.prototype.toString.call(obj) === '[object Array]') {
return obj.forEach(function(elem, idx, ar) {
if (typeof elem === 'string' || elem instanceof String) {
return ar[idx] = inline ? HDIN(elem) : HD(elem);
} else {
return hardenStringsInObject(elem);
}
});
} else if (typeof obj === 'object') {
return Object.keys(obj).forEach(function(key) {
var sub;
sub = obj[key];
if (typeof sub === 'string' || sub instanceof String) {
if (_.contains(['skills', 'url', 'website', 'startDate', 'endDate', 'releaseDate', 'date', 'phone', 'email', 'address', 'postalCode', 'city', 'country', 'region'], key)) {
return;
}
if (key === 'summary') {
return obj[key] = HD(obj[key]);
} else {
return obj[key] = inline ? HDIN(obj[key]) : HD(obj[key]);
}
} else {
return hardenStringsInObject(sub);
}
});
}
};
Object.keys(ret).forEach(function(member) {
return hardenStringsInObject(ret[member]);
});
return ret;
};
return JRSResume; return JRSResume;
})(AbstractResume); }).call(this);
/** Get the default (empty) sheet. */ /** Get the default (empty) sheet. */
JRSResume.default = function() {
JRSResume["default"] = function() {
return new JRSResume().parseJSON(require('fresh-resume-starter').jrs); return new JRSResume().parseJSON(require('fresh-resume-starter').jrs);
}; };
/** /**
Convert this object to a JSON string, sanitizing meta-properties along the Convert this object to a JSON string, sanitizing meta-properties along the
way. Don't override .toString(). way. Don't override .toString().
*/ */
JRSResume.stringify = function(obj) { JRSResume.stringify = function(obj) {
var replacer; var replacer;
replacer = function(key, value) { replacer = function(key, value) { // Exclude these keys from stringification
var temp; var temp;
temp = _.some(['imp', 'warnings', 'computed', 'filt', 'ctrl', 'index', 'safeStartDate', 'safeEndDate', 'safeDate', 'safeReleaseDate', 'result', 'isModified', 'htmlPreview', 'display_progress_bar'], function(val) { temp = _.some(['imp', 'warnings', 'computed', 'filt', 'ctrl', 'index', 'safeStartDate', 'safeEndDate', 'safeDate', 'safeReleaseDate', 'result', 'isModified', 'htmlPreview', 'display_progress_bar'], function(val) {
return key.trim() === val; return key.trim() === val;
@ -370,15 +309,6 @@ Definition of the JRSResume class.
return JSON.stringify(obj, replacer, 2); return JSON.stringify(obj, replacer, 2);
}; };
/**
Convert human-friendly dates into formal Moment.js dates for all collections.
We don't want to lose the raw textual date as entered by the user, so we store
the Moment-ified date as a separate property with a prefix of .safe. For ex:
job.startDate is the date as entered by the user. job.safeStartDate is the
parsed Moment.js date that we actually use in processing.
*/
_parseDates = function() { _parseDates = function() {
var _fmt; var _fmt;
_fmt = require('./fluent-date').fmt; _fmt = require('./fluent-date').fmt;
@ -402,11 +332,9 @@ Definition of the JRSResume class.
}); });
}; };
/** /**
Export the JRSResume function/ctor. Export the JRSResume function/ctor.
*/ */
module.exports = JRSResume; module.exports = JRSResume;
}).call(this); }).call(this);

View File

@ -1,11 +1,9 @@
/**
Definition of the JRSTheme class.
@module core/jrs-theme
@license MIT. See LICENSE.MD for details.
*/
(function() { (function() {
/**
Definition of the JRSTheme class.
@module core/jrs-theme
@license MIT. See LICENSE.MD for details.
*/
var JRSTheme, PATH, _, errors, parsePath, pathExists; var JRSTheme, PATH, _, errors, parsePath, pathExists;
_ = require('underscore'); _ = require('underscore');
@ -18,32 +16,30 @@ Definition of the JRSTheme class.
errors = require('./status-codes'); errors = require('./status-codes');
/** /**
The JRSTheme class is a representation of a JSON Resume theme asset. The JRSTheme class is a representation of a JSON Resume theme asset.
@class JRSTheme @class JRSTheme
*/ */
JRSTheme = class JRSTheme {
JRSTheme = (function() {
function JRSTheme() {}
/** /**
Open and parse the specified JRS theme. Open and parse the specified JRS theme.
@method open @method open
*/ */
open(thFolder) {
JRSTheme.prototype.open = function(thFolder) {
var pathInfo, pkgJsonPath, thApi, thPkg; var pathInfo, pkgJsonPath, thApi, thPkg;
this.folder = thFolder; this.folder = thFolder;
pathInfo = parsePath(thFolder); pathInfo = parsePath(thFolder);
// Open and parse the theme's package.json file
pkgJsonPath = PATH.join(thFolder, 'package.json'); pkgJsonPath = PATH.join(thFolder, 'package.json');
if (pathExists(pkgJsonPath)) { if (pathExists(pkgJsonPath)) {
thApi = require(thFolder); thApi = require(thFolder); // Requiring the folder yields whatever the package.json's "main" is set to
thPkg = require(pkgJsonPath); thPkg = require(pkgJsonPath); // Get the package.json as JSON
this.name = thPkg.name; this.name = thPkg.name;
this.render = (thApi && thApi.render) || void 0; this.render = (thApi && thApi.render) || void 0;
this.engine = 'jrs'; this.engine = 'jrs';
// Create theme formats (HTML and PDF). Just add the bare minimum mix of
// properties necessary to allow JSON Resume themes to share a rendering
// path with FRESH themes.
this.formats = { this.formats = {
html: { html: {
outFormat: 'html', outFormat: 'html',
@ -76,31 +72,25 @@ Definition of the JRSTheme class.
}; };
} }
return this; return this;
}; }
/** /**
Determine if the theme supports the output format. Determine if the theme supports the output format.
@method hasFormat @method hasFormat
*/ */
hasFormat(fmt) {
JRSTheme.prototype.hasFormat = function(fmt) {
return _.has(this.formats, fmt); return _.has(this.formats, fmt);
}; }
/** /**
Return the requested output format. Return the requested output format.
@method getFormat @method getFormat
*/ */
getFormat(fmt) {
JRSTheme.prototype.getFormat = function(fmt) {
return this.formats[fmt]; return this.formats[fmt];
}; }
return JRSTheme; };
})();
module.exports = JRSTheme; module.exports = JRSTheme;

View File

@ -1,11 +1,13 @@
/**
Definition of the ResumeFactory class.
@license MIT. See LICENSE.md for details.
@module core/resume-factory
*/
(function() { (function() {
/**
Definition of the ResumeFactory class.
@license MIT. See LICENSE.md for details.
@module core/resume-factory
*/
/**
A simple factory class for FRESH and JSON Resumes.
@class ResumeFactory
*/
var FS, HME, HMS, ResumeConverter, ResumeFactory, SyntaxErrorEx, _, _parse, chalk, resumeDetect; var FS, HME, HMS, ResumeConverter, ResumeFactory, SyntaxErrorEx, _, _parse, chalk, resumeDetect;
FS = require('fs'); FS = require('fs');
@ -26,20 +28,13 @@ Definition of the ResumeFactory class.
require('string.prototype.startswith'); require('string.prototype.startswith');
/**
A simple factory class for FRESH and JSON Resumes.
@class ResumeFactory
*/
ResumeFactory = module.exports = { ResumeFactory = module.exports = {
/** /**
Load one or more resumes from disk. Load one or more resumes from disk.
@param {Object} opts An options object with settings for the factory as well @param {Object} opts An options object with settings for the factory as well
as passthrough settings for FRESHResume or JRSResume. Structure: as passthrough settings for FRESHResume or JRSResume. Structure:
{ {
format: 'FRESH', // Format to open as. ('FRESH', 'JRS', null) format: 'FRESH', // Format to open as. ('FRESH', 'JRS', null)
objectify: true, // FRESH/JRSResume or raw JSON? objectify: true, // FRESH/JRSResume or raw JSON?
@ -47,31 +42,38 @@ Definition of the ResumeFactory class.
sort: false sort: false
} }
} }
*/
*/
load: function(sources, opts, emitter) { load: function(sources, opts, emitter) {
return sources.map(function(src) { return sources.map(function(src) {
return this.loadOne(src, opts, emitter); return this.loadOne(src, opts, emitter);
}, this); }, this);
}, },
/** Load a single resume from disk. */
/** Load a single resume from disk. */
loadOne: function(src, opts, emitter) { loadOne: function(src, opts, emitter) {
var ResumeClass, info, json, orgFormat, reqLib, rez, toFormat; var ResumeClass, info, json, orgFormat, reqLib, rez, toFormat;
toFormat = opts.format; toFormat = opts.format; // Can be null
// Get the destination format. Can be 'fresh', 'jrs', or null/undefined.
toFormat && (toFormat = toFormat.toLowerCase().trim()); toFormat && (toFormat = toFormat.toLowerCase().trim());
// Load and parse the resume JSON
info = _parse(src, opts, emitter); info = _parse(src, opts, emitter);
if (info.fluenterror) { if (info.fluenterror) {
return info; return info;
} }
// Determine the resume format: FRESH or JRS
json = info.json; json = info.json;
orgFormat = resumeDetect(json); orgFormat = resumeDetect(json);
if (orgFormat === 'unk') { if (orgFormat === 'unk') {
info.fluenterror = HMS.unknownSchema; info.fluenterror = HMS.unknownSchema;
return info; return info;
} }
// Convert between formats if necessary
if (toFormat && (orgFormat !== toFormat)) { if (toFormat && (orgFormat !== toFormat)) {
json = ResumeConverter['to' + toFormat.toUpperCase()](json); json = ResumeConverter['to' + toFormat.toUpperCase()](json);
} }
// Objectify the resume, that is, convert it from JSON to a FRESHResume
// or JRSResume object.
rez = null; rez = null;
if (opts.objectify) { if (opts.objectify) {
reqLib = '../core/' + (toFormat || orgFormat) + '-resume'; reqLib = '../core/' + (toFormat || orgFormat) + '-resume';
@ -88,9 +90,10 @@ Definition of the ResumeFactory class.
}; };
_parse = function(fileName, opts, eve) { _parse = function(fileName, opts, eve) {
var orgFormat, rawData, ret; var err, orgFormat, rawData, ret;
rawData = null; rawData = null;
try { try {
// Read the file
eve && eve.stat(HME.beforeRead, { eve && eve.stat(HME.beforeRead, {
file: fileName file: fileName
}); });
@ -112,10 +115,12 @@ Definition of the ResumeFactory class.
fmt: orgFormat fmt: orgFormat
}); });
return ret; return ret;
} catch (_error) { } catch (error) {
err = error;
return { return {
// Can be ENOENT, EACCES, SyntaxError, etc.
fluenterror: rawData ? HMS.parseError : HMS.readError, fluenterror: rawData ? HMS.parseError : HMS.readError,
inner: _error, inner: err,
raw: rawData, raw: rawData,
file: fileName file: fileName
}; };

View File

@ -1,11 +1,9 @@
/**
Status codes for HackMyResume.
@module core/status-codes
@license MIT. See LICENSE.MD for details.
*/
(function() { (function() {
/**
Status codes for HackMyResume.
@module core/status-codes
@license MIT. See LICENSE.MD for details.
*/
module.exports = { module.exports = {
success: 0, success: 0,
themeNotFound: 1, themeNotFound: 1,
@ -37,7 +35,8 @@ Status codes for HackMyResume.
invalidOptionsFile: 27, invalidOptionsFile: 27,
optionsFileNotFound: 28, optionsFileNotFound: 28,
unknownSchema: 29, unknownSchema: 29,
themeHelperLoad: 30 themeHelperLoad: 30,
invalidSchemaVersion: 31
}; };
}).call(this); }).call(this);

View File

@ -1,39 +1,33 @@
/**
Definition of the BaseGenerator class.
@module generators/base-generator
@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() {
/**
Definition of the BaseGenerator class.
@module generators/base-generator
@license MIT. See LICENSE.md for details.
*/
var BaseGenerator; var BaseGenerator;
/**
The BaseGenerator class is the root of the generator hierarchy. Functionality
common to ALL generators lives here.
*/
module.exports = BaseGenerator = (function() { module.exports = BaseGenerator = (function() {
class BaseGenerator {
/** Base-class initialize. */
constructor(format) {
this.format = format;
}
/** Base-class initialize. */ };
function BaseGenerator(format) {
this.format = format;
}
/** Status codes. */ /** Status codes. */
BaseGenerator.prototype.codes = require('../core/status-codes'); BaseGenerator.prototype.codes = require('../core/status-codes');
/** Generator options. */ /** Generator options. */
BaseGenerator.prototype.opts = {}; BaseGenerator.prototype.opts = {};
return BaseGenerator; return BaseGenerator;
})(); }).call(this);
}).call(this); }).call(this);

View File

@ -1,14 +1,10 @@
/**
Definition of the HTMLGenerator class.
@module generators/html-generator
@license MIT. See LICENSE.md for details.
*/
(function() { (function() {
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; }, Definition of the HTMLGenerator class.
hasProp = {}.hasOwnProperty; @module generators/html-generator
@license MIT. See LICENSE.md for details.
*/
var FS, HTML, HtmlGenerator, PATH, TemplateGenerator;
TemplateGenerator = require('./template-generator'); TemplateGenerator = require('./template-generator');
@ -20,20 +16,16 @@ Definition of the HTMLGenerator class.
require('string.prototype.endswith'); require('string.prototype.endswith');
module.exports = HtmlGenerator = (function(superClass) { module.exports = HtmlGenerator = class HtmlGenerator extends TemplateGenerator {
extend(HtmlGenerator, superClass); constructor() {
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(info) {
HtmlGenerator.prototype.onBeforeSave = function(info) {
if (info.outputFile.endsWith('.css')) { if (info.outputFile.endsWith('.css')) {
return info.mk; return info.mk;
} }
@ -42,11 +34,9 @@ Definition of the HTMLGenerator class.
} else { } else {
return info.mk; return info.mk;
} }
}; }
return HtmlGenerator; };
})(TemplateGenerator);
}).call(this); }).call(this);

View File

@ -1,14 +1,10 @@
/**
Definition of the HtmlPdfCLIGenerator class.
@module generators/html-pdf-generator.js
@license MIT. See LICENSE.md for details.
*/
(function() { (function() {
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; }, Definition of the HtmlPdfCLIGenerator class.
hasProp = {}.hasOwnProperty; @module generators/html-pdf-generator.js
@license MIT. See LICENSE.md for details.
*/
var FS, HMSTATUS, HtmlPdfCLIGenerator, PATH, SLASH, SPAWN, TemplateGenerator, _, engines;
TemplateGenerator = require('./template-generator'); TemplateGenerator = require('./template-generator');
@ -24,26 +20,21 @@ Definition of the HtmlPdfCLIGenerator class.
SPAWN = require('../utils/safe-spawn'); 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,
wkhtmltopdf, and other PDF engines over a CLI (command-line interface). 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.
*/ */
module.exports = HtmlPdfCLIGenerator = class HtmlPdfCLIGenerator extends TemplateGenerator {
module.exports = HtmlPdfCLIGenerator = (function(superClass) { constructor() {
extend(HtmlPdfCLIGenerator, superClass); super('pdf', 'html');
function HtmlPdfCLIGenerator() {
HtmlPdfCLIGenerator.__super__.constructor.call(this, 'pdf', 'html');
} }
/** Generate the binary PDF. */ /** Generate the binary PDF. */
onBeforeSave(info) {
HtmlPdfCLIGenerator.prototype.onBeforeSave = function(info) {
var safe_eng; var safe_eng;
if (info.ext !== 'html' && info.ext !== 'pdf') { if (info.ext !== 'html' && info.ext !== 'pdf') {
//console.dir _.omit( info, 'mk' ), depth: null, colors: true
return info.mk; return info.mk;
} }
safe_eng = info.opts.pdf || 'wkhtmltopdf'; safe_eng = info.opts.pdf || 'wkhtmltopdf';
@ -53,43 +44,40 @@ Definition of the HtmlPdfCLIGenerator class.
if (_.has(engines, safe_eng)) { if (_.has(engines, safe_eng)) {
this.errHandler = info.opts.errHandler; this.errHandler = info.opts.errHandler;
engines[safe_eng].call(this, info.mk, info.outputFile, info.opts, this.onError); engines[safe_eng].call(this, info.mk, info.outputFile, info.opts, this.onError);
return null; return null; // halt further processing
} }
}; }
/* Low-level error callback for spawn(). May be called after HMR process /* Low-level error callback for spawn(). May be called after HMR process
termination, so object references may not be valid here. That's okay; if termination, so object references may not be valid here. That's okay; if
the references are invalid, the error was already logged. We could use the references are invalid, the error was already logged. We could use
spawn-watch here but that causes issues on legacy Node.js. spawn-watch here but that causes issues on legacy Node.js. */
*/ onError(ex, param) {
HtmlPdfCLIGenerator.prototype.onError = function(ex, param) {
var ref; var ref;
if ((ref = param.errHandler) != null) { if ((ref = param.errHandler) != null) {
if (typeof ref.err === "function") { if (typeof ref.err === "function") {
ref.err(HMSTATUS.pdfGeneration, ex); ref.err(HMSTATUS.pdfGeneration, ex);
} }
} }
}; }
return HtmlPdfCLIGenerator; };
})(TemplateGenerator);
// TODO: Move each engine to a separate module
engines = { engines = {
/** /**
Generate a PDF from HTML using wkhtmltopdf's CLI interface. Generate a PDF from HTML using wkhtmltopdf's CLI interface.
Spawns a child process with `wkhtmltopdf <source> <target>`. wkhtmltopdf Spawns a child process with `wkhtmltopdf <source> <target>`. wkhtmltopdf
must be installed and path-accessible. must be installed and path-accessible.
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, opts, on_error) { wkhtmltopdf: function(markup, fOut, opts, on_error) {
var tempFile, wkargs, wkopts; var tempFile, wkargs, wkopts;
// 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');
// Prepare wkhtmltopdf arguments.
wkopts = _.extend({ wkopts = _.extend({
'margin-top': '10mm', 'margin-top': '10mm',
'margin-bottom': '10mm' 'margin-bottom': '10mm'
@ -100,16 +88,16 @@ Definition of the HtmlPdfCLIGenerator class.
wkargs = wkopts.concat([tempFile, fOut]); wkargs = wkopts.concat([tempFile, fOut]);
SPAWN('wkhtmltopdf', wkargs, false, on_error, this); SPAWN('wkhtmltopdf', wkargs, false, on_error, this);
}, },
/** /**
Generate a PDF from HTML using Phantom's CLI interface. Generate a PDF from HTML using Phantom's CLI interface.
Spawns a child process with `phantomjs <script> <source> <target>`. Phantom Spawns a child process with `phantomjs <script> <source> <target>`. Phantom
must be installed and path-accessible. must be installed and path-accessible.
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: function(markup, fOut, opts, on_error) { phantomjs: function(markup, fOut, opts, on_error) {
var destPath, scriptPath, sourcePath, tempFile; var destPath, scriptPath, sourcePath, tempFile;
// 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 = PATH.relative(process.cwd(), PATH.resolve(__dirname, '../utils/rasterize.js')); scriptPath = PATH.relative(process.cwd(), PATH.resolve(__dirname, '../utils/rasterize.js'));
@ -118,15 +106,15 @@ Definition of the HtmlPdfCLIGenerator class.
destPath = SLASH(PATH.relative(process.cwd(), fOut)); destPath = SLASH(PATH.relative(process.cwd(), fOut));
SPAWN('phantomjs', [scriptPath, sourcePath, destPath], false, on_error, this); SPAWN('phantomjs', [scriptPath, sourcePath, destPath], false, on_error, this);
}, },
/** /**
Generate a PDF from HTML using WeasyPrint's CLI interface. Generate a PDF from HTML using WeasyPrint's CLI interface.
Spawns a child process with `weasyprint <source> <target>`. Weasy Print Spawns a child process with `weasyprint <source> <target>`. Weasy Print
must be installed and path-accessible. must be installed and path-accessible.
TODO: If HTML generation has run, reuse that output TODO: If HTML generation has run, reuse that output
*/ */
weasyprint: function(markup, fOut, opts, on_error) { weasyprint: function(markup, fOut, opts, on_error) {
var tempFile; var tempFile;
// 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');
SPAWN('weasyprint', [tempFile, fOut], false, on_error, this); SPAWN('weasyprint', [tempFile, fOut], false, on_error, this);

View File

@ -1,14 +1,17 @@
/**
Definition of the HtmlPngGenerator class.
@module generators/html-png-generator
@license MIT. See LICENSE.MD for details.
*/
(function() { (function() {
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; }, Definition of the HtmlPngGenerator class.
hasProp = {}.hasOwnProperty; @module generators/html-png-generator
@license MIT. See LICENSE.MD for details.
*/
/**
Generate a PDF from HTML using Phantom's CLI interface.
Spawns a child process with `phantomjs <script> <source> <target>`. Phantom
must be installed and path-accessible.
TODO: If HTML generation has run, reuse that output
TODO: Local web server to ease Phantom rendering
*/
var FS, HTML, HtmlPngGenerator, PATH, SLASH, SPAWN, TemplateGenerator, phantom;
TemplateGenerator = require('./template-generator'); TemplateGenerator = require('./template-generator');
@ -22,21 +25,18 @@ Definition of the HtmlPngGenerator class.
PATH = require('path'); PATH = require('path');
/** /**
An HTML-based PNG resume generator for HackMyResume. An HTML-based PNG resume generator for HackMyResume.
*/ */
module.exports = HtmlPngGenerator = class HtmlPngGenerator extends TemplateGenerator {
module.exports = HtmlPngGenerator = (function(superClass) { constructor() {
extend(HtmlPngGenerator, superClass); super('png', 'html');
function HtmlPngGenerator() {
HtmlPngGenerator.__super__.constructor.call(this, 'png', 'html');
} }
HtmlPngGenerator.prototype.invoke = function(rez, themeMarkup, cssInfo, opts) {}; invoke(rez, themeMarkup, cssInfo, opts) {}
HtmlPngGenerator.prototype.generate = function(rez, f, opts) { // TODO: Not currently called or callable.
generate(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';
@ -45,23 +45,13 @@ 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);
/**
Generate a PDF from HTML using Phantom's CLI interface.
Spawns a child process with `phantomjs <script> <source> <target>`. Phantom
must be installed and path-accessible.
TODO: If HTML generation has run, reuse that output
TODO: Local web server to ease Phantom rendering
*/
phantom = function(markup, fOut) { phantom = function(markup, fOut) {
var destPath, info, scriptPath, sourcePath, tempFile; var destPath, info, scriptPath, sourcePath, tempFile;
// Save the markup to a temporary file
tempFile = fOut.replace(/\.png$/i, '.png.html'); tempFile = fOut.replace(/\.png$/i, '.png.html');
FS.writeFileSync(tempFile, markup, 'utf8'); FS.writeFileSync(tempFile, markup, 'utf8');
scriptPath = SLASH(PATH.relative(process.cwd(), PATH.resolve(__dirname, '../utils/rasterize.js'))); scriptPath = SLASH(PATH.relative(process.cwd(), PATH.resolve(__dirname, '../utils/rasterize.js')));

View File

@ -1,14 +1,10 @@
/**
Definition of the JsonGenerator class.
@module generators/json-generator
@license MIT. See LICENSE.md for details.
*/
(function() { (function() {
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; }, Definition of the JsonGenerator class.
hasProp = {}.hasOwnProperty; @module generators/json-generator
@license MIT. See LICENSE.md for details.
*/
var BaseGenerator, FJCV, FS, JsonGenerator, _;
BaseGenerator = require('./base-generator'); BaseGenerator = require('./base-generator');
@ -18,29 +14,24 @@ Definition of the JsonGenerator class.
FJCV = require('fresh-jrs-converter'); FJCV = require('fresh-jrs-converter');
/** The JsonGenerator generates a FRESH or JRS resume as an output. */ /** The JsonGenerator generates a FRESH or JRS resume as an output. */
module.exports = JsonGenerator = class JsonGenerator extends BaseGenerator {
module.exports = JsonGenerator = (function(superClass) { constructor() {
extend(JsonGenerator, superClass); super('json');
function JsonGenerator() {
JsonGenerator.__super__.constructor.call(this, 'json');
} }
JsonGenerator.prototype.invoke = function(rez) { invoke(rez) {
var altRez; var altRez;
altRez = FJCV['to' + (rez.format() === 'FRESH' ? 'JRS' : 'FRESH')](rez); altRez = FJCV['to' + (rez.format() === 'FRESH' ? 'JRS' : 'FRESH')](rez);
return altRez = FJCV.toSTRING(altRez); return altRez = FJCV.toSTRING(altRez);
}; }
JsonGenerator.prototype.generate = function(rez, f) { //altRez.stringify()
generate(rez, f) {
FS.writeFileSync(f, this.invoke(rez), 'utf8'); FS.writeFileSync(f, this.invoke(rez), 'utf8');
}; }
return JsonGenerator; };
})(BaseGenerator);
}).call(this); }).call(this);

View File

@ -1,14 +1,10 @@
/**
Definition of the JsonYamlGenerator class.
@module generators/json-yaml-generator
@license MIT. See LICENSE.md for details.
*/
(function() { (function() {
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; }, Definition of the JsonYamlGenerator class.
hasProp = {}.hasOwnProperty; @module generators/json-yaml-generator
@license MIT. See LICENSE.md for details.
*/
var BaseGenerator, FS, JsonYamlGenerator, YAML;
BaseGenerator = require('./base-generator'); BaseGenerator = require('./base-generator');
@ -16,34 +12,28 @@ Definition of the JsonYamlGenerator class.
YAML = require('yamljs'); YAML = require('yamljs');
/** /**
JsonYamlGenerator takes a JSON resume object and translates it directly to JsonYamlGenerator takes a JSON resume object and translates it directly to
JSON without a template, producing an equivalent YAML-formatted resume. See JSON without a template, producing an equivalent YAML-formatted resume. See
also YamlGenerator (yaml-generator.js). also YamlGenerator (yaml-generator.js).
*/ */
module.exports = JsonYamlGenerator = class JsonYamlGenerator extends BaseGenerator {
module.exports = JsonYamlGenerator = (function(superClass) { constructor() {
extend(JsonYamlGenerator, superClass); super('yml');
function JsonYamlGenerator() {
JsonYamlGenerator.__super__.constructor.call(this, 'yml');
} }
JsonYamlGenerator.prototype.invoke = function(rez, themeMarkup, cssInfo, opts) { invoke(rez, themeMarkup, cssInfo, opts) {
return YAML.stringify(JSON.parse(rez.stringify()), Infinity, 2); return YAML.stringify(JSON.parse(rez.stringify()), 2e308, 2);
}; }
JsonYamlGenerator.prototype.generate = function(rez, f, opts) { generate(rez, f, opts) {
var data; var data;
data = YAML.stringify(JSON.parse(rez.stringify()), Infinity, 2); data = YAML.stringify(JSON.parse(rez.stringify()), 2e308, 2);
FS.writeFileSync(f, data, 'utf8'); FS.writeFileSync(f, data, 'utf8');
return data; return data;
}; }
return JsonYamlGenerator; };
})(BaseGenerator);
}).call(this); }).call(this);

View File

@ -1,32 +1,22 @@
/**
Definition of the LaTeXGenerator class.
@module generators/latex-generator
@license MIT. See LICENSE.md for details.
*/
(function() { (function() {
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; }, Definition of the LaTeXGenerator class.
hasProp = {}.hasOwnProperty; @module generators/latex-generator
@license MIT. See LICENSE.md for details.
*/
var LaTeXGenerator, TemplateGenerator;
TemplateGenerator = require('./template-generator'); TemplateGenerator = require('./template-generator');
/** /**
LaTeXGenerator generates a LaTeX resume via TemplateGenerator. LaTeXGenerator generates a LaTeX resume via TemplateGenerator.
*/ */
module.exports = LaTeXGenerator = class LaTeXGenerator extends TemplateGenerator {
module.exports = LaTeXGenerator = (function(superClass) { constructor() {
extend(LaTeXGenerator, superClass); super('latex', 'tex');
function LaTeXGenerator() {
LaTeXGenerator.__super__.constructor.call(this, 'latex', 'tex');
} }
return LaTeXGenerator; };
})(TemplateGenerator);
}).call(this); }).call(this);

View File

@ -1,32 +1,22 @@
/**
Definition of the MarkdownGenerator class.
@module generators/markdown-generator
@license MIT. See LICENSE.md for details.
*/
(function() { (function() {
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; }, Definition of the MarkdownGenerator class.
hasProp = {}.hasOwnProperty; @module generators/markdown-generator
@license MIT. See LICENSE.md for details.
*/
var MarkdownGenerator, TemplateGenerator;
TemplateGenerator = require('./template-generator'); TemplateGenerator = require('./template-generator');
/** /**
MarkdownGenerator generates a Markdown-formatted resume via TemplateGenerator. MarkdownGenerator generates a Markdown-formatted resume via TemplateGenerator.
*/ */
module.exports = MarkdownGenerator = class MarkdownGenerator extends TemplateGenerator {
module.exports = MarkdownGenerator = (function(superClass) { constructor() {
extend(MarkdownGenerator, superClass); super('md', 'txt');
function MarkdownGenerator() {
MarkdownGenerator.__super__.constructor.call(this, 'md', 'txt');
} }
return MarkdownGenerator; };
})(TemplateGenerator);
}).call(this); }).call(this);

View File

@ -1,14 +1,13 @@
/**
Definition of the TemplateGenerator class. TODO: Refactor
@module generators/template-generator
@license MIT. See LICENSE.md for details.
*/
(function() { (function() {
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; }, Definition of the TemplateGenerator class. TODO: Refactor
hasProp = {}.hasOwnProperty; @module generators/template-generator
@license MIT. See LICENSE.md for details.
*/
/** Default template generator options. */
/** Freeze newlines for protection against errant JST parsers. */
/** Unfreeze newlines when the coast is clear. */
var BaseGenerator, EXTEND, FRESHTheme, FS, JRSTheme, MD, MKDIRP, PATH, TemplateGenerator, XML, _, _defaultOpts, _reg, createSymLinks, freeze, parsePath, unfreeze;
FS = require('fs-extra'); FS = require('fs-extra');
@ -32,46 +31,38 @@ Definition of the TemplateGenerator class. TODO: Refactor
JRSTheme = require('../core/jrs-theme'); JRSTheme = require('../core/jrs-theme');
/** /**
TemplateGenerator performs resume generation via local Handlebar or Underscore TemplateGenerator performs resume generation via local Handlebar or Underscore
style template expansion and is appropriate for text-based formats like HTML, style template expansion and is appropriate for text-based formats like HTML,
plain text, and XML versions of Microsoft Word, Excel, and OpenOffice. plain text, and XML versions of Microsoft Word, Excel, and OpenOffice.
@class TemplateGenerator @class TemplateGenerator
*/ */
module.exports = TemplateGenerator = class TemplateGenerator extends BaseGenerator {
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. */
*/ constructor(outputFormat, templateFormat, cssFile) {
super(outputFormat);
function TemplateGenerator(outputFormat, templateFormat, cssFile) {
TemplateGenerator.__super__.constructor.call(this, outputFormat);
this.tplFormat = templateFormat || outputFormat; this.tplFormat = templateFormat || outputFormat;
return; 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.
@method invoke @method invoke
@param rez A FreshResume object. @param rez A FreshResume object.
@param opts Generator options. @param opts Generator options.
@returns {Array} An array of objects representing the generated output @returns {Array} An array of objects representing the generated output
files. files. */
*/ invoke(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;
// 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, function(fi) { curFmt.files = _.sortBy(curFmt.files, function(fi) {
return fi.ext !== 'css'; return fi.ext !== 'css';
}); });
// Run the transformation!
results = curFmt.files.map(function(tplInfo, idx) { results = curFmt.files.map(function(tplInfo, idx) {
var trx; var trx;
if (tplInfo.action === 'transform') { if (tplInfo.action === 'transform') {
@ -95,25 +86,30 @@ 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.
@method generate @method generate
@param rez A FreshResume object. @param rez A FreshResume object.
@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(rez, f, opts) {
TemplateGenerator.prototype.generate = function(rez, f, opts) {
var curFmt, genInfo, outFolder; var curFmt, genInfo, outFolder;
// Prepare
this.opts = EXTEND(true, {}, _defaultOpts, opts); this.opts = EXTEND(true, {}, _defaultOpts, opts);
// Call the string-based generation method
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);
// Process individual files within this format. For example, the HTML
// output format for a theme may have multiple HTML files, CSS files,
// etc. Process them here.
genInfo.files.forEach(function(file) { genInfo.files.forEach(function(file) {
var thisFilePath; var thisFilePath;
// console.dir _.omit(file.info,'cssData','data','css' )
// Pre-processing
file.info.orgPath = file.info.orgPath || ''; file.info.orgPath = file.info.orgPath || '';
thisFilePath = file.info.primary ? f : PATH.join(outFolder, file.info.orgPath); thisFilePath = file.info.primary ? f : PATH.join(outFolder, file.info.orgPath);
if (file.info.action !== 'copy' && this.onBeforeSave) { if (file.info.action !== 'copy' && this.onBeforeSave) {
@ -129,7 +125,9 @@ Definition of the TemplateGenerator class. TODO: Refactor
} }
} }
if (typeof opts.beforeWrite === "function") { if (typeof opts.beforeWrite === "function") {
opts.beforeWrite(thisFilePath); opts.beforeWrite({
data: thisFilePath
});
} }
MKDIRP.sync(PATH.dirname(thisFilePath)); MKDIRP.sync(PATH.dirname(thisFilePath));
if (file.info.action !== 'copy') { if (file.info.action !== 'copy') {
@ -141,8 +139,11 @@ Definition of the TemplateGenerator class. TODO: Refactor
FS.copySync(file.info.path, thisFilePath); FS.copySync(file.info.path, thisFilePath);
} }
if (typeof opts.afterWrite === "function") { if (typeof opts.afterWrite === "function") {
opts.afterWrite(thisFilePath); opts.afterWrite({
data: thisFilePath
});
} }
// Post-processing
if (this.onAfterSave) { if (this.onAfterSave) {
return this.onAfterSave({ return this.onAfterSave({
outputFile: fileName, outputFile: fileName,
@ -151,10 +152,10 @@ Definition of the TemplateGenerator class. TODO: Refactor
}); });
} }
}, this); }, this);
// Some themes require a symlink structure. If so, create it.
createSymLinks(curFmt, outFolder); createSymLinks(curFmt, outFolder);
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.
@ -162,10 +163,8 @@ Definition of the TemplateGenerator class. TODO: Refactor
@param jst The stringified template data @param jst The stringified template data
@param format The format name, such as "html" or "latex" @param format The format name, such as "html" or "latex"
@param cssInfo Needs to be refactored. @param cssInfo Needs to be refactored.
@param opts Options and passthrough data. @param opts Options and passthrough data. */
*/ transform(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);
@ -176,29 +175,30 @@ Definition of the TemplateGenerator class. TODO: Refactor
result = unfreeze(result); result = unfreeze(result);
} }
return result; return result;
}; }
return TemplateGenerator; };
})(BaseGenerator);
createSymLinks = function(curFmt, outFolder) { createSymLinks = function(curFmt, outFolder) {
// Some themes require a symlink structure. If so, create it.
if (curFmt.symLinks) { if (curFmt.symLinks) {
Object.keys(curFmt.symLinks).forEach(function(loc) { Object.keys(curFmt.symLinks).forEach(function(loc) {
var absLoc, absTarg, succeeded, type; var absLoc, absTarg, err, succeeded, type;
absLoc = PATH.join(outFolder, loc); absLoc = PATH.join(outFolder, loc);
absTarg = PATH.join(PATH.dirname(absLoc), curFmt.symLinks[loc]); absTarg = PATH.join(PATH.dirname(absLoc), curFmt.symLinks[loc]);
// Set type to 'file', 'dir', or 'junction' (Windows only)
type = parsePath(absLoc).extname ? 'file' : 'junction'; type = parsePath(absLoc).extname ? 'file' : 'junction';
try { try {
return FS.symlinkSync(absTarg, absLoc, type); return FS.symlinkSync(absTarg, absLoc, type);
} catch (_error) { } catch (error) {
err = error;
succeeded = false; succeeded = false;
if (_error.code === 'EEXIST') { if (err.code === 'EEXIST') {
FS.unlinkSync(absLoc); FS.unlinkSync(absLoc);
try { try {
FS.symlinkSync(absTarg, absLoc, type); FS.symlinkSync(absTarg, absLoc, type);
succeeded = true; succeeded = true;
} catch (_error) {} } catch (error) {}
} }
if (!succeeded) { if (!succeeded) {
throw ex; throw ex;
@ -208,31 +208,22 @@ Definition of the TemplateGenerator class. TODO: Refactor
} }
}; };
/** Freeze newlines for protection against errant JST parsers. */
freeze = function(markup) { freeze = function(markup) {
markup.replace(_reg.regN, _defaultOpts.nSym); markup.replace(_reg.regN, _defaultOpts.nSym);
return markup.replace(_reg.regR, _defaultOpts.rSym); return markup.replace(_reg.regR, _defaultOpts.rSym);
}; };
/** Unfreeze newlines when the coast is clear. */
unfreeze = function(markup) { unfreeze = function(markup) {
markup.replace(_reg.regSymR, '\r'); markup.replace(_reg.regSymR, '\r');
return markup.replace(_reg.regSymN, '\n'); return markup.replace(_reg.regSymN, '\n');
}; };
/** Default template generator options. */
_defaultOpts = { _defaultOpts = {
engine: 'underscore', engine: 'underscore',
keepBreaks: true, keepBreaks: true,
freezeBreaks: false, freezeBreaks: false,
nSym: '&newl;', nSym: '&newl;', // newline entity
rSym: '&retn;', rSym: '&retn;', // return entity
template: { template: {
interpolate: /\{\{(.+?)\}\}/g, interpolate: /\{\{(.+?)\}\}/g,
escape: /\{\{\=(.+?)\}\}/g, escape: /\{\{\=(.+?)\}\}/g,
@ -269,13 +260,12 @@ Definition of the TemplateGenerator class. TODO: Refactor
prettify: { prettify: {
indent_size: 2, indent_size: 2,
unformatted: ['em', 'strong', 'a'], unformatted: ['em', 'strong', 'a'],
max_char: 80 max_char: 80 // ← See lib/html.js in above-linked repo
} }
}; };
//wrap_line_length: 120, <-- Don't use this
/** Regexes for linebreak preservation. */ /** Regexes for linebreak preservation. */
_reg = { _reg = {
regN: new RegExp('\n', 'g'), regN: new RegExp('\n', 'g'),
regR: new RegExp('\r', 'g'), regR: new RegExp('\r', 'g'),

View File

@ -1,32 +1,22 @@
/**
Definition of the TextGenerator class.
@module generators/text-generator
@license MIT. See LICENSE.md for details.
*/
(function() { (function() {
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; }, Definition of the TextGenerator class.
hasProp = {}.hasOwnProperty; @module generators/text-generator
@license MIT. See LICENSE.md for details.
*/
var TemplateGenerator, TextGenerator;
TemplateGenerator = require('./template-generator'); TemplateGenerator = require('./template-generator');
/** /**
The TextGenerator generates a plain-text resume via the TemplateGenerator. The TextGenerator generates a plain-text resume via the TemplateGenerator.
*/ */
module.exports = TextGenerator = class TextGenerator extends TemplateGenerator {
module.exports = TextGenerator = (function(superClass) { constructor() {
extend(TextGenerator, superClass); super('txt');
function TextGenerator() {
TextGenerator.__super__.constructor.call(this, 'txt');
} }
return TextGenerator; };
})(TemplateGenerator);
}).call(this); }).call(this);

View File

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

View File

@ -1,30 +1,20 @@
/**
Definition of the XMLGenerator class.
@license MIT. See LICENSE.md for details.
@module generatprs/xml-generator
*/
(function() { (function() {
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; }, Definition of the XMLGenerator class.
hasProp = {}.hasOwnProperty; @license MIT. See LICENSE.md for details.
@module generatprs/xml-generator
*/
var BaseGenerator, XMLGenerator;
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 = XMLGenerator = class XMLGenerator extends BaseGenerator {
module.exports = XMLGenerator = (function(superClass) { constructor() {
extend(XMLGenerator, superClass); super('xml');
function XMLGenerator() {
XMLGenerator.__super__.constructor.call(this, 'xml');
} }
return XMLGenerator; };
})(BaseGenerator);
}).call(this); }).call(this);

View File

@ -1,32 +1,22 @@
/**
Definition of the YAMLGenerator class.
@module yaml-generator.js
@license MIT. See LICENSE.md for details.
*/
(function() { (function() {
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; }, Definition of the YAMLGenerator class.
hasProp = {}.hasOwnProperty; @module yaml-generator.js
@license MIT. See LICENSE.md for details.
*/
var TemplateGenerator, YAMLGenerator;
TemplateGenerator = require('./template-generator'); TemplateGenerator = require('./template-generator');
/** /**
YamlGenerator generates a YAML-formatted resume via TemplateGenerator. YamlGenerator generates a YAML-formatted resume via TemplateGenerator.
*/ */
module.exports = YAMLGenerator = class YAMLGenerator extends TemplateGenerator {
module.exports = YAMLGenerator = (function(superClass) { constructor() {
extend(YAMLGenerator, superClass); super('yml', 'yml');
function YAMLGenerator() {
YAMLGenerator.__super__.constructor.call(this, 'yml', 'yml');
} }
return YAMLGenerator; };
})(TemplateGenerator);
}).call(this); }).call(this);

View File

@ -1,11 +1,10 @@
/**
Block helper definitions for HackMyResume / FluentCV.
@license MIT. See LICENSE.md for details.
@module helpers/generic-helpers
*/
(function() { (function() {
/**
Block helper definitions for HackMyResume / FluentCV.
@license MIT. See LICENSE.md for details.
@module helpers/generic-helpers
*/
/** Block helper function definitions. */
var BlockHelpers, HMSTATUS, LO, _, unused; var BlockHelpers, HMSTATUS, LO, _, unused;
HMSTATUS = require('../core/status-codes'); HMSTATUS = require('../core/status-codes');
@ -16,15 +15,11 @@ Block helper definitions for HackMyResume / FluentCV.
unused = require('../utils/string'); unused = require('../utils/string');
/** Block helper function definitions. */
BlockHelpers = module.exports = { BlockHelpers = module.exports = {
/** /**
Emit the enclosed content if the resume has a section with Emit the enclosed content if the resume has a section with
the specified name. Otherwise, emit an empty string ''. the specified name. Otherwise, emit an empty string ''.
*/ */
section: function(title, options) { section: function(title, options) {
var obj, ret; var obj, ret;
title = title.trim().toLowerCase(); title = title.trim().toLowerCase();
@ -43,22 +38,30 @@ Block helper definitions for HackMyResume / FluentCV.
} }
return ret; return ret;
}, },
ifHasSkill: function(rez, skill, options) {
var ret, skUp;
skUp = skill.toUpperCase();
ret = _.some(rez.skills.list, function(sk) {
return (skUp.toUpperCase() === sk.name.toUpperCase()) && sk.years;
}, this);
if (ret) {
return options.fn(this);
}
},
/** /**
Emit the enclosed content if the resume has the named Emit the enclosed content if the resume has the named
property or subproperty. property or subproperty.
*/ */
has: function(title, options) { has: function(title, options) {
title = title && title.trim().toLowerCase(); title = title && title.trim().toLowerCase();
if (LO.get(this.r, title)) { if (LO.get(this.r, title)) {
return options.fn(this); return options.fn(this);
} }
}, },
/** /**
Return true if either value is truthy. Return true if either value is truthy.
@method either @method either
*/ */
either: function(lhs, rhs, options) { either: function(lhs, rhs, options) {
if (lhs || rhs) { if (lhs || rhs) {
return options.fn(this); return options.fn(this);

View File

@ -1,11 +1,9 @@
/**
Generic template helper definitions for command-line output.
@module console-helpers.js
@license MIT. See LICENSE.md for details.
*/
(function() { (function() {
/**
Generic template helper definitions for command-line output.
@module console-helpers.js
@license MIT. See LICENSE.md for details.
*/
var CHALK, LO, PAD, _, consoleFormatHelpers; var CHALK, LO, PAD, _, consoleFormatHelpers;
PAD = require('string-padding'); PAD = require('string-padding');
@ -21,7 +19,7 @@ Generic template helper definitions for command-line output.
consoleFormatHelpers = module.exports = { consoleFormatHelpers = module.exports = {
v: function(val, defaultVal, padding, style) { v: function(val, defaultVal, padding, style) {
var retVal, spaces; var retVal, spaces;
retVal = val === null || val === void 0 ? defaultVal : val; retVal = (val === null || val === void 0) ? defaultVal : val;
spaces = 0; spaces = 0;
if (String.is(padding)) { if (String.is(padding)) {
spaces = parseInt(padding, 10); spaces = parseInt(padding, 10);

View File

@ -1,12 +1,18 @@
/**
Generic template helper definitions for HackMyResume / FluentCV.
@license MIT. See LICENSE.md for details.
@module helpers/generic-helpers
*/
(function() { (function() {
var FS, FluentDate, GenericHelpers, H2W, HMSTATUS, LO, MD, PATH, XML, _, _fromTo, _reportError, moment, printf, skillLevelToIndex, unused; /**
Generic template helper definitions for HackMyResume / FluentCV.
@license MIT. See LICENSE.md for details.
@module helpers/generic-helpers
*/
/** Generic template helper function definitions. */
/**
Format a from/to date range for display.
*/
/**
Report an error to the outside world without throwing an exception. Currently
relies on kludging the running verb into. opts.
*/
var FS, FluentDate, GenericHelpers, H2W, HMSTATUS, LO, MD, PATH, XML, _, _fromTo, _reportError, _skillLevelToIndex, moment, printf, unused;
MD = require('marked'); MD = require('marked');
@ -32,11 +38,7 @@ Generic template helper definitions for HackMyResume / FluentCV.
unused = require('../utils/string'); unused = require('../utils/string');
/** Generic template helper function definitions. */
GenericHelpers = module.exports = { GenericHelpers = module.exports = {
/** /**
Emit a formatted string representing the specified datetime. Emit a formatted string representing the specified datetime.
Convert the input date to the specified format through Moment.js. If date is Convert the input date to the specified format through Moment.js. If date is
@ -49,7 +51,7 @@ Generic template helper definitions for HackMyResume / FluentCV.
Moment.js-compatible datetime format. Moment.js-compatible datetime format.
@param {string|Moment} fallback A fallback value to use if the specified date @param {string|Moment} fallback A fallback value to use if the specified date
is null, undefined, or falsy. is null, undefined, or falsy.
*/ */
formatDate: function(datetime, dtFormat, fallback) { formatDate: function(datetime, dtFormat, fallback) {
var momentDate; var momentDate;
if (datetime == null) { if (datetime == null) {
@ -58,22 +60,32 @@ Generic template helper definitions for HackMyResume / FluentCV.
if (dtFormat == null) { if (dtFormat == null) {
dtFormat = 'YYYY-MM'; dtFormat = 'YYYY-MM';
} }
// If a Moment.js object was passed in, just call format on it
if (datetime && moment.isMoment(datetime)) { if (datetime && moment.isMoment(datetime)) {
return datetime.format(dtFormat); return datetime.format(dtFormat);
} }
if (String.is(datetime)) { 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); momentDate = moment(datetime, dtFormat);
if (momentDate.isValid()) { if (momentDate.isValid()) {
return momentDate.format(dtFormat); return momentDate.format(dtFormat);
} }
// If that didn't work, try again with the single-parameter constructor
// but this may throw a deprecation warning
momentDate = moment(datetime); momentDate = moment(datetime);
if (momentDate.isValid()) { if (momentDate.isValid()) {
return momentDate.format(dtFormat); return momentDate.format(dtFormat);
} }
} }
// 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'.
return datetime || (typeof fallback === 'string' ? fallback : (fallback === true ? 'Present' : '')); return datetime || (typeof fallback === 'string' ? fallback : (fallback === true ? 'Present' : ''));
}, },
/** /**
Emit a formatted string representing the specified datetime. Emit a formatted string representing the specified datetime.
@param {string} dateValue A raw date value from the FRESH or JRS resume. @param {string} dateValue A raw date value from the FRESH or JRS resume.
@ -81,7 +93,7 @@ Generic template helper definitions for HackMyResume / FluentCV.
compatible with Moment.js datetime formats. compatible with Moment.js datetime formats.
@param {string} [dateDefault=null] The default date value to use if the dateValue @param {string} [dateDefault=null] The default date value to use if the dateValue
parameter is null, undefined, or falsy. parameter is null, undefined, or falsy.
*/ */
date: function(dateValue, dateFormat, dateDefault) { date: function(dateValue, dateFormat, dateDefault) {
var dateValueMoment, dateValueSafe, reserved; var dateValueMoment, dateValueSafe, reserved;
if (!dateDefault || !String.is(dateDefault)) { if (!dateDefault || !String.is(dateDefault)) {
@ -107,30 +119,27 @@ Generic template helper definitions for HackMyResume / FluentCV.
} }
return dateValue; 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.
*/ */
dateRange: function(obj, fmt, sep, fallback) { dateRange: function(obj, fmt, sep, fallback) {
if (!obj) { if (!obj) {
return ''; return '';
} }
return _fromTo(obj.start, obj.end, fmt, sep, fallback); return _fromTo(obj.start, obj.end, fmt, sep, fallback);
}, },
/** /**
Format a from/to date range for display. Format a from/to date range for display.
@method toFrom @method toFrom
*/ */
fromTo: function() { fromTo: function() {
return _fromTo.apply(this, arguments); return _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
*/ */
color: function(colorName, colorDefault) { color: function(colorName, colorDefault) {
var ret; var ret;
if (!(colorName && colorName.trim())) { if (!(colorName && colorName.trim())) {
@ -150,12 +159,11 @@ Generic template helper definitions for HackMyResume / FluentCV.
return ret; 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
theme.json file. For example: 'default' or 'heading1'. theme.json file. For example: 'default' or 'heading1'.
*/ */
fontSize: function(key, defSize, units) { fontSize: function(key, defSize, units) {
var fontSpec, hasDef, ret; var fontSpec, hasDef, ret;
ret = ''; ret = '';
@ -170,18 +178,22 @@ Generic template helper definitions for HackMyResume / FluentCV.
} else if (GenericHelpers.theme.fonts) { } else if (GenericHelpers.theme.fonts) {
fontSpec = LO.get(GenericHelpers.theme.fonts, this.format + '.' + key); fontSpec = LO.get(GenericHelpers.theme.fonts, this.format + '.' + key);
if (!fontSpec) { if (!fontSpec) {
// Check for an "all" format
if (GenericHelpers.theme.fonts.all) { if (GenericHelpers.theme.fonts.all) {
fontSpec = GenericHelpers.theme.fonts.all[key]; fontSpec = GenericHelpers.theme.fonts.all[key];
} }
} }
if (fontSpec) { if (fontSpec) {
// fontSpec can be a string, an array, or an object
if (String.is(fontSpec)) { if (String.is(fontSpec)) {
// No font size was specified, only a font family.
} else if (_.isArray(fontSpec)) { } else if (_.isArray(fontSpec)) {
if (!String.is(fontSpec[0])) { if (!String.is(fontSpec[0])) {
ret = fontSpec[0].size; ret = fontSpec[0].size;
} }
} else { } else {
// A font description object.
ret = fontSpec.size; ret = fontSpec.size;
} }
} }
@ -200,7 +212,6 @@ Generic template helper definitions for HackMyResume / FluentCV.
} }
return ret; return 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.
@ -208,7 +219,7 @@ Generic template helper definitions for HackMyResume / FluentCV.
theme.json file. For example: 'default' or 'heading1'. theme.json file. For example: 'default' or 'heading1'.
@param defFont {String} The font to use if the specified key isn't present. @param defFont {String} The font to use if the specified key isn't present.
Can be any valid font-face name such as 'Helvetica Neue' or 'Calibri'. Can be any valid font-face name such as 'Helvetica Neue' or 'Calibri'.
*/ */
fontFace: function(key, defFont) { fontFace: function(key, defFont) {
var fontSpec, hasDef, ret; var fontSpec, hasDef, ret;
ret = ''; ret = '';
@ -220,19 +231,25 @@ Generic template helper definitions for HackMyResume / FluentCV.
expected: 'key' expected: 'key'
}); });
return ret; return ret;
// If the theme has a "fonts" section, lookup the font face.
} else if (GenericHelpers.theme.fonts) { } else if (GenericHelpers.theme.fonts) {
fontSpec = LO.get(GenericHelpers.theme.fonts, this.format + '.' + key); fontSpec = LO.get(GenericHelpers.theme.fonts, this.format + '.' + key);
if (!fontSpec) { if (!fontSpec) {
// Check for an "all" format
if (GenericHelpers.theme.fonts.all) { if (GenericHelpers.theme.fonts.all) {
fontSpec = GenericHelpers.theme.fonts.all[key]; fontSpec = GenericHelpers.theme.fonts.all[key];
} }
} }
if (fontSpec) { if (fontSpec) {
// fontSpec can be a string, an array, or an object
if (String.is(fontSpec)) { if (String.is(fontSpec)) {
ret = fontSpec; ret = fontSpec;
} else if (_.isArray(fontSpec)) { } else if (_.isArray(fontSpec)) {
// An array of fonts were specified. Each one could be a string
// or an object
ret = String.is(fontSpec[0]) ? fontSpec[0] : fontSpec[0].name; ret = String.is(fontSpec[0]) ? fontSpec[0] : fontSpec[0].name;
} else { } else {
// A font description object.
ret = fontSpec.name; ret = fontSpec.name;
} }
} }
@ -250,18 +267,6 @@ Generic template helper definitions for HackMyResume / FluentCV.
} }
return ret; return ret;
}, },
/**
Emit a comma-delimited list of font names suitable associated with the
provided key.
@param key {String} A named style from the "fonts" section of the theme's
theme.json file. For example: 'default' or 'heading1'.
@param defFontList {Array} The font list to use if the specified key isn't
present. Can be an array of valid font-face name such as 'Helvetica Neue'
or 'Calibri'.
@param sep {String} The default separator to use in the rendered output.
Defaults to ", " (comma with a space).
*/
fontList: function(key, defFontList, sep) { fontList: function(key, defFontList, sep) {
var fontSpec, hasDef, ret; var fontSpec, hasDef, ret;
ret = ''; ret = '';
@ -280,14 +285,18 @@ Generic template helper definitions for HackMyResume / FluentCV.
} }
} }
if (fontSpec) { if (fontSpec) {
// fontSpec can be a string, an array, or an object
if (String.is(fontSpec)) { if (String.is(fontSpec)) {
ret = fontSpec; ret = fontSpec;
} else if (_.isArray(fontSpec)) { } else if (_.isArray(fontSpec)) {
// An array of fonts were specified. Each one could be a string
// or an object
fontSpec = fontSpec.map(function(ff) { fontSpec = fontSpec.map(function(ff) {
return "'" + (String.is(ff) ? ff : ff.name) + "'"; return "'" + (String.is(ff) ? ff : ff.name) + "'";
}); });
ret = fontSpec.join(sep === void 0 ? ', ' : sep || ''); ret = fontSpec.join(sep === void 0 ? ', ' : sep || '');
} else { } else {
// A font description object.
ret = fontSpec.name; ret = fontSpec.name;
} }
} }
@ -306,11 +315,6 @@ Generic template helper definitions for HackMyResume / FluentCV.
} }
return ret; return ret;
}, },
/**
Capitalize the first letter of the word. TODO: Rename
@method section
*/
camelCase: function(val) { camelCase: function(val) {
val = (val && val.trim()) || ''; val = (val && val.trim()) || '';
if (val) { if (val) {
@ -319,33 +323,35 @@ Generic template helper definitions for HackMyResume / FluentCV.
return val; return val;
} }
}, },
/** /**
Display a user-overridable section title for a FRESH resume theme. Use this in Display a user-overridable section title for a FRESH resume theme. Use this in
lieue of hard-coding section titles. lieue of hard-coding section titles.
Usage: Usage:
{{sectionTitle "sectionName"}} {{sectionTitle "sectionName"}}
{{sectionTitle "sectionName" "sectionTitle"}} {{sectionTitle "sectionName" "sectionTitle"}}
Example: Example:
{{sectionTitle "Education"}} {{sectionTitle "Education"}}
{{sectionTitle "Employment" "Project History"}} {{sectionTitle "Employment" "Project History"}}
@param sect_name The name of the section being title. Must be one of the @param sect_name The name of the section being title. Must be one of the
top-level FRESH resume sections ("info", "education", "employment", etc.). top-level FRESH resume sections ("info", "education", "employment", etc.).
@param sect_title The theme-specified section title. May be replaced by the @param sect_title The theme-specified section title. May be replaced by the
user. user.
@method sectionTitle @method sectionTitle
*/ */
sectionTitle: function(sname, stitle) { sectionTitle: function(sname, stitle) {
// If not provided by the user, stitle should default to sname. ps.
// Handlebars silently passes in the options object to the last param,
// where in Underscore stitle will be null/undefined, so we check both.
// TODO: not actually sure that's true, given that we _.wrap these functions
stitle = (stitle && String.is(stitle) && stitle) || sname; stitle = (stitle && String.is(stitle) && stitle) || sname;
// If there's a section title override, use it.
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. */
wpml: function(txt, inline) { wpml: function(txt, inline) {
if (!txt) { if (!txt) {
return ''; return '';
@ -356,11 +362,10 @@ Generic template helper definitions for HackMyResume / FluentCV.
txt = H2W(txt); txt = H2W(txt);
return txt; return txt;
}, },
/** /**
Emit a conditional link. Emit a conditional link.
@method link @method link
*/ */
link: function(text, url) { link: function(text, url) {
if (url && url.trim()) { if (url && url.trim()) {
return '<a href="' + url + '">' + text + '</a>'; return '<a href="' + url + '">' + text + '</a>';
@ -368,11 +373,21 @@ Generic template helper definitions for HackMyResume / FluentCV.
return text; return text;
} }
}, },
/**
Emit a conditional Markdown link.
@method link
*/
linkMD: function(text, url) {
if (url && url.trim()) {
return '[' + text + '](' + url + ')';
} else {
return text;
}
},
/** /**
Return the last word of the specified text. Return the last word of the specified text.
@method lastWord @method lastWord
*/ */
lastWord: function(txt) { lastWord: function(txt) {
if (txt && txt.trim()) { if (txt && txt.trim()) {
return _.last(txt.split(' ')); return _.last(txt.split(' '));
@ -380,7 +395,6 @@ Generic template helper definitions for HackMyResume / FluentCV.
return ''; return '';
} }
}, },
/** /**
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
@ -388,28 +402,26 @@ Generic template helper definitions for HackMyResume / FluentCV.
("beginner", "intermediate", etc.), as an integer (1,5,etc), as a string ("beginner", "intermediate", etc.), as an integer (1,5,etc), as a string
integer ("1", "5", etc.), or as an RRGGBB color triplet ('#C00000', integer ("1", "5", etc.), or as an RRGGBB color triplet ('#C00000',
'#FFFFAA'). '#FFFFAA').
*/ */
skillColor: function(lvl) { skillColor: function(lvl) {
var idx, skillColors; var idx, skillColors;
idx = skillLevelToIndex(lvl); idx = _skillLevelToIndex(lvl);
skillColors = (this.theme && this.theme.palette && this.theme.palette.skillLevels) || ['#FFFFFF', '#5CB85C', '#F1C40F', '#428BCA', '#C00000']; skillColors = (this.theme && this.theme.palette && this.theme.palette.skillLevels) || ['#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
*/ */
skillHeight: function(lvl) { skillHeight: function(lvl) {
var idx; var idx;
idx = skillLevelToIndex(lvl); idx = _skillLevelToIndex(lvl);
return ['38.25', '30', '16', '8', '0'][idx]; return ['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
*/ */
initialWords: function(txt) { initialWords: function(txt) {
if (txt && txt.trim()) { if (txt && txt.trim()) {
return _.initial(txt.split(' ')).join(' '); return _.initial(txt.split(' ')).join(' ');
@ -417,11 +429,10 @@ Generic template helper definitions for HackMyResume / FluentCV.
return ''; return '';
} }
}, },
/** /**
Trim the protocol (http or https) from a URL/ Trim the protocol (http or https) from a URL/
@method trimURL @method trimURL
*/ */
trimURL: function(url) { trimURL: function(url) {
if (url && url.trim()) { if (url && url.trim()) {
return url.trim().replace(/^https?:\/\//i, ''); return url.trim().replace(/^https?:\/\//i, '');
@ -429,11 +440,10 @@ Generic template helper definitions for HackMyResume / FluentCV.
return ''; return '';
} }
}, },
/** /**
Convert text to lowercase. Convert text to lowercase.
@method toLower @method toLower
*/ */
toLower: function(txt) { toLower: function(txt) {
if (txt && txt.trim()) { if (txt && txt.trim()) {
return txt.toLowerCase(); return txt.toLowerCase();
@ -441,11 +451,10 @@ Generic template helper definitions for HackMyResume / FluentCV.
return ''; return '';
} }
}, },
/** /**
Convert text to lowercase. Convert text to lowercase.
@method toLower @method toLower
*/ */
toUpper: function(txt) { toUpper: function(txt) {
if (txt && txt.trim()) { if (txt && txt.trim()) {
return txt.toUpperCase(); return txt.toUpperCase();
@ -453,7 +462,6 @@ Generic template helper definitions for HackMyResume / FluentCV.
return ''; return '';
} }
}, },
/** /**
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
@ -462,9 +470,10 @@ Generic template helper definitions for HackMyResume / FluentCV.
@param linkage {String} The default link method. Can be either `embed` or @param linkage {String} The default link method. Can be either `embed` or
`link`. If omitted, defaults to `embed`. Can be overridden by the `--css` `link`. If omitted, defaults to `embed`. Can be overridden by the `--css`
command-line switch. command-line switch.
*/ */
styleSheet: function(url, linkage) { styleSheet: function(url, linkage) {
var rawCss, renderedCss, ret; var rawCss, renderedCss, ret;
// Establish the linkage style
linkage = this.opts.css || linkage || 'embed'; linkage = this.opts.css || linkage || 'embed';
ret = ''; ret = '';
if (linkage === 'link') { if (linkage === 'link') {
@ -474,17 +483,20 @@ Generic template helper definitions for HackMyResume / FluentCV.
renderedCss = this.engine.generateSimple(this, rawCss); renderedCss = this.engine.generateSimple(this, rawCss);
ret = printf('<style>%s</style>', renderedCss); ret = printf('<style>%s</style>', renderedCss);
} }
// If the currently-executing template is inherited, append styles
if (this.opts.themeObj.inherits && this.opts.themeObj.inherits.html && this.format === 'html') { if (this.opts.themeObj.inherits && this.opts.themeObj.inherits.html && this.format === 'html') {
ret += linkage === 'link' ? '<link href="' + this.opts.themeObj.overrides.path + '" rel="stylesheet" type="text/css">' : '<style>' + this.opts.themeObj.overrides.data + '</style>'; ret += linkage === 'link' ? '<link href="' + this.opts.themeObj.overrides.path + '" rel="stylesheet" type="text/css">' : '<style>' + this.opts.themeObj.overrides.data + '</style>';
} }
// TODO: It would be nice to use Handlebar.SafeString here, but these
// are supposed to be generic helpers. Provide an equivalent, or expose
// it when Handlebars is the chosen engine, which is most of the time.
return ret; return 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
@method compare @method compare
*/ */
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) {
@ -527,6 +539,9 @@ Generic template helper definitions for HackMyResume / FluentCV.
return options.inverse(this); return options.inverse(this);
} }
}, },
/**
Emit padded text.
*/
pad: function(stringOrArray, padAmount, unused) { pad: function(stringOrArray, padAmount, unused) {
var PAD, ret; var PAD, ret;
stringOrArray = stringOrArray || ''; stringOrArray = stringOrArray || '';
@ -541,26 +556,43 @@ Generic template helper definitions for HackMyResume / FluentCV.
ret = PAD(stringOrArray, stringOrArray.length + Math.abs(padAmount), null, padAmount < 0 ? PAD.LEFT : PAD.RIGHT); ret = PAD(stringOrArray, stringOrArray.length + Math.abs(padAmount), null, padAmount < 0 ? PAD.LEFT : PAD.RIGHT);
} }
return ret; return ret;
},
/**
Given the name of a skill ("JavaScript" or "HVAC repair"), return the number
of years assigned to that skill in the resume.skills.list collection.
*/
skillYears: function(skill, rez) {
var sk;
sk = _.find(rez.skills.list, function(sk) {
return sk.name.toUpperCase() === skill.toUpperCase();
});
if (sk) {
return sk.years;
} else {
return '?';
}
},
/**
Given an object that may be a string or an object, return it as-is if it's a
string, otherwise return the value at obj[objPath].
*/
stringOrObject: function(obj, objPath, rez) {
if (_.isString(obj)) {
return obj;
} else {
return LO.get(obj, objPath);
}
} }
}; };
/**
Report an error to the outside world without throwing an exception. Currently
relies on kludging the running verb into. opts.
*/
_reportError = function(code, params) { _reportError = function(code, params) {
return GenericHelpers.opts.errHandler.err(code, params); return GenericHelpers.opts.errHandler.err(code, params);
}; };
/**
Format a from/to date range for display.
*/
_fromTo = function(dateA, dateB, fmt, sep, fallback) { _fromTo = function(dateA, dateB, fmt, sep, fallback) {
var dateATrim, dateBTrim, dateFrom, dateTemp, dateTo, reserved; var dateATrim, dateBTrim, dateFrom, dateTemp, dateTo, reserved;
// Prevent accidental use of safe.start, safe.end, safe.date
// The dateRange helper is for raw dates only
if (moment.isMoment(dateA) || moment.isMoment(dateB)) { if (moment.isMoment(dateA) || moment.isMoment(dateB)) {
_reportError(HMSTATUS.invalidHelperUse, { _reportError(HMSTATUS.invalidHelperUse, {
helper: 'dateRange' helper: 'dateRange'
@ -570,6 +602,7 @@ Generic template helper definitions for HackMyResume / FluentCV.
dateFrom = null; dateFrom = null;
dateTo = null; dateTo = null;
dateTemp = null; dateTemp = null;
// Check for 'current', 'present', 'now', '', null, and undefined
dateA = dateA || ''; dateA = dateA || '';
dateB = dateB || ''; dateB = dateB || '';
dateATrim = dateA.trim().toLowerCase(); dateATrim = dateA.trim().toLowerCase();
@ -599,7 +632,7 @@ Generic template helper definitions for HackMyResume / FluentCV.
return ''; return '';
}; };
skillLevelToIndex = function(lvl) { _skillLevelToIndex = function(lvl) {
var idx, intVal; var idx, intVal;
idx = 0; idx = 0;
if (String.is(lvl)) { if (String.is(lvl)) {
@ -630,6 +663,25 @@ Generic template helper definitions for HackMyResume / FluentCV.
return idx; return idx;
}; };
// Note [1] --------------------------------------------------------------------
// 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
// defFont parameter to be omitted in certain cases. This is a little kludgy,
// but works fine for this case. If we start doing this regularly, we should
// rebind these parameters.
// Note [2]: -------------------------------------------------------------------
// If execution reaches here, some sort of cosmic ray or sunspot has landed on
// HackMyResume, or a theme author is deliberately messing with us by doing
// something like:
// "fonts": {
// "default": "",
// "heading1": null
// }
// Rather than sort it out, we'll just fall back to defFont.
}).call(this); }).call(this);
//# sourceMappingURL=generic-helpers.js.map //# sourceMappingURL=generic-helpers.js.map

View File

@ -1,11 +1,9 @@
/**
Template helper definitions for Handlebars.
@license MIT. See LICENSE.md for details.
@module handlebars-helpers.js
*/
(function() { (function() {
/**
Template helper definitions for Handlebars.
@license MIT. See LICENSE.md for details.
@module handlebars-helpers.js
*/
var HANDLEBARS, HMS, _, blockHelpers, helpers, path; var HANDLEBARS, HMS, _, blockHelpers, helpers, path;
HANDLEBARS = require('handlebars'); HANDLEBARS = require('handlebars');
@ -20,32 +18,38 @@ Template helper definitions for Handlebars.
HMS = require('../core/status-codes'); HMS = require('../core/status-codes');
/** /**
Register useful Handlebars helpers. Register useful Handlebars helpers.
@method registerHelpers @method registerHelpers
*/ */
module.exports = function(theme, rez, opts) {
module.exports = function(theme, opts) {
var curGlob, ex, glob, slash, wrappedHelpers; var curGlob, ex, glob, slash, wrappedHelpers;
helpers.theme = theme; helpers.theme = theme;
helpers.opts = opts; helpers.opts = opts;
helpers.type = 'handlebars'; helpers.type = 'handlebars';
// Prepare generic helpers for use with Handlebars. We do this by wrapping them
// in a Handlebars-aware wrapper which calls the helper internally.
wrappedHelpers = _.mapObject(helpers, function(hVal, hKey) { wrappedHelpers = _.mapObject(helpers, function(hVal, hKey) {
if (_.isFunction(hVal)) { if (_.isFunction(hVal)) {
_.wrap(hVal, function(func) { return _.wrap(hVal, function(func) {
var args; var args;
args = Array.prototype.slice.call(arguments); args = Array.prototype.slice.call(arguments);
args.shift(); args.shift(); // lose the 1st element (func) [^1]
args.pop(); //args.pop() # lose the last element (HB options hash)
return func.apply(this, args); args[args.length - 1] = rez; // replace w/ resume object
return func.apply(this, args); // call the generic helper
}); });
} }
return hVal; return hVal;
}, this); }, this);
HANDLEBARS.registerHelper(wrappedHelpers); HANDLEBARS.registerHelper(wrappedHelpers);
// Prepare Handlebars-specific helpers - "blockHelpers" is really a misnomer
// since any kind of Handlebars-specific helper can live here
HANDLEBARS.registerHelper(blockHelpers); HANDLEBARS.registerHelper(blockHelpers);
if (_.isString(theme.helpers)) { if (_.isString(theme.helpers)) {
// Register any theme-provided custom helpers...
// Normalize "theme.helpers" (string or array) to an array
theme.helpers = [theme.helpers]; theme.helpers = [theme.helpers];
} }
if (_.isArray(theme.helpers)) { if (_.isArray(theme.helpers)) {
@ -53,14 +57,14 @@ Template helper definitions for Handlebars.
slash = require('slash'); slash = require('slash');
curGlob = null; curGlob = null;
try { try {
_.each(theme.helpers, function(fGlob) { _.each(theme.helpers, function(fGlob) { // foreach theme.helpers entry
var files; var files;
curGlob = fGlob; curGlob = fGlob; // ..cache in case of exception
fGlob = path.join(theme.folder, fGlob); fGlob = path.join(theme.folder, fGlob); // ..make relative to theme
files = glob.sync(slash(fGlob)); files = glob.sync(slash(fGlob)); // ..expand the glob
if (files.length > 0) { if (files.length > 0) { // ..guard against empty glob
_.each(files, function(f) { _.each(files, function(f) { // ..loop over concrete paths
HANDLEBARS.registerHelper(require(f)); HANDLEBARS.registerHelper(require(f)); // ..register the path
}); });
} else { } else {
throw { throw {
@ -70,8 +74,8 @@ Template helper definitions for Handlebars.
}; };
} }
}); });
} catch (_error) { } catch (error) {
ex = _error; ex = error;
throw { throw {
fluenterror: HMS.themeHelperLoad, fluenterror: HMS.themeHelperLoad,
inner: ex, inner: ex,
@ -82,6 +86,12 @@ Template helper definitions for Handlebars.
} }
}; };
// [^1]: This little bit of acrobatics ensures that our generic helpers are
// called as generic helpers, not as Handlebars-specific helpers. This allows
// them to be used in other templating engines, like Underscore. If you need a
// Handlebars-specific helper with normal Handlebars context and options, put it
// in block-helpers.coffee.
}).call(this); }).call(this);
//# sourceMappingURL=handlebars-helpers.js.map //# sourceMappingURL=handlebars-helpers.js.map

View File

@ -1,11 +1,9 @@
/**
Template helper definitions for Underscore.
@license MIT. See LICENSE.md for details.
@module handlebars-helpers.js
*/
(function() { (function() {
/**
Template helper definitions for Underscore.
@license MIT. See LICENSE.md for details.
@module handlebars-helpers.js
*/
var HANDLEBARS, _, helpers; var HANDLEBARS, _, helpers;
HANDLEBARS = require('handlebars'); HANDLEBARS = require('handlebars');
@ -14,12 +12,10 @@ Template helper definitions for Underscore.
helpers = require('./generic-helpers'); helpers = require('./generic-helpers');
/** /**
Register useful Underscore helpers. Register useful Underscore helpers.
@method registerHelpers @method registerHelpers
*/ */
module.exports = function(theme, opts, cssInfo, ctx, eng) { module.exports = function(theme, opts, cssInfo, ctx, eng) {
helpers.theme = theme; helpers.theme = theme;
helpers.opts = opts; helpers.opts = opts;

18
dist/index.js vendored
View File

@ -1,21 +1,17 @@
/**
External API surface for HackMyResume.
@license MIT. See LICENSE.md for details.
@module hackmycore/index
*/
/** API facade for HackMyResume. */
(function() { (function() {
/**
External API surface for HackMyResume.
@license MIT. See LICENSE.md for details.
@module hackmycore/index
*/
/** API facade for HackMyResume. */
module.exports = { module.exports = {
verbs: { verbs: {
build: require('./verbs/build'), build: require('./verbs/build'),
analyze: require('./verbs/analyze'), analyze: require('./verbs/analyze'),
validate: require('./verbs/validate'), validate: require('./verbs/validate'),
convert: require('./verbs/convert'), convert: require('./verbs/convert'),
"new": require('./verbs/create'), new: require('./verbs/create'),
peek: require('./verbs/peek') peek: require('./verbs/peek')
}, },
alias: { alias: {

65
dist/inspectors/duration-inspector.js vendored Normal file
View File

@ -0,0 +1,65 @@
(function() {
var FluentDate, _, lo;
FluentDate = require('../core/fluent-date');
_ = require('underscore');
lo = require('lodash');
module.exports = {
/**
Compute the total duration of the work history.
@returns The total duration of the sheet's work history, that is, the number
of years between the start date of the earliest job on the resume and the
*latest end date of all jobs in the work history*. This last condition is for
sheets that have overlapping jobs.
*/
run: function(rez, collKey, startKey, endKey, unit) {
var firstDate, hist, lastDate, new_e;
unit = unit || 'years';
hist = lo.get(rez, collKey);
if (!hist || !hist.length) {
return 0;
}
// BEGIN CODE DUPLICATION --> src/inspectors/gap-inspector.coffee (TODO)
// Convert the candidate's employment history to an array of dates,
// where each element in the array is a start date or an end date of a
// job -- it doesn't matter which.
new_e = hist.map(function(job) {
var obj;
obj = _.pick(job, [startKey, endKey]);
if (!_.has(obj, endKey)) {
// Synthesize an end date if this is a "current" gig
obj[endKey] = 'current';
}
if (obj && (obj[startKey] || obj[endKey])) {
obj = _.pairs(obj);
obj[0][1] = FluentDate.fmt(obj[0][1]);
if (obj.length > 1) {
obj[1][1] = FluentDate.fmt(obj[1][1]);
}
}
return obj;
});
// Flatten the array, remove empties, and sort
new_e = _.filter(_.flatten(new_e, true), function(v) {
return v && v.length && v[0] && v[0].length;
});
if (!new_e || !new_e.length) {
return 0;
}
new_e = _.sortBy(new_e, function(elem) {
return elem[1].unix();
});
// END CODE DUPLICATION
firstDate = _.first(new_e)[1];
lastDate = _.last(new_e)[1];
return lastDate.diff(firstDate, unit);
}
};
}).call(this);
//# sourceMappingURL=duration-inspector.js.map

View File

@ -1,11 +1,12 @@
/**
Employment gap analysis for HackMyResume.
@license MIT. See LICENSE.md for details.
@module inspectors/gap-inspector
*/
(function() { (function() {
/**
Employment gap analysis for HackMyResume.
@license MIT. See LICENSE.md for details.
@module inspectors/gap-inspector
*/
/**
Identify gaps in the candidate's employment history.
*/
var FluentDate, LO, _, gapInspector, moment; var FluentDate, LO, _, gapInspector, moment;
_ = require('underscore'); _ = require('underscore');
@ -16,14 +17,8 @@ Employment gap analysis for HackMyResume.
LO = require('lodash'); LO = require('lodash');
/**
Identify gaps in the candidate's employment history.
*/
gapInspector = module.exports = { gapInspector = module.exports = {
moniker: 'gap-inspector', moniker: 'gap-inspector',
/** /**
Run the Gap Analyzer on a resume. Run the Gap Analyzer on a resume.
@method run @method run
@ -35,9 +30,10 @@ Employment gap analysis for HackMyResume.
end: // A Moment.js date end: // A Moment.js date
duration: // Gap length duration: // Gap length
} }
*/ */
run: function(rez) { run: function(rez) {
var coverage, dur, g, gap_start, hist, new_e, num_gaps, o, ref_count, tdur, total_gap_days; var coverage, dur, g, gap_start, hist, new_e, num_gaps, o, ref_count, tdur, total_gap_days;
// This is what we'll return
coverage = { coverage = {
gaps: [], gaps: [],
overlaps: [], overlaps: [],
@ -48,10 +44,14 @@ Employment gap analysis for HackMyResume.
gaps: 0 gaps: 0
} }
}; };
// Missing employment section? Bye bye.
hist = LO.get(rez, 'employment.history'); hist = LO.get(rez, 'employment.history');
if (!hist || !hist.length) { if (!hist || !hist.length) {
return coverage; return coverage;
} }
// Convert the candidate's employment history to an array of dates,
// where each element in the array is a start date or an end date of a
// job -- it doesn't matter which.
new_e = hist.map(function(job) { new_e = hist.map(function(job) {
var obj; var obj;
obj = _.pick(job, ['start', 'end']); obj = _.pick(job, ['start', 'end']);
@ -64,6 +64,7 @@ Employment gap analysis for HackMyResume.
} }
return obj; return obj;
}); });
// Flatten the array, remove empties, and sort
new_e = _.filter(_.flatten(new_e, true), function(v) { new_e = _.filter(_.flatten(new_e, true), function(v) {
return v && v.length && v[0] && v[0].length; return v && v.length && v[0] && v[0].length;
}); });
@ -73,6 +74,11 @@ Employment gap analysis for HackMyResume.
new_e = _.sortBy(new_e, function(elem) { new_e = _.sortBy(new_e, function(elem) {
return elem[1].unix(); return elem[1].unix();
}); });
// Iterate over elements in the array. Each time a start date is found,
// increment a reference count. Each time an end date is found, decrement
// the reference count. When the reference count reaches 0, we have a gap.
// When the reference count is > 0, the candidate is employed. When the
// reference count reaches 2, the candidate is overlapped.
num_gaps = 0; num_gaps = 0;
ref_count = 0; ref_count = 0;
total_gap_days = 0; total_gap_days = 0;
@ -81,11 +87,13 @@ Employment gap analysis for HackMyResume.
var inc, lastGap, lastOver; var inc, lastGap, lastOver;
inc = point[0] === 'start' ? 1 : -1; inc = point[0] === 'start' ? 1 : -1;
ref_count += inc; ref_count += inc;
// If the ref count just reached 0, start a new GAP
if (ref_count === 0) { if (ref_count === 0) {
return coverage.gaps.push({ return coverage.gaps.push({
start: point[1], start: point[1],
end: null end: null
}); });
// If the ref count reached 1 by rising, end the last GAP
} else if (ref_count === 1 && inc === 1) { } else if (ref_count === 1 && inc === 1) {
lastGap = _.last(coverage.gaps); lastGap = _.last(coverage.gaps);
if (lastGap) { if (lastGap) {
@ -93,11 +101,13 @@ Employment gap analysis for HackMyResume.
lastGap.duration = lastGap.end.diff(lastGap.start, 'days'); lastGap.duration = lastGap.end.diff(lastGap.start, 'days');
return total_gap_days += lastGap.duration; return total_gap_days += lastGap.duration;
} }
// If the ref count reaches 2 by rising, start a new OVERLAP
} else if (ref_count === 2 && inc === 1) { } else if (ref_count === 2 && inc === 1) {
return coverage.overlaps.push({ return coverage.overlaps.push({
start: point[1], start: point[1],
end: null end: null
}); });
// If the ref count reaches 1 by falling, end the last OVERLAP
} else if (ref_count === 1 && inc === -1) { } else if (ref_count === 1 && inc === -1) {
lastOver = _.last(coverage.overlaps); lastOver = _.last(coverage.overlaps);
if (lastOver) { if (lastOver) {
@ -109,6 +119,9 @@ Employment gap analysis for HackMyResume.
} }
} }
}); });
// It's possible that the last gap/overlap didn't have an explicit .end
// date.If so, set the end date to the present date and compute the
// duration normally.
if (coverage.overlaps.length) { if (coverage.overlaps.length) {
o = _.last(coverage.overlaps); o = _.last(coverage.overlaps);
if (o && !o.end) { if (o && !o.end) {
@ -123,6 +136,7 @@ Employment gap analysis for HackMyResume.
g.duration = g.end.diff(g.start, 'days'); g.duration = g.end.diff(g.start, 'days');
} }
} }
// Package data for return to the client
tdur = rez.duration('days'); tdur = rez.duration('days');
dur = { dur = {
total: tdur, total: tdur,

View File

@ -1,48 +1,63 @@
/**
Keyword analysis for HackMyResume.
@license MIT. See LICENSE.md for details.
@module inspectors/keyword-inspector
*/
(function() { (function() {
/**
Keyword analysis for HackMyResume.
@license MIT. See LICENSE.md for details.
@module inspectors/keyword-inspector
*/
/**
Analyze the resume's use of keywords.
TODO: BUG: Keyword search regex is inaccurate, especially for one or two
letter keywords like "C" or "CLI".
@class keywordInspector
*/
var FluentDate, _, keywordInspector; var FluentDate, _, keywordInspector;
_ = require('underscore'); _ = require('underscore');
FluentDate = require('../core/fluent-date'); FluentDate = require('../core/fluent-date');
/**
Analyze the resume's use of keywords.
TODO: BUG: Keyword search regex is inaccurate, especially for one or two
letter keywords like "C" or "CLI".
@class keywordInspector
*/
keywordInspector = module.exports = { keywordInspector = module.exports = {
/** A unique name for this inspector. */ /** A unique name for this inspector. */
moniker: 'keyword-inspector', moniker: 'keyword-inspector',
/** /**
Run the Keyword Inspector on a resume. Run the Keyword Inspector on a resume.
@method run @method run
@return An collection of statistical keyword data. @return An collection of statistical keyword data.
*/ */
run: function(rez) { run: function(rez) {
var prefix, regex_quote, searchable, suffix; var prefix, regex_quote, searchable, suffix;
// "Quote" or safely escape a keyword so it can be used as a regex. For
// example, if the keyword is "C++", yield "C\+\+".
// http://stackoverflow.com/a/2593661/4942583
regex_quote = function(str) { regex_quote = function(str) {
return (str + '').replace(/[.?*+^$[\]\\(){}|-]/ig, "\\$&"); return (str + '').replace(/[.?*+^$[\]\\(){}|-]/ig, "\\$&");
}; };
// Create a searchable plain-text digest of the resume
// TODO: BUG: Don't search within keywords for other keywords. Job A
// declares the "foo" keyword. Job B declares the "foo & bar" keyword. Job
// B's mention of "foobar" should not count as a mention of "foo".
// To achieve this, remove keywords from the search digest and treat them
// separately.
searchable = ''; searchable = '';
rez.transformStrings(['imp', 'computed', 'safe'], function(key, val) { rez.transformStrings(['imp', 'computed', 'safe'], function(key, val) {
return searchable += ' ' + val; return searchable += ' ' + val;
}); });
// Assemble a regex skeleton we can use to test for keywords with a bit
// more
prefix = '(?:' + ['^', '\\s+', '[\\.,]+'].join('|') + ')'; prefix = '(?:' + ['^', '\\s+', '[\\.,]+'].join('|') + ')';
suffix = '(?:' + ['$', '\\s+', '[\\.,]+'].join('|') + ')'; suffix = '(?:' + ['$', '\\s+', '[\\.,]+'].join('|') + ')';
return rez.keywords().map(function(kw) { return rez.keywords().map(function(kw) {
var count, myArray, regex, regex_str; var count, myArray, regex, regex_str;
// 1. Using word boundary or other regex class is inaccurate
// var regex = new RegExp( '\\b' + regex_quote( kw )/* + '\\b'*/, 'ig');
// 2. Searching for the raw keyword is inaccurate ("C" will match any
// word containing a 'c'!).
// var regex = new RegExp( regex_quote( kw ), 'ig');
// 3. Instead, use a custom regex with special delimeters.
regex_str = prefix + regex_quote(kw) + suffix; regex_str = prefix + regex_quote(kw) + suffix;
regex = new RegExp(regex_str, 'ig'); regex = new RegExp(regex_str, 'ig');
myArray = null; myArray = null;

View File

@ -1,32 +1,27 @@
/**
Section analysis for HackMyResume.
@license MIT. See LICENSE.md for details.
@module inspectors/totals-inspector
*/
(function() { (function() {
/**
Section analysis for HackMyResume.
@license MIT. See LICENSE.md for details.
@module inspectors/totals-inspector
*/
/**
Retrieve sectional overview and summary information.
@class totalsInspector
*/
var FluentDate, _, totalsInspector; var FluentDate, _, totalsInspector;
_ = require('underscore'); _ = require('underscore');
FluentDate = require('../core/fluent-date'); FluentDate = require('../core/fluent-date');
/**
Retrieve sectional overview and summary information.
@class totalsInspector
*/
totalsInspector = module.exports = { totalsInspector = module.exports = {
moniker: 'totals-inspector', moniker: 'totals-inspector',
/** /**
Run the Totals Inspector on a resume. Run the Totals Inspector on a resume.
@method run @method run
@return An object containing summary information for each section on the @return An object containing summary information for each section on the
resume. resume.
*/ */
run: function(rez) { run: function(rez) {
var sectionTotals; var sectionTotals;
sectionTotals = {}; sectionTotals = {};

View File

@ -1,11 +1,13 @@
/**
Definition of the HandlebarsGenerator class.
@license MIT. See LICENSE.md for details.
@module renderers/handlebars-generator
*/
(function() { (function() {
/**
Definition of the HandlebarsGenerator class.
@license MIT. See LICENSE.md for details.
@module renderers/handlebars-generator
*/
/**
Perform template-based resume generation using Handlebars.js.
@class HandlebarsGenerator
*/
var FS, HANDLEBARS, HMSTATUS, HandlebarsGenerator, PATH, READFILES, SLASH, _, parsePath, registerHelpers, registerPartials; var FS, HANDLEBARS, HMSTATUS, HandlebarsGenerator, PATH, READFILES, SLASH, _, parsePath, registerHelpers, registerPartials;
_ = require('underscore'); _ = require('underscore');
@ -26,33 +28,28 @@ Definition of the HandlebarsGenerator class.
SLASH = require('slash'); SLASH = require('slash');
/**
Perform template-based resume generation using Handlebars.js.
@class HandlebarsGenerator
*/
HandlebarsGenerator = module.exports = { HandlebarsGenerator = module.exports = {
generateSimple: function(data, tpl) { generateSimple: function(data, tpl) {
var template; var err, template;
try { try {
// Compile and run the Handlebars template.
template = HANDLEBARS.compile(tpl, { template = HANDLEBARS.compile(tpl, {
strict: false, strict: false,
assumeObjects: false, assumeObjects: false,
noEscape: data.opts.noescape || false noEscape: data.opts.noescape
}); });
return template(data); return template(data);
} catch (_error) { } catch (error1) {
err = error1;
throw { throw {
fluenterror: HMSTATUS[template ? 'invokeTemplate' : 'compileTemplate'], fluenterror: HMSTATUS[template ? 'invokeTemplate' : 'compileTemplate'],
inner: _error inner: err
}; };
} }
}, },
generate: function(json, jst, format, curFmt, opts, theme) { generate: function(json, jst, format, curFmt, opts, theme) {
var ctx, encData; var ctx, encData;
registerPartials(format, theme); // Preprocess text
registerHelpers(theme, opts);
encData = json; encData = json;
if (format === 'html' || format === 'pdf') { if (format === 'html' || format === 'pdf') {
encData = json.markdownify(); encData = json.markdownify();
@ -60,6 +57,10 @@ Definition of the HandlebarsGenerator class.
if (format === 'doc') { if (format === 'doc') {
encData = json.xmlify(); encData = json.xmlify();
} }
// Set up partials and helpers
registerPartials(format, theme);
registerHelpers(theme, encData, opts);
// Set up the context
ctx = { ctx = {
r: encData, r: encData,
RAW: json, RAW: json,
@ -70,6 +71,7 @@ Definition of the HandlebarsGenerator class.
results: curFmt.files, results: curFmt.files,
headFragment: opts.headFragment || '' headFragment: opts.headFragment || ''
}; };
// Render the template
return this.generateSimple(ctx, jst); return this.generateSimple(ctx, jst);
} }
}; };
@ -77,7 +79,10 @@ Definition of the HandlebarsGenerator class.
registerPartials = function(format, theme) { registerPartials = function(format, theme) {
var partialsFolder; var partialsFolder;
if (_.contains(['html', 'doc', 'md', 'txt', 'pdf'], format)) { if (_.contains(['html', 'doc', 'md', 'txt', 'pdf'], format)) {
// Locate the global partials folder
partialsFolder = PATH.join(parsePath(require.resolve('fresh-themes')).dirname, '/partials/', format === 'pdf' ? 'html' : format); partialsFolder = PATH.join(parsePath(require.resolve('fresh-themes')).dirname, '/partials/', format === 'pdf' ? 'html' : format);
// Register global partials in the /partials/[format] folder
// TODO: Only do this once per HMR invocation.
_.each(READFILES(partialsFolder, function(error) { _.each(READFILES(partialsFolder, function(error) {
return {}; return {};
}), function(el) { }), function(el) {
@ -90,6 +95,7 @@ Definition of the HandlebarsGenerator class.
return theme.partialsInitialized = true; return theme.partialsInitialized = true;
}); });
} }
// Register theme-specific partials
return _.each(theme.partials, function(el) { return _.each(theme.partials, function(el) {
var compiledTemplate, tplData; var compiledTemplate, tplData;
tplData = FS.readFileSync(el.path, 'utf8'); tplData = FS.readFileSync(el.path, 'utf8');

View File

@ -1,11 +1,13 @@
/**
Definition of the JRSGenerator class.
@license MIT. See LICENSE.md for details.
@module renderers/jrs-generator
*/
(function() { (function() {
/**
Definition of the JRSGenerator class.
@license MIT. See LICENSE.md for details.
@module renderers/jrs-generator
*/
/**
Perform template-based resume generation for JSON Resume themes.
@class JRSGenerator
*/
var FS, HANDLEBARS, JRSGenerator, MD, MDIN, PATH, READFILES, SLASH, _, parsePath, registerHelpers; var FS, HANDLEBARS, JRSGenerator, MD, MDIN, PATH, READFILES, SLASH, _, parsePath, registerHelpers;
_ = require('underscore'); _ = require('underscore');
@ -26,15 +28,10 @@ Definition of the JRSGenerator class.
MD = require('marked'); MD = require('marked');
/**
Perform template-based resume generation for JSON Resume themes.
@class JRSGenerator
*/
JRSGenerator = module.exports = { JRSGenerator = module.exports = {
generate: function(json, jst, format, cssInfo, opts, theme) { generate: function(json, jst, format, cssInfo, opts, theme) {
var org, rezHtml, turnoff; var org, rezHtml, turnoff;
// Disable JRS theme chatter (console.log, console.error, etc.)
turnoff = ['log', 'error', 'dir']; turnoff = ['log', 'error', 'dir'];
org = turnoff.map(function(c) { org = turnoff.map(function(c) {
var ret; var ret;
@ -42,17 +39,20 @@ Definition of the JRSGenerator class.
console[c] = function() {}; console[c] = function() {};
return ret; return ret;
}); });
// Freeze and render
rezHtml = theme.render(json.harden()); rezHtml = theme.render(json.harden());
// Turn logging back on
turnoff.forEach(function(c, idx) { turnoff.forEach(function(c, idx) {
return console[c] = org[idx]; return console[c] = org[idx];
}); });
return rezHtml = rezHtml.replace(/@@@@~.*?~@@@@/gm, function(val) { // Unfreeze and apply Markdown
return MDIN(val.replace(/~@@@@/gm, '').replace(/@@@@~/gm, '')); return rezHtml = rezHtml.replace(/@@@@~[\s\S]*?~@@@@/g, function(val) {
return MDIN(val.replace(/~@@@@/g, '').replace(/@@@@~/g, ''));
}); });
} }
}; };
MDIN = function(txt) { MDIN = function(txt) { // TODO: Move this
return MD(txt || '').replace(/^\s*<p>|<\/p>\s*$/gi, ''); return MD(txt || '').replace(/^\s*<p>|<\/p>\s*$/gi, '');
}; };

View File

@ -1,11 +1,13 @@
/**
Definition of the UnderscoreGenerator class.
@license MIT. See LICENSE.md for details.
@module underscore-generator.js
*/
(function() { (function() {
/**
Definition of the UnderscoreGenerator class.
@license MIT. See LICENSE.md for details.
@module underscore-generator.js
*/
/**
Perform template-based resume generation using Underscore.js.
@class UnderscoreGenerator
*/
var UnderscoreGenerator, _, escapeLaTeX, registerHelpers; var UnderscoreGenerator, _, escapeLaTeX, registerHelpers;
_ = require('underscore'); _ = require('underscore');
@ -16,28 +18,26 @@ Definition of the UnderscoreGenerator class.
escapeLaTeX = require('escape-latex'); escapeLaTeX = require('escape-latex');
/**
Perform template-based resume generation using Underscore.js.
@class UnderscoreGenerator
*/
UnderscoreGenerator = module.exports = { UnderscoreGenerator = module.exports = {
generateSimple: function(data, tpl) { generateSimple: function(data, tpl) {
var HMS, t; var HMS, err, t;
try { try {
// Compile and run the Handlebars template.
t = _.template(tpl); t = _.template(tpl);
return t(data); return t(data);
} catch (_error) { } catch (error) {
err = error;
//console.dir _error
HMS = require('../core/status-codes'); HMS = require('../core/status-codes');
throw { throw {
fluenterror: HMS[t ? 'invokeTemplate' : 'compileTemplate'], fluenterror: HMS[t ? 'invokeTemplate' : 'compileTemplate'],
inner: _error inner: err
}; };
} }
}, },
generate: function(json, jst, format, cssInfo, opts, theme) { generate: function(json, jst, format, cssInfo, opts, theme) {
var ctx, delims, r, traverse; var ctx, delims, r, traverse;
// 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, function(val, key) { delims = _.mapObject(delims, function(val, key) {
@ -68,16 +68,20 @@ Definition of the UnderscoreGenerator class.
default: default:
r = json; r = json;
} }
// Set up the context
ctx = { ctx = {
r: r, 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: @
headFragment: opts.headFragment || '', headFragment: opts.headFragment || '',
opts: opts opts: opts
}; };
// Link to our helpers
registerHelpers(theme, opts, cssInfo, ctx, this); registerHelpers(theme, opts, cssInfo, ctx, this);
// Generate!
return this.generateSimple(ctx, jst); return this.generateSimple(ctx, jst);
} }
}; };

View File

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

27
dist/utils/fresh-version-regex.js vendored Normal file
View File

@ -0,0 +1,27 @@
(function() {
/**
Defines a regex suitable for matching FRESH versions.
@module file-contains.js
*/
// Set up a regex that matches all of the following:
// - FRESH
// - JRS
// - FRESCA
// - FRESH@1.0.0
// - FRESH@1.0
// - FRESH@1
// - JRS@0.16.0
// - JRS@0.16
// - JRS@0
// Don't use a SEMVER regex (eg, NPM's semver-regex) because a) we want to
// support partial semvers like "0" or "1.2" and b) we'll expand this later to
// support fully scoped FRESH versions.
module.exports = function() {
return RegExp('^(FRESH|FRESCA|JRS)(?:@(\\d+(?:\\.\\d+)?(?:\\.\\d+)?))?$');
};
}).call(this);
//# sourceMappingURL=fresh-version-regex.js.map

View File

@ -1,12 +1,12 @@
/**
Definition of the Markdown to WordProcessingML conversion routine.
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
@module utils/html-to-wpml
*/
(function() { (function() {
var HTML5Tokenizer, _; /**
Definition of the Markdown to WordProcessingML conversion routine.
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
@module utils/html-to-wpml
*/
var HTML5Tokenizer, XML, _;
XML = require('xml-escape');
_ = require('underscore'); _ = require('underscore');
@ -14,8 +14,12 @@ Definition of the Markdown to WordProcessingML conversion routine.
module.exports = function(html) { module.exports = function(html) {
var final, is_bold, is_italic, is_link, link_url, tokens; var final, is_bold, is_italic, is_link, link_url, tokens;
// Tokenize the HTML stream.
tokens = HTML5Tokenizer.tokenize(html); tokens = HTML5Tokenizer.tokenize(html);
final = is_bold = is_italic = is_link = link_url = ''; final = is_bold = is_italic = is_link = link_url = '';
// Process <em>, <strong>, and <a> elements in the HTML stream, producing
// equivalent WordProcessingML that can be dumped into a <w:p> or other
// text container element.
_.each(tokens, function(tok) { _.each(tokens, function(tok) {
var style; var style;
switch (tok.type) { switch (tok.type) {
@ -51,7 +55,7 @@ Definition of the Markdown to WordProcessingML conversion routine.
style = is_bold ? '<w:b/>' : ''; style = is_bold ? '<w:b/>' : '';
style += is_italic ? '<w:i/>' : ''; style += is_italic ? '<w:i/>' : '';
style += is_link ? '<w:rStyle w:val="Hyperlink"/>' : ''; style += is_link ? '<w:rStyle w:val="Hyperlink"/>' : '';
return final += (is_link ? '<w:hlink w:dest="' + link_url + '">' : '') + '<w:r><w:rPr>' + style + '</w:rPr><w:t>' + tok.chars + '</w:t></w:r>' + (is_link ? '</w:hlink>' : ''); return final += (is_link ? '<w:hlink w:dest="' + link_url + '">' : '') + '<w:r><w:rPr>' + style + '</w:rPr><w:t>' + XML(tok.chars) + '</w:t></w:r>' + (is_link ? '</w:hlink>' : '');
} }
} }
}); });

View File

@ -1,11 +1,9 @@
/**
Inline Markdown-to-Chalk conversion routines.
@license MIT. See LICENSE.md for details.
@module utils/md2chalk
*/
(function() { (function() {
/**
Inline Markdown-to-Chalk conversion routines.
@license MIT. See LICENSE.md for details.
@module utils/md2chalk
*/
var CHALK, LO, MD; var CHALK, LO, MD;
MD = require('marked'); MD = require('marked');

View File

@ -1,4 +1,7 @@
(function() { (function() {
// Exemplar script for generating documents with Phantom.js.
// https://raw.githubusercontent.com/ariya/phantomjs/master/examples/rasterize.js
// Converted to CoffeeScript by hacksalot
"use strict"; "use strict";
var address, output, page, pageHeight, pageWidth, size, system; var address, output, page, pageHeight, pageWidth, size, system;
@ -50,7 +53,7 @@
} else { } else {
console.log("size:", system.args[3]); console.log("size:", system.args[3]);
pageWidth = parseInt(system.args[3], 10); pageWidth = parseInt(system.args[3], 10);
pageHeight = parseInt(pageWidth * 3 / 4, 10); pageHeight = parseInt(pageWidth * 3 / 4, 10); // it's as good an assumption as any
console.log("pageHeight:", pageHeight); console.log("pageHeight:", pageHeight);
page.viewportSize = { page.viewportSize = {
width: pageWidth, width: pageWidth,

View File

@ -1,13 +1,11 @@
/**
Definition of the ResumeDetector class.
@module utils/resume-detector
@license MIT. See LICENSE.md for details.
*/
(function() { (function() {
/**
Definition of the ResumeDetector class.
@module utils/resume-detector
@license MIT. See LICENSE.md for details.
*/
module.exports = function(rez) { module.exports = function(rez) {
if (rez.meta && rez.meta.format) { if (rez.meta && rez.meta.format) { //&& rez.meta.format.substr(0, 5).toUpperCase() == 'FRESH'
return 'fresh'; return 'fresh';
} else if (rez.basics) { } else if (rez.basics) {
return 'jrs'; return 'jrs';

55
dist/utils/resume-scrubber.js vendored Normal file
View File

@ -0,0 +1,55 @@
(function() {
module.exports = {
/**
Removes ignored or private fields from a resume object
@returns an object with the following structure:
{
scrubbed: the processed resume object
ignoreList: an array of ignored nodes that were removed
privateList: an array of private nodes that were removed
}
*/
scrubResume: function(rep, opts) {
var ignoreList, includePrivates, privateList, scrubbed, traverse;
traverse = require('traverse');
ignoreList = [];
privateList = [];
includePrivates = opts && opts.private;
scrubbed = traverse(rep).map(function() { // [^1]
if (!this.isLeaf) {
if (this.node.ignore === true || this.node.ignore === 'true') {
ignoreList.push(this.node);
this.delete();
} else if ((this.node.private === true || this.node.private === 'true') && !includePrivates) {
privateList.push(this.node);
this.delete();
}
}
if (_.isArray(this.node)) { // [^2]
this.after(function() {
this.update(_.compact(this.node));
});
}
});
return {
scrubbed: scrubbed,
ingoreList: ignoreList,
privateList: privateList
};
}
};
// [^1]: As of v0.6.6, the NPM traverse library has a quirk when attempting
// to remove array elements directly using traverse's `this.remove`. See:
// https://github.com/substack/js-traverse/issues/48
// [^2]: The workaround is to use traverse's 'this.delete' to nullify the value
// first, followed by removal with something like _.compact.
// https://github.com/substack/js-traverse/issues/48#issuecomment-142607200
}).call(this);
//# sourceMappingURL=resume-scrubber.js.map

View File

@ -1,11 +1,9 @@
/**
Definition of the SafeJsonLoader class.
@module utils/safe-json-loader
@license MIT. See LICENSE.md for details.
*/
(function() { (function() {
/**
Definition of the SafeJsonLoader class.
@module utils/safe-json-loader
@license MIT. See LICENSE.md for details.
*/
var FS, SyntaxErrorEx; var FS, SyntaxErrorEx;
FS = require('fs'); FS = require('fs');
@ -13,16 +11,19 @@ Definition of the SafeJsonLoader class.
SyntaxErrorEx = require('./syntax-error-ex'); SyntaxErrorEx = require('./syntax-error-ex');
module.exports = function(file) { module.exports = function(file) {
var ret, retRaw; var err, ret, retRaw;
ret = {}; ret = {};
try { try {
ret.raw = FS.readFileSync(file, 'utf8'); ret.raw = FS.readFileSync(file, 'utf8');
ret.json = JSON.parse(ret.raw); ret.json = JSON.parse(ret.raw);
} catch (_error) { } catch (error) {
err = error;
// If we get here, either FS.readFileSync or JSON.parse failed.
// We'll return HMSTATUS.readError or HMSTATUS.parseError.
retRaw = ret.raw && ret.raw.trim(); retRaw = ret.raw && ret.raw.trim();
ret.ex = { ret.ex = {
op: retRaw ? 'parse' : 'read', op: retRaw ? 'parse' : 'read',
inner: SyntaxErrorEx.is(_error) ? new SyntaxErrorEx(_error, retRaw) : _error, inner: SyntaxErrorEx.is(err) ? new SyntaxErrorEx(err, retRaw) : err,
file: file file: file
}; };
} }

View File

@ -1,19 +1,15 @@
/**
Safe spawn utility for HackMyResume / FluentCV.
@module utils/safe-spawn
@license MIT. See LICENSE.md for details.
*/
/** Safely spawn a process synchronously or asynchronously without throwing an
exception
*/
(function() { (function() {
/**
Safe spawn utility for HackMyResume / FluentCV.
@module utils/safe-spawn
@license MIT. See LICENSE.md for details.
*/
/** Safely spawn a process synchronously or asynchronously without throwing an
exception */
module.exports = function(cmd, args, isSync, callback, param) { module.exports = function(cmd, args, isSync, callback, param) {
var info, spawn; var ex, info, spawn;
try { try {
// .spawnSync not available on earlier Node.js, so default to .spawn
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) {
@ -33,11 +29,12 @@ exception
}; };
} }
} }
} catch (_error) { } catch (error) {
ex = error;
if (typeof callback === "function") { if (typeof callback === "function") {
callback(_error, param); callback(ex, param);
} }
return _error; return ex;
} }
}; };

View File

@ -1,26 +1,23 @@
/**
Object string transformation.
@module utils/string-transformer
@license MIT. See LICENSE.md for details.
*/
(function() { (function() {
/**
Object string transformation.
@module utils/string-transformer
@license MIT. See LICENSE.md for details.
*/
var _, moment; var _, moment;
_ = require('underscore'); _ = require('underscore');
moment = require('moment'); moment = require('moment');
/** /**
Create a copy of this object in which all string fields have been run through Create a copy of this object in which all string fields have been run through
a transformation function (such as a Markdown filter or XML encoder). a transformation function (such as a Markdown filter or XML encoder).
*/ */
module.exports = function(ret, filt, transformer) { module.exports = function(ret, filt, transformer) {
var that, transformStringsInObject; var that, transformStringsInObject;
that = this; that = this;
// TODO: refactor recursion
transformStringsInObject = function(obj, filters) { transformStringsInObject = function(obj, filters) {
if (!obj) { if (!obj) {
return; return;

22
dist/utils/string.js vendored
View File

@ -1,17 +1,13 @@
/**
Definitions of string utility functions.
@module utils/string
*/
/**
Determine if the string is null, empty, or whitespace.
See: http://stackoverflow.com/a/32800728/4942583
@method isNullOrWhitespace
*/
(function() { (function() {
/**
Definitions of string utility functions.
@module utils/string
*/
/**
Determine if the string is null, empty, or whitespace.
See: http://stackoverflow.com/a/32800728/4942583
@method isNullOrWhitespace
*/
String.isNullOrWhitespace = function(input) { String.isNullOrWhitespace = function(input) {
return !input || !input.trim(); return !input || !input.trim();
}; };

View File

@ -1,26 +1,22 @@
/**
Definition of the SyntaxErrorEx class.
@module utils/syntax-error-ex
@license MIT. See LICENSE.md for details.
*/
/**
Represents a SyntaxError exception with line and column info.
Collect syntax error information from the provided exception object. The
JavaScript `SyntaxError` exception isn't interpreted uniformly across environ-
ments, so we reparse on error to grab the line and column.
See: http://stackoverflow.com/q/13323356
@class SyntaxErrorEx
*/
(function() { (function() {
/**
Definition of the SyntaxErrorEx class.
@module utils/syntax-error-ex
@license MIT. See LICENSE.md for details.
*/
var SyntaxErrorEx; var SyntaxErrorEx;
SyntaxErrorEx = (function() { /**
function SyntaxErrorEx(ex, rawData) { Represents a SyntaxError exception with line and column info.
var JSONLint, colNum, lineNum, lint, ref; Collect syntax error information from the provided exception object. The
JavaScript `SyntaxError` exception isn't interpreted uniformly across environ-
ments, so we reparse on error to grab the line and column.
See: http://stackoverflow.com/q/13323356
@class SyntaxErrorEx
*/
SyntaxErrorEx = class SyntaxErrorEx {
constructor(ex, rawData) {
var JSONLint, colNum, err, lineNum, lint;
lineNum = null; lineNum = null;
colNum = null; colNum = null;
JSONLint = require('json-lint'); JSONLint = require('json-lint');
@ -28,22 +24,22 @@ See: http://stackoverflow.com/q/13323356
comments: false comments: false
}); });
if (lint.error) { if (lint.error) {
ref = [lint.line, lint.character], this.line = ref[0], this.col = ref[1]; [this.line, this.col] = [lint.line, lint.character];
} }
if (!lint.error) { if (!lint.error) {
JSONLint = require('jsonlint'); JSONLint = require('jsonlint');
try { try {
JSONLint.parse(rawData); JSONLint.parse(rawData);
} catch (_error) { } catch (error) {
this.line = (/on line (\d+)/.exec(_error))[1]; err = error;
this.line = (/on line (\d+)/.exec(err))[1];
} }
} }
} }
return SyntaxErrorEx; };
})();
// Return true if the supplied parameter is a JavaScript SyntaxError
SyntaxErrorEx.is = function(ex) { SyntaxErrorEx.is = function(ex) {
return ex instanceof SyntaxError; return ex instanceof SyntaxError;
}; };

40
dist/verbs/analyze.js vendored
View File

@ -1,14 +1,12 @@
/**
Implementation of the 'analyze' verb for HackMyResume.
@module verbs/analyze
@license MIT. See LICENSE.md for details.
*/
(function() { (function() {
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; }, Implementation of the 'analyze' verb for HackMyResume.
hasProp = {}.hasOwnProperty; @module verbs/analyze
@license MIT. See LICENSE.md for details.
*/
/** Private workhorse for the 'analyze' command. */
/** Analyze a single resume. */
var AnalyzeVerb, HMEVENT, HMSTATUS, MKDIRP, PATH, ResumeFactory, Verb, _, _analyze, _analyzeOne, _loadInspectors, chalk;
MKDIRP = require('mkdirp'); MKDIRP = require('mkdirp');
@ -26,22 +24,13 @@ Implementation of the 'analyze' verb for HackMyResume.
chalk = require('chalk'); chalk = require('chalk');
/** An invokable resume analysis command. */ /** An invokable resume analysis command. */
module.exports = AnalyzeVerb = class AnalyzeVerb extends Verb {
module.exports = AnalyzeVerb = (function(superClass) { constructor() {
extend(AnalyzeVerb, superClass); super('analyze', _analyze);
function AnalyzeVerb() {
AnalyzeVerb.__super__.constructor.call(this, 'analyze', _analyze);
} }
return AnalyzeVerb; };
})(Verb);
/** Private workhorse for the 'analyze' command. */
_analyze = function(sources, dst, opts) { _analyze = function(sources, dst, opts) {
var nlzrs, results; var nlzrs, results;
@ -58,7 +47,7 @@ Implementation of the 'analyze' verb for HackMyResume.
format: 'FRESH', format: 'FRESH',
objectify: true, objectify: true,
inner: { inner: {
"private": opts["private"] === true private: opts.private === true
} }
}, this); }, this);
if (opts.assert && this.hasError()) { if (opts.assert && this.hasError()) {
@ -80,9 +69,6 @@ Implementation of the 'analyze' verb for HackMyResume.
return results; return results;
}; };
/** Analyze a single resume. */
_analyzeOne = function(resumeObject, nlzrs, opts) { _analyzeOne = function(resumeObject, nlzrs, opts) {
var info, rez, safeFormat; var info, rez, safeFormat;
rez = resumeObject.rez; rez = resumeObject.rez;

199
dist/verbs/build.js vendored
View File

@ -1,14 +1,49 @@
/**
Implementation of the 'build' verb for HackMyResume.
@module verbs/build
@license MIT. See LICENSE.md for details.
*/
(function() { (function() {
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; }, Implementation of the 'build' verb for HackMyResume.
hasProp = {}.hasOwnProperty; @module verbs/build
@license MIT. See LICENSE.md for details.
*/
/**
Reinforce the chosen theme with "freebie" formats provided by HackMyResume.
A "freebie" format is an output format such as JSON, YML, or PNG that can be
generated directly from the resume model or from one of the theme's declared
output formats. For example, the PNG format can be generated for any theme
that declares an HTML format; the theme doesn't have to provide an explicit
PNG template.
@param theTheme A FRESHTheme or JRSTheme object.
*/
/**
Given a source resume in FRESH or JRS format, a destination resume path, and a
theme file, generate 0..N resumes in the desired formats.
@param src Path to the source JSON resume file: "rez/resume.json".
@param dst An array of paths to the target resume file(s).
@param opts Generation options.
*/
/**
Expand output files. For example, "foo.all" should be expanded to
["foo.html", "foo.doc", "foo.pdf", "etc"].
@param dst An array of output files as specified by the user.
@param theTheme A FRESHTheme or JRSTheme object.
*/
/**
Load the specified theme, which could be either a FRESH theme or a JSON Resume
theme (or both).
*/
/**
Prepare for a BUILD run.
*/
/**
Generate a single target resume such as "out/rez.html" or "out/rez.doc".
TODO: Refactor.
@param targInfo Information for the target resume.
@param theme A FRESHTheme or JRSTheme object.
*/
/** Ensure that user-specified outputs/targets are valid. */
/**
Verify the specified theme name/path.
*/
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;
_ = require('underscore'); _ = require('underscore');
@ -69,34 +104,17 @@ Implementation of the 'build' verb for HackMyResume.
loadTheme = null; loadTheme = null;
/** An invokable resume generation command. */ /** An invokable resume generation command. */
module.exports = BuildVerb = class BuildVerb extends Verb {
module.exports = BuildVerb = (function(superClass) {
extend1(BuildVerb, superClass);
/** Create a new build verb. */ /** Create a new build verb. */
constructor() {
function BuildVerb() { super('build', _build);
BuildVerb.__super__.constructor.call(this, 'build', _build);
} }
return BuildVerb; };
})(Verb);
/**
Given a source resume in FRESH or JRS format, a destination resume path, and a
theme file, generate 0..N resumes in the desired formats.
@param src Path to the source JSON resume file: "rez/resume.json".
@param dst An array of paths to the target resume file(s).
@param opts Generation options.
*/
_build = function(src, dst, opts) { _build = function(src, dst, opts) {
var inv, isFRESH, mixed, newEx, orgFormat, problemSheets, results, rez, sheetObjects, sheets, tFolder, targets, theme, toFormat; var err, 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
@ -104,26 +122,30 @@ Implementation of the 'build' verb for HackMyResume.
return null; return null;
} }
_prep.call(this, src, dst, opts); _prep.call(this, src, dst, opts);
// Load input resumes as JSON...
sheetObjects = ResumeFactory.load(src, { sheetObjects = ResumeFactory.load(src, {
format: null, format: null,
objectify: false, objectify: false,
quit: true, quit: true,
inner: { inner: {
sort: _opts.sort, sort: _opts.sort,
"private": _opts["private"] private: _opts.private
} }
}, this); }, this);
// Explicit check for any resume loading errors...
problemSheets = _.filter(sheetObjects, function(so) { problemSheets = _.filter(sheetObjects, function(so) {
return so.fluenterror; return so.fluenterror;
}); });
if (problemSheets && problemSheets.length) { if (problemSheets && problemSheets.length) {
problemSheets[0].quit = true; problemSheets[0].quit = true; // can't go on
this.err(problemSheets[0].fluenterror, problemSheets[0]); this.err(problemSheets[0].fluenterror, problemSheets[0]);
return null; return null;
} }
// Get the collection of raw JSON sheets
sheets = sheetObjects.map(function(r) { sheets = sheetObjects.map(function(r) {
return r.json; return r.json;
}); });
// Load the theme...
theme = null; theme = null;
this.stat(HMEVENT.beforeTheme, { this.stat(HMEVENT.beforeTheme, {
theme: _opts.theme theme: _opts.theme
@ -137,10 +159,11 @@ Implementation of the 'build' verb for HackMyResume.
} }
theme = _opts.themeObj = _loadTheme(tFolder); theme = _opts.themeObj = _loadTheme(tFolder);
_addFreebieFormats(theme); _addFreebieFormats(theme);
} catch (_error) { } catch (error) {
err = error;
newEx = { newEx = {
fluenterror: HMSTATUS.themeLoad, fluenterror: HMSTATUS.themeLoad,
inner: _error, inner: err,
attempted: _opts.theme, attempted: _opts.theme,
quit: true quit: true
}; };
@ -150,6 +173,7 @@ Implementation of the 'build' verb for HackMyResume.
this.stat(HMEVENT.afterTheme, { this.stat(HMEVENT.afterTheme, {
theme: theme theme: theme
}); });
// Check for invalid outputs...
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, {
@ -159,6 +183,7 @@ Implementation of the 'build' verb for HackMyResume.
}); });
return null; return null;
} }
//# 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;
@ -195,14 +220,17 @@ Implementation of the 'build' verb for HackMyResume.
fmt: toFormat fmt: toFormat
}); });
} }
// Announce the theme
this.stat(HMEVENT.applyTheme, { this.stat(HMEVENT.applyTheme, {
r: rez, r: rez,
theme: theme theme: theme
}); });
// Load the resume into a FRESHResume or JRSResume object
_rezObj = new RTYPES[toFormat]().parseJSON(rez, { _rezObj = new RTYPES[toFormat]().parseJSON(rez, {
"private": _opts["private"] private: _opts.private
}); });
targets = _expand(dst, theme); targets = _expand(dst, theme);
// Run the transformation!
_.each(targets, function(t) { _.each(targets, function(t) {
var ref; var ref;
if (this.hasError() && opts.assert) { if (this.hasError() && opts.assert) {
@ -227,16 +255,12 @@ Implementation of the 'build' verb for HackMyResume.
return results; return results;
}; };
/**
Prepare for a BUILD run.
*/
_prep = function(src, dst, opts) { _prep = function(src, dst, opts) {
var that; var that;
// 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';
_opts.prettify = opts.prettify === true; _opts.prettify = opts.prettify === true;
_opts["private"] = opts["private"] === true; _opts.private = opts.private === true;
_opts.noescape = opts.noescape === true; _opts.noescape = opts.noescape === true;
_opts.css = opts.css; _opts.css = opts.css;
_opts.pdf = opts.pdf; _opts.pdf = opts.pdf;
@ -249,6 +273,7 @@ Implementation of the 'build' verb for HackMyResume.
_opts.sort = opts.sort; _opts.sort = opts.sort;
_opts.wkhtmltopdf = opts.wkhtmltopdf; _opts.wkhtmltopdf = opts.wkhtmltopdf;
that = this; that = this;
// Set up callbacks for internal generators
_opts.onTransform = function(info) { _opts.onTransform = function(info) {
that.stat(HMEVENT.afterTransform, info); that.stat(HMEVENT.afterTransform, info);
}; };
@ -258,17 +283,11 @@ Implementation of the 'build' verb for HackMyResume.
_opts.afterWrite = function(info) { _opts.afterWrite = function(info) {
that.stat(HMEVENT.afterWrite, info); that.stat(HMEVENT.afterWrite, info);
}; };
// If two or more files are passed to the GENERATE command and the TO
// keyword is omitted, the last file specifies the output file.
(src.length > 1 && (!dst || !dst.length)) && dst.push(src.pop()); (src.length > 1 && (!dst || !dst.length)) && dst.push(src.pop());
}; };
/**
Generate a single target resume such as "out/rez.html" or "out/rez.doc".
TODO: Refactor.
@param targInfo Information for the target resume.
@param theme A FRESHTheme or JRSTheme object.
*/
_single = function(targInfo, theme, finished) { _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;
@ -286,6 +305,8 @@ Implementation of the 'build' verb for HackMyResume.
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.
// 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(function(fmt) { theFormat = _fmts.filter(function(fmt) {
return fmt.name === targInfo.fmt.outFormat; return fmt.name === targInfo.fmt.outFormat;
@ -293,15 +314,17 @@ Implementation of the 'build' verb for HackMyResume.
MKDIRP.sync(PATH.dirname(f)); MKDIRP.sync(PATH.dirname(f));
ret = theFormat.gen.generate(_rezObj, f, _opts); ret = theFormat.gen.generate(_rezObj, f, _opts);
} else { } else {
// Otherwise this is an ad-hoc format (JSON, YML, or PNG) that every theme
// gets "for free".
theFormat = _fmts.filter(function(fmt) { theFormat = _fmts.filter(function(fmt) {
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); MKDIRP.sync(outFolder); // Ensure dest folder exists;
ret = theFormat.gen.generate(_rezObj, f, _opts); ret = theFormat.gen.generate(_rezObj, f, _opts);
} }
} catch (_error) { } catch (error) {
e = _error; e = error;
ex = e; ex = e;
} }
this.stat(HMEVENT.afterGenerate, { this.stat(HMEVENT.afterGenerate, {
@ -322,9 +345,6 @@ Implementation of the 'build' verb for HackMyResume.
return ret; return ret;
}; };
/** 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,
@ -341,18 +361,9 @@ Implementation of the 'build' verb for HackMyResume.
}); });
}; };
/**
Reinforce the chosen theme with "freebie" formats provided by HackMyResume.
A "freebie" format is an output format such as JSON, YML, or PNG that can be
generated directly from the resume model or from one of the theme's declared
output formats. For example, the PNG format can be generated for any theme
that declares an HTML format; the theme doesn't have to provide an explicit
PNG template.
@param theTheme A FRESHTheme or JRSTheme object.
*/
_addFreebieFormats = function(theTheme) { _addFreebieFormats = function(theTheme) {
// Add freebie formats (JSON, YAML, PNG) every theme gets...
// Add HTML-driven PNG only if the theme has an HTML format.
theTheme.formats.json = theTheme.formats.json || { theTheme.formats.json = theTheme.formats.json || {
freebie: true, freebie: true,
title: 'json', title: 'json',
@ -383,16 +394,10 @@ Implementation of the 'build' verb for HackMyResume.
} }
}; };
/**
Expand output files. For example, "foo.all" should be expanded to
["foo.html", "foo.doc", "foo.pdf", "etc"].
@param dst An array of output files as specified by the user.
@param theTheme A FRESHTheme or JRSTheme object.
*/
_expand = function(dst, theTheme) { _expand = function(dst, theTheme) {
var destColl, targets; var destColl, targets;
// 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.
destColl = (dst && dst.length && dst) || [PATH.normalize('out/resume.all')]; destColl = (dst && dst.length && dst) || [PATH.normalize('out/resume.all')];
targets = []; targets = [];
destColl.forEach(function(t) { destColl.forEach(function(t) {
@ -417,19 +422,22 @@ Implementation of the 'build' verb for HackMyResume.
return targets; return targets;
}; };
/**
Verify the specified theme name/path.
*/
_verifyTheme = function(themeNameOrPath) { _verifyTheme = function(themeNameOrPath) {
var exists, tFolder, themesObj; var exists, tFolder, themesObj;
// First, see if this is one of the predefined FRESH themes. There are only a
// handful of these, but they may change over time, so we need to query
// the official source of truth: the fresh-themes repository, which mounts the
// themes conveniently by name to the module object, and which is embedded
// locally inside the HackMyResume installation.
themesObj = require('fresh-themes'); themesObj = require('fresh-themes');
if (_.has(themesObj.themes, themeNameOrPath)) { if (_.has(themesObj.themes, themeNameOrPath)) {
tFolder = PATH.join(parsePath(require.resolve('fresh-themes')).dirname, '/themes/', themeNameOrPath); tFolder = PATH.join(parsePath(require.resolve('fresh-themes')).dirname, '/themes/', themeNameOrPath);
} else { } else {
// Otherwsie it's a path to an arbitrary FRESH or JRS theme sitting somewhere
// on the user's system (or, in the future, at a URI).
tFolder = PATH.resolve(themeNameOrPath); tFolder = PATH.resolve(themeNameOrPath);
} }
// In either case, make sure the theme folder exists
exists = require('path-exists').sync; exists = require('path-exists').sync;
if (exists(tFolder)) { if (exists(tFolder)) {
return tFolder; return tFolder;
@ -441,21 +449,36 @@ Implementation of the 'build' verb for HackMyResume.
} }
}; };
/**
Load the specified theme, which could be either a FRESH theme or a JSON Resume
theme (or both).
*/
_loadTheme = function(tFolder) { _loadTheme = function(tFolder) {
var exists, theTheme, themeJsonPath; var exists, theTheme, themeJsonPath;
themeJsonPath = PATH.join(tFolder, 'theme.json'); themeJsonPath = PATH.join(tFolder, 'theme.json'); // [^1]
exists = require('path-exists').sync; exists = require('path-exists').sync;
// Create a FRESH or JRS theme object
theTheme = exists(themeJsonPath) ? new FRESHTheme().open(tFolder) : new JRSTheme().open(tFolder); theTheme = exists(themeJsonPath) ? new FRESHTheme().open(tFolder) : new JRSTheme().open(tFolder);
// Cache the theme object
_opts.themeObj = theTheme; _opts.themeObj = theTheme;
return theTheme; return theTheme;
}; };
// FOOTNOTES
// ------------------------------------------------------------------------------
// [^1] We don't know ahead of time whether this is a FRESH or JRS theme.
// However, all FRESH themes have a theme.json file, so we'll use that as a
// canary for now, as an interim solution.
// Unfortunately, with the exception of FRESH's theme.json, both FRESH and
// JRS themes are free-form and don't have a ton of reliable distinguishing
// marks, which makes a simple task like ad hoc theme detection harder than
// it should be to do cleanly.
// Another complicating factor is that it's possible for a theme to be BOTH.
// That is, a single set of theme files can serve as a FRESH theme -and- a
// JRS theme.
// TODO: The most robust way to deal with all these issues is with a strong
// theme validator. If a theme structure validates as a particular theme
// type, then for all intents and purposes, it IS a theme of that type.
}).call(this); }).call(this);
//# sourceMappingURL=build.js.map //# sourceMappingURL=build.js.map

100
dist/verbs/convert.js vendored
View File

@ -1,14 +1,13 @@
/**
Implementation of the 'convert' verb for HackMyResume.
@module verbs/convert
@license MIT. See LICENSE.md for details.
*/
(function() { (function() {
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; }, Implementation of the 'convert' verb for HackMyResume.
hasProp = {}.hasOwnProperty; @module verbs/convert
@license MIT. See LICENSE.md for details.
*/
/** Private workhorse method. Convert 0..N resumes between FRESH and JRS
formats. */
/** Private workhorse method. Convert a single resume. */
var ConvertVerb, HMEVENT, HMSTATUS, ResumeFactory, Verb, _, _convert, _convertOne, chalk;
ResumeFactory = require('../core/resume-factory'); ResumeFactory = require('../core/resume-factory');
@ -22,24 +21,15 @@ Implementation of the 'convert' verb for HackMyResume.
HMEVENT = require('../core/event-codes'); HMEVENT = require('../core/event-codes');
module.exports = ConvertVerb = (function(superClass) { module.exports = ConvertVerb = class ConvertVerb extends Verb {
extend(ConvertVerb, superClass); constructor() {
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
formats.
*/
_convert = function(srcs, dst, opts) { _convert = function(srcs, dst, opts) {
var results; var fmtUp, results, targetVer;
if (!srcs || !srcs.length) { if (!srcs || !srcs.length) {
this.err(HMSTATUS.resumeNotFound, { this.err(HMSTATUS.resumeNotFound, {
quit: true quit: true
@ -60,18 +50,44 @@ Implementation of the 'convert' verb for HackMyResume.
}); });
} }
} }
// Different number of source and dest resumes? Error out.
if (srcs && dst && srcs.length && dst.length && srcs.length !== dst.length) { if (srcs && dst && srcs.length && dst.length && srcs.length !== dst.length) {
this.err(HMSTATUS.inputOutputParity, { this.err(HMSTATUS.inputOutputParity, {
quit: true quit: true
}); });
} }
// Validate the destination format (if specified)
targetVer = null;
if (opts.format) {
fmtUp = opts.format.trim().toUpperCase();
if (!_.contains(['FRESH', 'FRESCA', 'JRS', 'JRS@1', 'JRS@edge'], fmtUp)) {
this.err(HMSTATUS.invalidSchemaVersion, {
data: opts.format.trim(),
quit: true
});
}
}
// freshVerRegex = require '../utils/fresh-version-regex'
// matches = fmtUp.match freshVerRegex()
// # null
// # [ 'JRS@1.0', 'JRS', '1.0', index: 0, input: 'FRESH' ]
// # [ 'FRESH', 'FRESH', undefined, index: 0, input: 'FRESH' ]
// if not matches
// @err HMSTATUS.invalidSchemaVersion, data: opts.format.trim(), quit: true
// targetSchema = matches[1]
// targetVer = matches[2] || '1'
// If any errors have occurred this early, we're done.
if (this.hasError()) { if (this.hasError()) {
this.reject(this.errorCode); this.reject(this.errorCode);
return null; return null;
} }
// Map each source resume to the converted destination resume
results = _.map(srcs, function(src, idx) { results = _.map(srcs, function(src, idx) {
var r; var r;
r = _convertOne.call(this, src, dst, idx); // Convert each resume in turn
r = _convertOne.call(this, src, dst, idx, fmtUp);
// Handle conversion errors
if (r.fluenterror) { if (r.fluenterror) {
r.quit = opts.assert; r.quit = opts.assert;
this.err(r.fluenterror, r); this.err(r.fluenterror, r);
@ -86,11 +102,9 @@ Implementation of the 'convert' verb for HackMyResume.
return results; return results;
}; };
_convertOne = function(src, dst, idx, targetSchema) {
/** Private workhorse method. Convert a single resume. */ var err, rez, rinfo, srcFmt, targetFormat;
// Load the resume
_convertOne = function(src, dst, idx) {
var rez, rinfo, srcFmt, targetFormat;
rinfo = ResumeFactory.loadOne(src, { rinfo = ResumeFactory.loadOne(src, {
format: null, format: null,
objectify: true, objectify: true,
@ -98,19 +112,23 @@ Implementation of the 'convert' verb for HackMyResume.
privatize: false privatize: false
} }
}); });
// If a load error occurs, report it and move on to the next file (if any)
if (rinfo.fluenterror) { if (rinfo.fluenterror) {
this.stat(HMEVENT.beforeConvert, { this.stat(HMEVENT.beforeConvert, {
srcFile: src, srcFile: src, //rinfo.file
srcFmt: '???', srcFmt: '???',
dstFile: dst[idx], dstFile: dst[idx],
dstFmt: '???', dstFmt: '???',
error: true error: true
}); });
//@err rinfo.fluenterror, rinfo
return rinfo; return rinfo;
} }
// Determine the resume's SOURCE format
// TODO: replace with detector component
rez = rinfo.rez; rez = rinfo.rez;
srcFmt = ''; srcFmt = '';
if (rez.meta && rez.meta.format) { if (rez.meta && rez.meta.format) { //&& rez.meta.format.substr(0, 5).toUpperCase() == 'FRESH'
srcFmt = 'FRESH'; srcFmt = 'FRESH';
} else if (rez.basics) { } else if (rez.basics) {
srcFmt = 'JRS'; srcFmt = 'JRS';
@ -118,14 +136,28 @@ Implementation of the 'convert' verb for HackMyResume.
rinfo.fluenterror = HMSTATUS.unknownSchema; rinfo.fluenterror = HMSTATUS.unknownSchema;
return rinfo; return rinfo;
} }
targetFormat = srcFmt === 'JRS' ? 'FRESH' : 'JRS'; // Determine the TARGET format for the conversion
targetFormat = targetSchema || (srcFmt === 'JRS' ? 'FRESH' : 'JRS');
// Fire the beforeConvert event
this.stat(HMEVENT.beforeConvert, { this.stat(HMEVENT.beforeConvert, {
srcFile: rinfo.file, srcFile: rinfo.file,
srcFmt: srcFmt, srcFmt: srcFmt,
dstFile: dst[idx], dstFile: dst[idx],
dstFmt: targetFormat dstFmt: targetFormat
}); });
rez.saveAs(dst[idx], targetFormat); try {
// Save it to the destination format
rez.saveAs(dst[idx], targetFormat);
} catch (error) {
err = error;
if (err.badVer) {
return {
fluenterror: HMSTATUS.invalidSchemaVersion,
quit: true,
data: err.badVer
};
}
}
return rez; return rez;
}; };

47
dist/verbs/create.js vendored
View File

@ -1,14 +1,12 @@
/**
Implementation of the 'create' verb for HackMyResume.
@module verbs/create
@license MIT. See LICENSE.md for details.
*/
(function() { (function() {
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; }, Implementation of the 'create' verb for HackMyResume.
hasProp = {}.hasOwnProperty; @module verbs/create
@license MIT. See LICENSE.md for details.
*/
/** Create a new empty resume in either FRESH or JRS format. */
/** Create a single new resume */
var CreateVerb, HMEVENT, HMSTATUS, MKDIRP, PATH, Verb, _, _create, _createOne, chalk;
MKDIRP = require('mkdirp'); MKDIRP = require('mkdirp');
@ -24,19 +22,12 @@ Implementation of the 'create' verb for HackMyResume.
HMEVENT = require('../core/event-codes'); HMEVENT = require('../core/event-codes');
module.exports = CreateVerb = (function(superClass) { module.exports = CreateVerb = class CreateVerb extends Verb {
extend(CreateVerb, superClass); constructor() {
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 = function(src, dst, opts) { _create = function(src, dst, opts) {
var results; var results;
@ -66,11 +57,8 @@ Implementation of the 'create' verb for HackMyResume.
return results; return results;
}; };
/** Create a single new resume */
_createOne = function(t, opts) { _createOne = function(t, opts) {
var RezClass, newRez, ret, safeFmt; var RezClass, err, newRez, ret, safeFmt;
try { try {
ret = null; ret = null;
safeFmt = opts.format.toUpperCase(); safeFmt = opts.format.toUpperCase();
@ -78,15 +66,16 @@ Implementation of the 'create' verb for HackMyResume.
fmt: safeFmt, fmt: safeFmt,
file: t file: t
}); });
MKDIRP.sync(PATH.dirname(t)); MKDIRP.sync(PATH.dirname(t)); // Ensure dest folder exists;
RezClass = require('../core/' + safeFmt.toLowerCase() + '-resume'); RezClass = require('../core/' + safeFmt.toLowerCase() + '-resume');
newRez = RezClass["default"](); newRez = RezClass.default();
newRez.save(t); newRez.save(t);
ret = newRez; ret = newRez;
} catch (_error) { } catch (error) {
err = error;
ret = { ret = {
fluenterror: HMSTATUS.createError, fluenterror: HMSTATUS.createError,
inner: _error inner: err
}; };
} finally { } finally {
this.stat(HMEVENT.afterCreate, { this.stat(HMEVENT.afterCreate, {

42
dist/verbs/peek.js vendored
View File

@ -1,14 +1,12 @@
/**
Implementation of the 'peek' verb for HackMyResume.
@module verbs/peek
@license MIT. See LICENSE.md for details.
*/
(function() { (function() {
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; }, Implementation of the 'peek' verb for HackMyResume.
hasProp = {}.hasOwnProperty; @module verbs/peek
@license MIT. See LICENSE.md for details.
*/
/** Peek at a resume, resume section, or resume field. */
/** Peek at a single resume, resume section, or resume field. */
var HMEVENT, HMSTATUS, PeekVerb, Verb, _, __, _peek, _peekOne, safeLoadJSON;
Verb = require('../verbs/verb'); Verb = require('../verbs/verb');
@ -22,19 +20,12 @@ Implementation of the 'peek' verb for HackMyResume.
HMEVENT = require('../core/event-codes'); HMEVENT = require('../core/event-codes');
module.exports = PeekVerb = (function(superClass) { module.exports = PeekVerb = class PeekVerb extends Verb {
extend(PeekVerb, superClass); constructor() {
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 = function(src, dst, opts) { _peek = function(src, dst, opts) {
var objPath, results; var objPath, results;
@ -54,6 +45,8 @@ Implementation of the 'peek' verb for HackMyResume.
if (tgt.error) { if (tgt.error) {
this.setError(tgt.error.fluenterror, tgt.error); this.setError(tgt.error.fluenterror, tgt.error);
} }
//tgt.error.quit = opts.assert
//@err tgt.error.fluenterror, tgt.error
return tgt; return tgt;
}, this); }, this);
if (this.hasError() && !opts.assert) { if (this.hasError() && !opts.assert) {
@ -64,20 +57,20 @@ Implementation of the 'peek' verb for HackMyResume.
return results; return results;
}; };
/** Peek at a single resume, resume section, or resume field. */
_peekOne = function(t, objPath) { _peekOne = function(t, objPath) {
var errCode, obj, pkgError, tgt; var errCode, obj, pkgError, tgt;
this.stat(HMEVENT.beforePeek, { this.stat(HMEVENT.beforePeek, {
file: t, file: t,
target: objPath target: objPath
}); });
// Load the input file JSON 1st
obj = safeLoadJSON(t); obj = safeLoadJSON(t);
// Fetch the requested object path (or the entire file)
tgt = null; tgt = null;
if (!obj.ex) { if (!obj.ex) {
tgt = objPath ? __.get(obj.json, objPath) : obj.json; tgt = objPath ? __.get(obj.json, objPath) : obj.json;
} }
//# safeLoadJSON can only return a READ error or a PARSE error
pkgError = null; pkgError = null;
if (obj.ex) { if (obj.ex) {
errCode = obj.ex.op === 'parse' ? HMSTATUS.parseError : HMSTATUS.readError; errCode = obj.ex.op === 'parse' ? HMSTATUS.parseError : HMSTATUS.readError;
@ -89,6 +82,7 @@ Implementation of the 'peek' verb for HackMyResume.
inner: obj.ex inner: obj.ex
}; };
} }
// Fire the 'afterPeek' event with collected info
this.stat(HMEVENT.afterPeek, { this.stat(HMEVENT.afterPeek, {
file: t, file: t,
requested: objPath, requested: objPath,

View File

@ -1,14 +1,21 @@
/**
Implementation of the 'validate' verb for HackMyResume.
@module verbs/validate
@license MIT. See LICENSE.md for details.
*/
(function() { (function() {
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; }, Implementation of the 'validate' verb for HackMyResume.
hasProp = {}.hasOwnProperty; @module verbs/validate
@license MIT. See LICENSE.md for details.
*/
/**
Validate a single resume.
@returns {
file: <fileName>,
isValid: <validFlag>,
status: <validationStatus>,
violations: <validationErrors>,
schema: <schemaType>,
error: <errorObject>
}
*/
var FS, HMEVENT, HMSTATUS, ResumeFactory, SyntaxErrorEx, ValidateVerb, Verb, _, _validate, _validateOne, chalk, safeLoadJSON;
FS = require('fs'); FS = require('fs');
@ -28,20 +35,15 @@ Implementation of the 'validate' verb for HackMyResume.
safeLoadJSON = require('../utils/safe-json-loader'); safeLoadJSON = require('../utils/safe-json-loader');
/** An invokable resume validation command. */ /** An invokable resume validation command. */
module.exports = ValidateVerb = class ValidateVerb extends Verb {
module.exports = ValidateVerb = (function(superClass) { constructor() {
extend(ValidateVerb, superClass); super('validate', _validate);
function ValidateVerb() {
ValidateVerb.__super__.constructor.call(this, 'validate', _validate);
} }
return ValidateVerb; };
})(Verb);
// Validate 1 to N resumes in FRESH or JSON Resume format.
_validate = function(sources, unused, opts) { _validate = function(sources, unused, opts) {
var results, schemas, validator; var results, schemas, validator;
if (!sources || !sources.length) { if (!sources || !sources.length) {
@ -71,21 +73,8 @@ Implementation of the 'validate' verb for HackMyResume.
return 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) { _validateOne = function(t, validator, schemas, opts) {
var errCode, obj, ret, validate; var err, errCode, obj, ret, validate;
ret = { ret = {
file: t, file: t,
isValid: false, isValid: false,
@ -93,6 +82,7 @@ Implementation of the 'validate' verb for HackMyResume.
schema: '-----' schema: '-----'
}; };
try { try {
// Read and parse the resume JSON. Won't throw.
obj = safeLoadJSON(t); obj = safeLoadJSON(t);
if (!obj.ex) { if (!obj.ex) {
if (obj.json.basics) { if (obj.json.basics) {
@ -111,6 +101,7 @@ Implementation of the 'validate' verb for HackMyResume.
ret.violations = validate.errors; ret.violations = validate.errors;
} }
} else { } else {
// If failure, package JSON read/parse errors
if (obj.ex.op === 'parse') { if (obj.ex.op === 'parse') {
errCode = HMSTATUS.parseError; errCode = HMSTATUS.parseError;
ret.status = 'broken'; ret.status = 'broken';
@ -124,10 +115,12 @@ Implementation of the 'validate' verb for HackMyResume.
quiet: errCode === HMSTATUS.readError quiet: errCode === HMSTATUS.readError
}; };
} }
} catch (_error) { } catch (error) {
err = error;
// Package any unexpected exceptions
ret.error = { ret.error = {
fluenterror: HMSTATUS.validateError, fluenterror: HMSTATUS.validateError,
inner: _error inner: err
}; };
} }
this.stat(HMEVENT.afterValidate, ret); this.stat(HMEVENT.afterValidate, ret);

72
dist/verbs/verb.js vendored
View File

@ -1,11 +1,9 @@
/**
Definition of the Verb class.
@module verbs/verb
@license MIT. See LICENSE.md for details.
*/
(function() { (function() {
/**
Definition of the Verb class.
@module verbs/verb
@license MIT. See LICENSE.md for details.
*/
var EVENTS, HMEVENT, Promise, Verb; var EVENTS, HMEVENT, Promise, Verb;
EVENTS = require('events'); EVENTS = require('events');
@ -14,65 +12,57 @@ Definition of the Verb class.
Promise = require('pinkie-promise'); Promise = require('pinkie-promise');
/** /**
An abstract invokable verb. An abstract invokable verb.
Provides base class functionality for verbs. Provide common services such as Provides base class functionality for verbs. Provide common services such as
error handling, event management, and promise support. error handling, event management, and promise support.
@class Verb @class Verb
*/ */
module.exports = Verb = class Verb {
module.exports = Verb = (function() {
/** Constructor. Automatically called at creation. */ /** Constructor. Automatically called at creation. */
function Verb(moniker, workhorse) { constructor(moniker, workhorse) {
this.moniker = moniker; this.moniker = moniker;
this.workhorse = workhorse; this.workhorse = workhorse;
this.emitter = new EVENTS.EventEmitter(); this.emitter = new EVENTS.EventEmitter();
return; return;
} }
/** Invoke the command. */ /** Invoke the command. */
invoke() {
Verb.prototype.invoke = function() {
var argsArray, that; var argsArray, that;
// Sent the 'begin' notification for this verb
this.stat(HMEVENT.begin, { this.stat(HMEVENT.begin, {
cmd: this.moniker cmd: this.moniker
}); });
// Prepare command arguments
argsArray = Array.prototype.slice.call(arguments); argsArray = Array.prototype.slice.call(arguments);
// Create a promise for this verb instance
that = this; that = this;
return this.promise = new Promise(function(res, rej) { return this.promise = new Promise(function(res, rej) {
that.resolve = res; that.resolve = res;
that.reject = rej; that.reject = rej;
that.workhorse.apply(that, argsArray); that.workhorse.apply(that, argsArray);
}); });
}; }
/** Forward subscriptions to the event emitter. */ /** Forward subscriptions to the event emitter. */
on() {
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(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(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); this.setError(errorCode, payload);
if (payload.quit) { if (payload.quit) {
this.reject(errorCode); this.reject(errorCode);
@ -82,36 +72,28 @@ Definition of the Verb class.
throw payload; throw payload;
} }
return true; return true;
}; }
/** Fire the 'hmr:status' error event. */ /** Fire the 'hmr:status' error event. */
stat(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? */ /** Has an error occurred during this verb invocation? */
hasError() {
Verb.prototype.hasError = function() {
return this.errorCode || this.errorObj; return this.errorCode || this.errorObj;
}; }
/** Associate error info with the invocation. */ /** Associate error info with the invocation. */
setError(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);

View File

@ -49,32 +49,33 @@
"main": "dist/index.js", "main": "dist/index.js",
"homepage": "https://github.com/hacksalot/HackMyResume", "homepage": "https://github.com/hacksalot/HackMyResume",
"dependencies": { "dependencies": {
"chalk": "^1.1.1", "chalk": "^2.3.1",
"commander": "^2.9.0", "commander": "^2.9.0",
"copy": "^0.1.3", "copy": "^0.3.1",
"escape-latex": "^0.1.2", "escape-latex": "^1.0.0",
"extend": "^3.0.0", "extend": "^3.0.0",
"fresh-jrs-converter": "^0.2.3", "fresh-jrs-converter": "^1.0.0",
"fresh-resume-schema": "^1.0.0-beta", "fresh-resume-schema": "^1.0.0-beta",
"fresh-resume-starter": "^0.3.1", "fresh-resume-starter": "^0.3.1",
"fresh-themes": "^0.16.0-beta", "fresh-resume-validator": "^0.2.0",
"fs-extra": "^0.26.4", "fresh-themes": "^0.17.0-beta",
"fs-extra": "^5.0.0",
"glob": "^7.1.2", "glob": "^7.1.2",
"handlebars": "^4.0.5", "handlebars": "^4.0.5",
"html": "0.0.10", "html": "^1.0.0",
"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", "jsonlint": "^1.6.2",
"lodash": "^3.10.1", "lodash": "^4.17.5",
"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": "^1.0.2",
"path-exists": "^2.1.0", "path-exists": "^3.0.0",
"pinkie-promise": "^2.0.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.4.3",
"slash": "^1.0.0", "slash": "^1.0.0",
"string-padding": "^1.0.2", "string-padding": "^1.0.2",
"string.prototype.endswith": "^0.2.0", "string.prototype.endswith": "^0.2.0",
@ -83,22 +84,20 @@
"underscore": "^1.8.3", "underscore": "^1.8.3",
"word-wrap": "^1.1.0", "word-wrap": "^1.1.0",
"xml-escape": "^1.0.0", "xml-escape": "^1.0.0",
"yamljs": "^0.2.4" "yamljs": "^0.3.0"
}, },
"devDependencies": { "devDependencies": {
"chai": "*", "chai": "*",
"chai-as-promised": "^7.1.1", "chai-as-promised": "^7.1.1",
"dir-compare": "0.0.2", "dir-compare": "^1.4.0",
"fresh-test-resumes": "^0.9.1", "fresh-test-resumes": "^0.9.2",
"fresh-test-themes": "^0.1.0", "fresh-test-themes": "^0.2.0",
"fresh-theme-underscore": "^0.1.1",
"grunt": "*", "grunt": "*",
"grunt-cli": "^0.1.13", "grunt-contrib-clean": "^1.1.0",
"grunt-contrib-clean": "^0.7.0", "grunt-contrib-coffee": "^2.0.0",
"grunt-contrib-coffee": "^0.13.0", "grunt-contrib-copy": "^1.0.0",
"grunt-contrib-copy": "^0.8.2", "grunt-contrib-jshint": "^1.1.0",
"grunt-contrib-jshint": "^0.11.3",
"grunt-contrib-yuidoc": "^0.10.0",
"grunt-jsdoc": "^1.1.0",
"grunt-simple-mocha": "*", "grunt-simple-mocha": "*",
"jsonresume-theme-boilerplate": "^0.1.2", "jsonresume-theme-boilerplate": "^0.1.2",
"jsonresume-theme-classy": "^1.0.9", "jsonresume-theme-classy": "^1.0.9",

View File

@ -110,17 +110,19 @@ assembleError = ( ex ) ->
quit = false quit = false
when HMSTATUS.resumeNotFound when HMSTATUS.resumeNotFound
msg = M2C( this.msgs.resumeNotFound.msg, 'yellow' ); #msg = M2C( this.msgs.resumeNotFound.msg, 'yellow' );
msg += M2C(FS.readFileSync(
PATH.resolve(__dirname, 'help/' + ex.verb + '.txt'), 'utf8' ), 'white', 'yellow')
when HMSTATUS.missingCommand when HMSTATUS.missingCommand
msg = M2C( this.msgs.missingCommand.msg + " (", 'yellow'); # msg = M2C( this.msgs.missingCommand.msg + " (", 'yellow');
msg += Object.keys( FCMD.verbs ).map( (v, idx, ar) -> # msg += Object.keys( FCMD.verbs ).map( (v, idx, ar) ->
return ( if idx == ar.length - 1 then chalk.yellow('or ') else '') + # return ( if idx == ar.length - 1 then chalk.yellow('or ') else '') +
chalk.yellow.bold(v.toUpperCase()); # chalk.yellow.bold(v.toUpperCase());
).join( chalk.yellow(', ')) + chalk.yellow(").\n\n"); # ).join( chalk.yellow(', ')) + chalk.yellow(").\n\n");
msg += chalk.gray(FS.readFileSync( msg += M2C(FS.readFileSync(
PATH.resolve(__dirname, '../cli/use.txt'), 'utf8' )) PATH.resolve(__dirname, 'help/use.txt'), 'utf8' ), 'white', 'yellow')
when HMSTATUS.invalidCommand when HMSTATUS.invalidCommand
msg = printf( M2C( this.msgs.invalidCommand.msg, 'yellow'), ex.attempted ) msg = printf( M2C( this.msgs.invalidCommand.msg, 'yellow'), ex.attempted )
@ -257,6 +259,9 @@ assembleError = ( ex ) ->
msg = printf M2C( @msgs.themeHelperLoad.msg ), ex.glob msg = printf M2C( @msgs.themeHelperLoad.msg ), ex.glob
etype = 'error' etype = 'error'
when HMSTATUS.invalidSchemaVersion
msg = printf M2C( @msgs.invalidSchemaVersion.msg ), ex.data
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

25
src/cli/help/analyze.txt Normal file
View File

@ -0,0 +1,25 @@
**analyze** | Analyze a resume for statistical insight
Usage:
**hackmyresume ANALYZE <resume>**
The ANALYZE command evaluates the specified resume(s) for
coverage, duration, gaps, keywords, and other metrics.
This command can be run against multiple resumes. Each
will be analyzed in turn.
Parameters:
**<resume>**
Path to a FRESH or JRS resume. Multiple resumes can be
specified, separated by spaces.
hackmyresume ANALYZE resume.json
hackmyresume ANALYZE r1.json r2.json r3.json
Options:
**None.**

69
src/cli/help/build.txt Normal file
View File

@ -0,0 +1,69 @@
**build** | Generate themed resumes in multiple formats
Usage:
**hackmyresume BUILD <resume> TO <target> [--theme]**
**[--pdf] [--no-escape] [--private]**
The BUILD command generates themed resumes and CVs in
multiple formats. Use it to create outbound resumes in
specific formats such HTML, MS Word, and PDF.
Parameters:
**<resume>**
Path to a FRESH or JRS resume (*.json) containing your
resume data. Multiple resumes may be specified.
If multiple resumes are specified, they will be merged
into a single resume prior to transformation.
**<target>**
Path to the desired output resume. Multiple resumes
may be specified. The file extension will determine
the format.
.all Generate all supported formats
.html HTML 5
.doc MS Word
.pdf Adobe Acrobat PDF
.txt plain text
.md Markdown
.png PNG Image
.latex LaTeX
Note: not all formats are supported by all themes!
Check the theme's documentation for details or use
the .all extension to build all available formats.
Options:
**--theme -t <theme-name-or-path>**
Path to a FRESH or JSON Resume theme OR the name of a
built-in theme. Valid theme names are 'modern',
'positive', 'compact', 'awesome', and 'basis'.
**--pdf -p <engine>**
Specify the PDF engine to use. Legal values are
'none', 'wkhtmltopdf', 'phantom', or 'weasyprint'.
**--no-escape**
Disable escaping / encoding of resume data during
resume generation. Handlebars themes only.
**--private**
Include resume fields marked as private.
Notes:
The BUILD command can be run against multiple source as well
as multiple target resumes. If multiple source resumes are
provided, they will be merged into a single source resume
before generation. If multiple output resumes are provided,
each will be generated in turn.

33
src/cli/help/convert.txt Normal file
View File

@ -0,0 +1,33 @@
**convert** | Convert resumes between FRESH and JRS formats
Usage:
**hackmyresume CONVERT <resume> TO <target> [--format]**
The CONVERT command converts one or more resume documents
between the FRESH Resume Schema and JSON Resume formats.
Parameters:
**<resume>**
Path to a FRESH or JRS resume. Multiple resumes can be
specified.
**<targets>**
The path of the converted resume. Multiple resumes can
be specified, one per provided input resume.
Options:
**--format -f <fmt>**
The desired format for the new resume(s). Valid values
are 'FRESH', 'JRS', or, to target the latest edge
version of the JSON Resume Schema, 'JRS@1'.
If this parameter is omitted, the destination format
will be inferred from the source resume's format. If
the source format is FRESH, the destination format
will be JSON Resume, and vice-versa.

23
src/cli/help/help.txt Normal file
View File

@ -0,0 +1,23 @@
**help** | View help on a specific HackMyResume command
Usage:
**hackmyresume HELP [<command>]**
The HELP command displays help information for a specific
HackMyResume command, including the HELP command itself.
Parameters:
**<command>**
The HackMyResume command to view help information for.
Must be BUILD, NEW, CONVERT, ANALYZE, VALIDATE, PEEK,
or HELP.
hackmyresume help convert
hackmyresume help help
Options:
**None.**

29
src/cli/help/new.txt Normal file
View File

@ -0,0 +1,29 @@
**new** | Create a new FRESH or JRS resume document
Usage:
**hackmyresume NEW <fileName> [--format]**
The NEW command generates a new resume document in FRESH
or JSON Resume format. This document can serve as an
official source of truth for your resume and career data
as well an input to tools like HackMyResume.
Parameters:
**<fileName>**
The filename (relative or absolute path) of the resume
to be created. Multiple resume paths can be specified,
and each will be created in turn.
hackmyresume NEW resume.json
hackmyresume NEW r1.json foo/r2.json ../r3.json
Options:
**--format -f <fmt>**
The desired format for the new resume(s). Valid values
are 'FRESH', 'JRS', or, to target the latest edge
version of the JSON Resume Schema, 'JRS@1'.

31
src/cli/help/peek.txt Normal file
View File

@ -0,0 +1,31 @@
**peek** | View portions of a resume from the command line
Usage:
**hackmyresume PEEK <resume> <at>**
The PEEK command displays a specific piece or part of the
resume without requiring the resume to be opened in an
editor.
Parameters:
**<resume>**
Path to a FRESH or JRS resume. Multiple resumes can be
specified, separated by spaces.
hackmyresume PEEK r1.json r2.json r3.json "employment.history[2]"
**<at>**
The resume property or field to be displayed. Can be
any valid resume path, for example:
education[0]
info.name
employment.history[3].start
Options:
**None.**

70
src/cli/help/use.txt Normal file
View File

@ -0,0 +1,70 @@
**HackMyResume** | A Swiss Army knife for resumes and CVs
Usage:
**hackmyresume [--version] [--help] [--silent] [--debug]**
**[--options] [--no-colors] <command> [<args>]**
Commands: (type "hackmyresume help COMMAND" for details)
**BUILD** Build your resume to the destination format(s).
**ANALYZE** Analyze your resume for keywords, gaps, and metrics.
**VALIDATE** Validate your resume for errors and typos.
**NEW** Create a new resume in FRESH or JSON Resume format.
**CONVERT** Convert your resume between FRESH and JSON Resume.
**PEEK** View a specific field or element on your resume.
**HELP** View help on a specific HackMyResume command.
Common Tasks:
Generate a resume in a specific format (HTML, Word, PDF, etc.)
**hackmyresume build rez.json to out/rez.html**
**hackmyresume build rez.json to out/rez.doc**
**hackmyresume build rez.json to out/rez.pdf**
**hackmyresume build rez.json to out/rez.txt**
**hackmyresume build rez.json to out/rez.md**
**hackmyresume build rez.json to out/rez.png**
**hackmyresume build rez.json to out/rez.tex**
Build a resume to ALL available formats:
**hackmyresume build rez.json to out/rez.all**
Build a resume with a specific theme:
**hackmyresume build rez.json to out/rez.all -t themeName**
Create a new empty resume:
**hackmyresume new rez.json**
Convert a resume between FRESH and JRS formats:
**hackmyresume convert rez.json converted.json**
Analyze a resume for important metrics
**hackmyresume analyze rez.json**
Find more resume themes:
**https://www.npmjs.com/search?q=jsonresume-theme**
**https://www.npmjs.com/search?q=fresh-theme**
**https://github.com/fresh-standard/fresh-themes**
Validate a resume's structure and syntax:
**hackmyresume validate resume.json**
View help on a specific command:
**hackmyresume help [build|convert|new|analyze|validate|peek|help]**
Submit a bug or request:
**https://githut.com/hacksalot/HackMyResume/issues**
HackMyResume is free and open source software published
under the MIT license. For more information, visit the
HackMyResume website or GitHub project page.

26
src/cli/help/validate.txt Normal file
View File

@ -0,0 +1,26 @@
**validate** | Validate a resume for correctness
Usage:
**hackmyresume VALIDATE <resume> [--assert]**
The VALIDATE command validates a FRESH or JRS document
against its governing schema, verifying that the resume
is correctly structured and formatted.
Parameters:
**<resume>**
Path to a FRESH or JRS resume. Multiple resumes can be
specified.
hackmyresume ANALYZE resume.json
hackmyresume ANALYZE r1.json r2.json r3.json
Options:
**--assert -a**
Tell HackMyResume to return a non-zero process exit
code if a resume fails to validate.

View File

@ -84,6 +84,7 @@ main = module.exports = ( rawArgs, exitCallback ) ->
program program
.command('convert') .command('convert')
.description('Convert a resume to/from FRESH or JSON RESUME format.') .description('Convert a resume to/from FRESH or JSON RESUME format.')
.option('-f --format <fmt>', 'FRESH or JRS format and optional version', undefined)
.action(-> .action(->
x = splitSrcDest.call( this ); x = splitSrcDest.call( this );
execute.call( this, x.src, x.dst, this.opts(), logMsg) execute.call( this, x.src, x.dst, this.opts(), logMsg)
@ -131,6 +132,19 @@ main = module.exports = ( rawArgs, exitCallback ) ->
return return
) )
# Create the HELP command
program
.command('help')
.arguments('[command]')
.description('Get help on a HackMyResume command')
.action ( cmd ) ->
cmd = cmd || 'use'
manPage = FS.readFileSync(
PATH.join(__dirname, 'help/' + cmd + '.txt'),
'utf8')
_out.log M2C(manPage, 'white', 'yellow.bold')
return
program.parse( args ) program.parse( args )
if !program.args.length if !program.args.length
@ -170,24 +184,23 @@ initialize = ( ar, exitCallback ) ->
_err.init o.debug, o.assert, o.silent _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 ] && o.verb != 'help'
_err.err fluenterror: HMSTATUS.invalidCommand, quit: true, attempted: o.orgVerb, true _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) ->
_err.err if this.name() != 'help'
fluenterror: _err.err
if this.name() != 'new' verb: @name()
then HMSTATUS.resumeNotFound fluenterror: HMSTATUS.resumeNotFound
else HMSTATUS.createNameMissing , true
, true
return return
# Override the .helpInformation behavior # Override the .helpInformation behavior
Command.prototype.helpInformation = -> Command.prototype.helpInformation = ->
manPage = FS.readFileSync( manPage = FS.readFileSync(
PATH.join(__dirname, 'use.txt'), 'utf8' ) PATH.join(__dirname, 'help/use.txt'), 'utf8' )
return chalk.green.bold(manPage) return M2C(manPage, 'white', 'yellow')
return { return {
args: o.args, args: o.args,
@ -288,7 +301,7 @@ executeSuccess = (obj) ->
### Failure handler for verb invocations. Calls process.exit by default ### ### Failure handler for verb invocations. Calls process.exit by default ###
executeFail = (err) -> executeFail = (err) ->
console.dir err #console.dir err
finalErrorCode = -1 finalErrorCode = -1
if err if err
if err.fluenterror if err.fluenterror
@ -348,7 +361,8 @@ splitSrcDest = () ->
params = this.parent.args.filter((j) -> return String.is(j) ) params = this.parent.args.filter((j) -> return String.is(j) )
if params.length == 0 if params.length == 0
throw { fluenterror: HMSTATUS.resumeNotFound, quit: true } #tmpName = @name()
throw { fluenterror: HMSTATUS.resumeNotFound, verb: @name(), quit: true }
# Find the TO keyword, if any # Find the TO keyword, if any
splitAt = _.findIndex( params, (p) -> return p.toLowerCase() == 'to'; ) splitAt = _.findIndex( params, (p) -> return p.toLowerCase() == 'to'; )

View File

@ -137,3 +137,5 @@ errors:
An error occurred while attempting to load the '%s' theme helper. Is the An error occurred while attempting to load the '%s' theme helper. Is the
theme correctly installed? theme correctly installed?
dummy: dontcare dummy: dontcare
invalidSchemaVersion:
msg: "'%s' is not recognized as a valid schema version."

View File

@ -1,52 +0,0 @@
Usage:
hackmyresume <command> <sources> [TO <targets>] [<options>]
Available commands:
BUILD Build your resume to the destination format(s).
ANALYZE Analyze your resume for keywords, gaps, and metrics.
VALIDATE Validate your resume for errors and typos.
CONVERT Convert your resume between FRESH and JSON Resume.
NEW Create a new resume in FRESH or JSON Resume format.
PEEK View a specific field or element on your resume.
Available options:
--theme -t Path to a FRESH or JSON Resume theme.
--pdf -p Specify the PDF engine to use (wkhtmltopdf or phantom).
--options -o Load options from an external JSON file.
--format -f The format (FRESH or JSON Resume) to use.
--debug -d Emit extended debugging info.
--assert -a Treat resume validation warnings as errors.
--private Include resume fields marked as private
--no-colors Disable terminal colors.
--tips Display theme messages and tips.
--help -h Display help documentation.
--version -v Display the current version.
Not all options are supported for all commands. For example, the
--theme option is only supported for the BUILD command.
Examples:
hackmyresume BUILD resume.json TO out/resume.all --theme modern
hackmyresume ANALYZE resume.json
hackmyresume NEW my-new-resume.json --format JRS
hackmyresume CONVERT resume-fresh.json TO resume-jrs.json
hackmyresume VALIDATE resume.json
hackmyresume PEEK resume.json employment[2].summary
Tips:
- You can specify multiple sources and/or targets for all commands.
- You can use any FRESH or JSON Resume theme with HackMyResume.
- Specify a file extension of .all to generate your resume to all
available formats supported by the theme. (BUILD command.)
- The --theme parameter can specify either the name of a preinstalled
theme, or the path to a local FRESH or JSON Resume theme.
- Visit https://www.npmjs.com/search?q=jsonresume-theme for a full
listing of all available JSON Resume themes.
- Visit https://github.com/fluentdesk/fresh-themes for a complete
listing of all available FRESH themes.
- Report bugs to https://githut.com/hacksalot/HackMyResume/issues.

View File

@ -1,98 +0,0 @@
###*
Definition of the AbstractResume class.
@license MIT. See LICENSE.md for details.
@module core/abstract-resume
###
_ = require 'underscore'
__ = require 'lodash'
FluentDate = require('./fluent-date')
class AbstractResume
###*
Compute the total duration of the work history.
@returns The total duration of the sheet's work history, that is, the number
of years between the start date of the earliest job on the resume and the
*latest end date of all jobs in the work history*. This last condition is for
sheets that have overlapping jobs.
###
duration: (collKey, startKey, endKey, unit) ->
unit = unit || 'years'
hist = __.get @, collKey
return 0 if !hist or !hist.length
# BEGIN CODE DUPLICATION --> src/inspectors/gap-inspector.coffee (TODO)
# Convert the candidate's employment history to an array of dates,
# where each element in the array is a start date or an end date of a
# job -- it doesn't matter which.
new_e = hist.map ( job ) ->
obj = _.pick( job, [startKey, endKey] )
# Synthesize an end date if this is a "current" gig
obj[endKey] = 'current' if !_.has obj, endKey
if obj && (obj[startKey] || obj[endKey])
obj = _.pairs obj
obj[0][1] = FluentDate.fmt( obj[0][1] )
if obj.length > 1
obj[1][1] = FluentDate.fmt( obj[1][1] )
obj
# Flatten the array, remove empties, and sort
new_e = _.filter _.flatten( new_e, true ), (v) ->
return v && v.length && v[0] && v[0].length
return 0 if !new_e or !new_e.length
new_e = _.sortBy new_e, ( elem ) -> return elem[1].unix()
# END CODE DUPLICATION
firstDate = _.first( new_e )[1];
lastDate = _.last( new_e )[1];
lastDate.diff firstDate, unit
###*
Removes ignored or private fields from a resume object
@returns an object with the following structure:
{
scrubbed: the processed resume object
ignoreList: an array of ignored nodes that were removed
privateList: an array of private nodes that were removed
}
###
scrubResume: (rep, opts) ->
traverse = require 'traverse'
ignoreList = []
privateList = []
includePrivates = opts && opts.private
scrubbed = traverse( rep ).map () -> # [^1]
if !@isLeaf
if @node.ignore == true || @node.ignore == 'true'
ignoreList.push @node
@delete()
else if (@node.private == true || @node.private == 'true') && !includePrivates
privateList.push @node
@delete()
if _.isArray(@node) # [^2]
@after () ->
@update _.compact this.node
return
return
scrubbed: scrubbed
ingoreList: ignoreList
privateList: privateList
module.exports = AbstractResume
# [^1]: As of v0.6.6, the NPM traverse library has a quirk when attempting
# to remove array elements directly using traverse's `this.remove`. See:
#
# https://github.com/substack/js-traverse/issues/48
#
# [^2]: The workaround is to use traverse's 'this.delete' to nullify the value
# first, followed by removal with something like _.compact.
#
# https://github.com/substack/js-traverse/issues/48#issuecomment-142607200
#

View File

@ -18,7 +18,6 @@ MD = require 'marked'
CONVERTER = require 'fresh-jrs-converter' CONVERTER = require 'fresh-jrs-converter'
JRSResume = require './jrs-resume' JRSResume = require './jrs-resume'
FluentDate = require './fluent-date' FluentDate = require './fluent-date'
AbstractResume = require './abstract-resume'
@ -27,7 +26,7 @@ A FRESH resume or CV. FRESH resumes are backed by JSON, and each FreshResume
object is an instantiation of that JSON decorated with utility methods. object is an instantiation of that JSON decorated with utility methods.
@constructor @constructor
### ###
class FreshResume extends AbstractResume class FreshResume# extends AbstractResume
@ -55,7 +54,8 @@ class FreshResume extends AbstractResume
if opts and opts.privatize if opts and opts.privatize
# Ignore any element with the 'ignore: true' or 'private: true' designator. # Ignore any element with the 'ignore: true' or 'private: true' designator.
{ scrubbed, ignoreList, privateList } = @scrubResume rep, opts scrubber = require '../utils/resume-scrubber'
{ scrubbed, ignoreList, privateList } = scrubber.scrubResume rep, opts
# Now apply the resume representation onto this object # Now apply the resume representation onto this object
extend true, @, if opts and opts.privatize then scrubbed else rep extend true, @, if opts and opts.privatize then scrubbed else rep
@ -95,12 +95,27 @@ class FreshResume extends AbstractResume
Save the sheet to disk in a specific format, either FRESH or JSON Resume. Save the sheet to disk in a specific format, either FRESH or JSON Resume.
### ###
saveAs: ( filename, format ) -> saveAs: ( filename, format ) ->
if format != 'JRS'
# If format isn't specified, default to FRESH
safeFormat = (format && format.trim()) || 'FRESH'
# Validate against the FRESH version regex
# freshVersionReg = require '../utils/fresh-version-regex'
# if (not freshVersionReg().test( safeFormat ))
# throw badVer: safeFormat
parts = safeFormat.split '@'
if parts[0] == 'FRESH'
@imp.file = filename || @imp.file @imp.file = filename || @imp.file
FS.writeFileSync @imp.file, @stringify(), 'utf8' FS.writeFileSync @imp.file, @stringify(), 'utf8'
else
newRep = CONVERTER.toJRS this else if parts[0] == 'JRS'
useEdgeSchema = if parts.length > 1 then parts[1] == '1' else false
newRep = CONVERTER.toJRS @, edge: useEdgeSchema
FS.writeFileSync filename, JRSResume.stringify( newRep ), 'utf8' FS.writeFileSync filename, JRSResume.stringify( newRep ), 'utf8'
else
throw badVer: safeFormat
@ @
@ -180,7 +195,24 @@ class FreshResume extends AbstractResume
###* Return a unique list of all keywords across all skills. ### ###*
Return a unique list of all skills declared in the resume.
###
# TODO: Several problems here:
# 1) Confusing name. Easily confused with the keyword-inspector module, which
# parses resume body text looking for these same keywords. This should probably
# be renamed.
#
# 2) Doesn't bother trying to integrate skills.list with skills.sets if they
# happen to declare different skills, and if skills.sets declares ONE skill and
# skills.list declared 50, only 1 skill will be registered.
#
# 3) In the future, skill.sets should only be able to use skills declared in
# skills.list. That is, skills.list is the official record of a candidate's
# declared skills. skills.sets is just a way of grouping those into skillsets
# for easier consumption.
keywords: () -> keywords: () ->
flatSkills = [] flatSkills = []
if @skills if @skills
@ -299,7 +331,9 @@ class FreshResume extends AbstractResume
duration: (unit) -> duration: (unit) ->
super('employment.history', 'start', 'end', unit) inspector = require '../inspectors/duration-inspector'
inspector.run @, 'employment.history', 'start', 'end', unit

View File

@ -59,9 +59,23 @@ class FRESHTheme
if @inherits if @inherits
cached = { } cached = { }
_.each @inherits, (th, key) -> _.each @inherits, (th, key) ->
themesFolder = require.resolve 'fresh-themes' # First, see if this is one of the predefined FRESH themes. There are
d = parsePath( themeFolder ).dirname # only a handful of these, but they may change over time, so we need to
themePath = PATH.join d, th # query the official source of truth: the fresh-themes repository, which
# mounts the themes conveniently by name to the module object, and which
# is embedded locally inside the HackMyResume installation.
# TODO: merge this code with
themesObj = require 'fresh-themes'
if _.has themesObj.themes, th
themePath = PATH.join(
parsePath( require.resolve('fresh-themes') ).dirname,
'/themes/',
th
)
else
d = parsePath( th ).dirname
themePath = PATH.join d, th
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 )

View File

@ -14,7 +14,6 @@ PATH = require('path')
MD = require('marked') MD = require('marked')
CONVERTER = require('fresh-jrs-converter') CONVERTER = require('fresh-jrs-converter')
moment = require('moment') moment = require('moment')
AbstractResume = require('./abstract-resume')
###* ###*
@ -22,7 +21,7 @@ A JRS resume or CV. JRS resumes are backed by JSON, and each JRSResume object
is an instantiation of that JSON decorated with utility methods. is an instantiation of that JSON decorated with utility methods.
@class JRSResume @class JRSResume
### ###
class JRSResume extends AbstractResume class JRSResume# extends AbstractResume
@ -49,8 +48,9 @@ class JRSResume extends AbstractResume
parseJSON: ( rep, opts ) -> parseJSON: ( rep, opts ) ->
opts = opts || { }; opts = opts || { };
if opts.privatize if opts.privatize
scrubber = require '../utils/resume-scrubber'
# Ignore any element with the 'ignore: true' or 'private: true' designator. # Ignore any element with the 'ignore: true' or 'private: true' designator.
{ scrubbed, ignoreList, privateList } = @scrubResume rep, opts { scrubbed, ignoreList, privateList } = scrubber.scrubResume rep, opts
# Extend resume properties onto ourself. # Extend resume properties onto ourself.
extend true, this, if opts.privatize then scrubbed else rep extend true, this, if opts.privatize then scrubbed else rep
@ -188,7 +188,8 @@ class JRSResume extends AbstractResume
duration: (unit) -> duration: (unit) ->
super('work', 'startDate', 'endDate', unit) inspector = require '../inspectors/duration-inspector';
inspector.run @, 'work', 'startDate', 'endDate', unit
@ -232,7 +233,6 @@ class JRSResume extends AbstractResume
### ###
harden: () -> harden: () ->
that = @
ret = @dupe() ret = @dupe()
HD = (txt) -> '@@@@~' + txt + '~@@@@' HD = (txt) -> '@@@@~' + txt + '~@@@@'
@ -241,38 +241,12 @@ class JRSResume extends AbstractResume
#return MD(txt || '' ).replace(/^\s*<p>|<\/p>\s*$/gi, ''); #return MD(txt || '' ).replace(/^\s*<p>|<\/p>\s*$/gi, '');
return HD txt return HD txt
# TODO: refactor recursion transformer = require '../utils/string-transformer'
hardenStringsInObject = ( obj, inline ) -> transformer ret,
[ 'skills','url','website','startDate','endDate', 'releaseDate', 'date',
return if !obj 'phone','email','address','postalCode','city','country','region',
inline = inline == undefined || inline 'safeStartDate','safeEndDate' ],
(key, val) -> HD val
if Object.prototype.toString.call( obj ) == '[object Array]'
obj.forEach (elem, idx, ar) ->
if typeof elem == 'string' || elem instanceof String
ar[idx] = if inline then HDIN(elem) else HD( elem )
else
hardenStringsInObject elem
else if typeof obj == 'object'
Object.keys( obj ).forEach (key) ->
sub = obj[key]
if typeof sub == 'string' || sub instanceof String
if _.contains(['skills','url','website','startDate','endDate',
'releaseDate','date','phone','email','address','postalCode',
'city','country','region'], key)
return
if key == 'summary'
obj[key] = HD( obj[key] )
else
obj[key] = if inline then HDIN( obj[key] ) else HD( obj[key] )
else
hardenStringsInObject sub
Object.keys( ret ).forEach (member) ->
hardenStringsInObject ret[ member ]
ret

View File

@ -104,9 +104,9 @@ _parse = ( fileName, opts, eve ) ->
eve && eve.stat HME.afterParse, { file: fileName, data: ret.json, fmt: orgFormat } eve && eve.stat HME.afterParse, { file: fileName, data: ret.json, fmt: orgFormat }
return ret return ret
catch catch err
# Can be ENOENT, EACCES, SyntaxError, etc. # Can be ENOENT, EACCES, SyntaxError, etc.
fluenterror: if rawData then HMS.parseError else HMS.readError fluenterror: if rawData then HMS.parseError else HMS.readError
inner: _error inner: err
raw: rawData raw: rawData
file: fileName file: fileName

View File

@ -37,3 +37,4 @@ module.exports =
optionsFileNotFound: 28 optionsFileNotFound: 28
unknownSchema: 29 unknownSchema: 29
themeHelperLoad: 30 themeHelperLoad: 30
invalidSchemaVersion: 31

View File

@ -122,14 +122,14 @@ module.exports = class TemplateGenerator extends BaseGenerator
return return
# Write the file # Write the file
opts.beforeWrite? thisFilePath opts.beforeWrite? data: thisFilePath
MKDIRP.sync PATH.dirname( thisFilePath ) MKDIRP.sync PATH.dirname( thisFilePath )
if file.info.action != 'copy' if file.info.action != 'copy'
FS.writeFileSync thisFilePath, file.data, encoding: 'utf8', flags: 'w' FS.writeFileSync thisFilePath, file.data, encoding: 'utf8', flags: 'w'
else else
FS.copySync file.info.path, thisFilePath FS.copySync file.info.path, thisFilePath
opts.afterWrite? thisFilePath opts.afterWrite? data: thisFilePath
# Post-processing # Post-processing
if @onAfterSave if @onAfterSave
@ -174,9 +174,9 @@ createSymLinks = ( curFmt, outFolder ) ->
try try
FS.symlinkSync absTarg, absLoc, type FS.symlinkSync absTarg, absLoc, type
catch catch err
succeeded = false succeeded = false
if _error.code == 'EEXIST' if err.code == 'EEXIST'
FS.unlinkSync absLoc FS.unlinkSync absLoc
try try
FS.symlinkSync absTarg, absLoc, type FS.symlinkSync absTarg, absLoc, type

View File

@ -38,6 +38,14 @@ BlockHelpers = module.exports =
ifHasSkill: ( rez, skill, options ) ->
skUp = skill.toUpperCase()
ret = _.some rez.skills.list, (sk) ->
(skUp.toUpperCase() == sk.name.toUpperCase()) and sk.years
, @
options.fn @ if ret
###* ###*
Emit the enclosed content if the resume has the named Emit the enclosed content if the resume has the named
property or subproperty. property or subproperty.
@ -55,4 +63,4 @@ BlockHelpers = module.exports =
Return true if either value is truthy. Return true if either value is truthy.
@method either @method either
### ###
either: ( lhs, rhs, options ) -> options.fn @ if lhs || rhs either: ( lhs, rhs, options ) -> options.fn @ if lhs || rhs

View File

@ -329,6 +329,7 @@ GenericHelpers = module.exports =
# If not provided by the user, stitle should default to sname. ps. # If not provided by the user, stitle should default to sname. ps.
# Handlebars silently passes in the options object to the last param, # Handlebars silently passes in the options object to the last param,
# where in Underscore stitle will be null/undefined, so we check both. # where in Underscore stitle will be null/undefined, so we check both.
# TODO: not actually sure that's true, given that we _.wrap these functions
stitle = (stitle && String.is(stitle) && stitle) || sname stitle = (stitle && String.is(stitle) && stitle) || sname
# If there's a section title override, use it. # If there's a section title override, use it.
@ -342,7 +343,7 @@ GenericHelpers = module.exports =
wpml: ( txt, inline ) -> wpml: ( txt, inline ) ->
return '' if !txt return '' if !txt
inline = (inline && !inline.hash) || false inline = (inline && !inline.hash) || false
txt = XML(txt.trim()) txt = XML txt.trim()
txt = if inline then MD(txt).replace(/^\s*<p>|<\/p>\s*$/gi, '') else MD(txt) txt = if inline then MD(txt).replace(/^\s*<p>|<\/p>\s*$/gi, '') else MD(txt)
txt = H2W( txt ) txt = H2W( txt )
return txt return txt
@ -358,6 +359,15 @@ GenericHelpers = module.exports =
###*
Emit a conditional Markdown link.
@method link
###
linkMD: ( text, url ) ->
return if url && url.trim() then ('[' + text + '](' + url + ')') else text
###* ###*
Return the last word of the specified text. Return the last word of the specified text.
@method lastWord @method lastWord
@ -376,7 +386,7 @@ GenericHelpers = module.exports =
'#FFFFAA'). '#FFFFAA').
### ###
skillColor: ( lvl ) -> skillColor: ( lvl ) ->
idx = skillLevelToIndex lvl idx = _skillLevelToIndex lvl
skillColors = (this.theme && this.theme.palette && skillColors = (this.theme && this.theme.palette &&
this.theme.palette.skillLevels) || this.theme.palette.skillLevels) ||
[ '#FFFFFF', '#5CB85C', '#F1C40F', '#428BCA', '#C00000' ] [ '#FFFFFF', '#5CB85C', '#F1C40F', '#428BCA', '#C00000' ]
@ -389,7 +399,7 @@ GenericHelpers = module.exports =
@method lastWord @method lastWord
### ###
skillHeight: ( lvl ) -> skillHeight: ( lvl ) ->
idx = skillLevelToIndex lvl idx = _skillLevelToIndex lvl
['38.25', '30', '16', '8', '0'][idx] ['38.25', '30', '16', '8', '0'][idx]
@ -491,6 +501,9 @@ GenericHelpers = module.exports =
###*
Emit padded text.
###
pad: (stringOrArray, padAmount, unused ) -> pad: (stringOrArray, padAmount, unused ) ->
stringOrArray = stringOrArray || '' stringOrArray = stringOrArray || ''
padAmount = padAmount || 0 padAmount = padAmount || 0
@ -506,6 +519,25 @@ GenericHelpers = module.exports =
###*
Given the name of a skill ("JavaScript" or "HVAC repair"), return the number
of years assigned to that skill in the resume.skills.list collection.
###
skillYears: ( skill, rez ) ->
sk = _.find rez.skills.list, (sk) -> sk.name.toUpperCase() == skill.toUpperCase()
if sk then sk.years else '?'
###*
Given an object that may be a string or an object, return it as-is if it's a
string, otherwise return the value at obj[objPath].
###
stringOrObject: ( obj, objPath, rez ) ->
if _.isString obj then obj else LO.get obj, objPath
###* ###*
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.
@ -562,7 +594,7 @@ _fromTo = ( dateA, dateB, fmt, sep, fallback ) ->
skillLevelToIndex = ( lvl ) -> _skillLevelToIndex = ( lvl ) ->
idx = 0 idx = 0
if String.is( lvl ) if String.is( lvl )
lvl = lvl.trim().toLowerCase() lvl = lvl.trim().toLowerCase()

View File

@ -17,23 +17,28 @@ Register useful Handlebars helpers.
@method registerHelpers @method registerHelpers
### ###
module.exports = ( theme, opts ) -> module.exports = ( theme, rez, opts ) ->
helpers.theme = theme helpers.theme = theme
helpers.opts = opts helpers.opts = opts
helpers.type = 'handlebars' helpers.type = 'handlebars'
# Prepare generic helpers for use with Handlebars. We do this by wrapping them
# in a Handlebars-aware wrapper which calls the helper internally.
wrappedHelpers = _.mapObject helpers, ( hVal, hKey ) -> wrappedHelpers = _.mapObject helpers, ( hVal, hKey ) ->
if _.isFunction hVal if _.isFunction hVal
_.wrap hVal, (func) -> return _.wrap hVal, (func) ->
args = Array.prototype.slice.call arguments args = Array.prototype.slice.call arguments
args.shift() # lose the 1st element (func) args.shift() # lose the 1st element (func) [^1]
args.pop() # lose the last element (the Handlebars options hash) #args.pop() # lose the last element (HB options hash)
func.apply @, args args[ args.length - 1 ] = rez # replace w/ resume object
func.apply @, args # call the generic helper
hVal hVal
, @ , @
HANDLEBARS.registerHelper wrappedHelpers HANDLEBARS.registerHelper wrappedHelpers
# Prepare Handlebars-specific helpers - "blockHelpers" is really a misnomer
# since any kind of Handlebars-specific helper can live here
HANDLEBARS.registerHelper blockHelpers HANDLEBARS.registerHelper blockHelpers
# Register any theme-provided custom helpers... # Register any theme-provided custom helpers...
@ -64,3 +69,9 @@ module.exports = ( theme, opts ) ->
inner: ex inner: ex
glob: curGlob, exit: true glob: curGlob, exit: true
return return
# [^1]: This little bit of acrobatics ensures that our generic helpers are
# called as generic helpers, not as Handlebars-specific helpers. This allows
# them to be used in other templating engines, like Underscore. If you need a
# Handlebars-specific helper with normal Handlebars context and options, put it
# in block-helpers.coffee.

View File

@ -0,0 +1,45 @@
FluentDate = require '../core/fluent-date'
_ = require 'underscore'
lo = require 'lodash'
module.exports =
###*
Compute the total duration of the work history.
@returns The total duration of the sheet's work history, that is, the number
of years between the start date of the earliest job on the resume and the
*latest end date of all jobs in the work history*. This last condition is for
sheets that have overlapping jobs.
###
run: (rez, collKey, startKey, endKey, unit) ->
unit = unit || 'years'
hist = lo.get rez, collKey
return 0 if !hist or !hist.length
# BEGIN CODE DUPLICATION --> src/inspectors/gap-inspector.coffee (TODO)
# Convert the candidate's employment history to an array of dates,
# where each element in the array is a start date or an end date of a
# job -- it doesn't matter which.
new_e = hist.map ( job ) ->
obj = _.pick( job, [startKey, endKey] )
# Synthesize an end date if this is a "current" gig
obj[endKey] = 'current' if !_.has obj, endKey
if obj && (obj[startKey] || obj[endKey])
obj = _.pairs obj
obj[0][1] = FluentDate.fmt( obj[0][1] )
if obj.length > 1
obj[1][1] = FluentDate.fmt( obj[1][1] )
obj
# Flatten the array, remove empties, and sort
new_e = _.filter _.flatten( new_e, true ), (v) ->
return v && v.length && v[0] && v[0].length
return 0 if !new_e or !new_e.length
new_e = _.sortBy new_e, ( elem ) -> return elem[1].unix()
# END CODE DUPLICATION
firstDate = _.first( new_e )[1];
lastDate = _.last( new_e )[1];
lastDate.diff firstDate, unit

View File

@ -30,22 +30,20 @@ HandlebarsGenerator = module.exports =
try try
# Compile and run the Handlebars template. # Compile and run the Handlebars template.
template = HANDLEBARS.compile tpl, template = HANDLEBARS.compile tpl,
strict: false, assumeObjects: false, noEscape: data.opts.noescape || false strict: false
assumeObjects: false
noEscape: data.opts.noescape
return template data return template data
catch catch err
throw throw
fluenterror: fluenterror:
HMSTATUS[ if template then 'invokeTemplate' else 'compileTemplate' ] HMSTATUS[ if template then 'invokeTemplate' else 'compileTemplate' ]
inner: _error inner: err
generate: ( json, jst, format, curFmt, opts, theme ) -> generate: ( json, jst, format, curFmt, opts, theme ) ->
# Set up partials and helpers
registerPartials format, theme
registerHelpers theme, opts
# Preprocess text # Preprocess text
encData = json encData = json
if format == 'html' || format == 'pdf' if format == 'html' || format == 'pdf'
@ -53,6 +51,10 @@ HandlebarsGenerator = module.exports =
if( format == 'doc' ) if( format == 'doc' )
encData = json.xmlify() encData = json.xmlify()
# Set up partials and helpers
registerPartials format, theme
registerHelpers theme, encData, opts
# Set up the context # Set up the context
ctx = ctx =
r: encData r: encData

View File

@ -37,8 +37,8 @@ JRSGenerator = module.exports =
turnoff.forEach (c, idx) -> console[c] = org[idx] turnoff.forEach (c, idx) -> console[c] = org[idx]
# Unfreeze and apply Markdown # Unfreeze and apply Markdown
rezHtml = rezHtml.replace /@@@@~.*?~@@@@/gm, (val) -> rezHtml = rezHtml.replace /@@@@~[\s\S]*?~@@@@/g, (val) ->
MDIN( val.replace( /~@@@@/gm,'' ).replace( /@@@@~/gm,'' ) ) MDIN( val.replace( /~@@@@/g,'' ).replace( /@@@@~/g,'' ) )
MDIN = (txt) -> # TODO: Move this MDIN = (txt) -> # TODO: Move this

View File

@ -24,12 +24,12 @@ UnderscoreGenerator = module.exports =
# Compile and run the Handlebars template. # Compile and run the Handlebars template.
t = _.template tpl t = _.template tpl
t data t data
catch catch err
#console.dir _error #console.dir _error
HMS = require '../core/status-codes' HMS = require '../core/status-codes'
throw throw
fluenterror: HMS[if t then 'invokeTemplate' else 'compileTemplate'] fluenterror: HMS[if t then 'invokeTemplate' else 'compileTemplate']
inner: _error inner: err

View File

@ -0,0 +1,23 @@
###*
Defines a regex suitable for matching FRESH versions.
@module file-contains.js
###
# Set up a regex that matches all of the following:
#
# - FRESH
# - JRS
# - FRESCA
# - FRESH@1.0.0
# - FRESH@1.0
# - FRESH@1
# - JRS@0.16.0
# - JRS@0.16
# - JRS@0
#
# Don't use a SEMVER regex (eg, NPM's semver-regex) because a) we want to
# support partial semvers like "0" or "1.2" and b) we'll expand this later to
# support fully scoped FRESH versions.
module.exports = () ->
RegExp '^(FRESH|FRESCA|JRS)(?:@(\\d+(?:\\.\\d+)?(?:\\.\\d+)?))?$'

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