chore: decaffeinate: convert error.coffee and 58 other files to JS

This commit is contained in:
decaffeinate 2018-02-13 20:43:42 -05:00 committed by hacksalot
parent b7cd01597e
commit 8a46d642e5
No known key found for this signature in database
GPG Key ID: 2F343EC247CA4B06
59 changed files with 4568 additions and 3676 deletions

View File

@ -1,268 +1,328 @@
###* /*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
/**
Error-handling routines for HackMyResume. Error-handling routines for HackMyResume.
@module cli/error @module cli/error
@license MIT. See LICENSE.md for details. @license MIT. See LICENSE.md for details.
### */
HMSTATUS = require '../core/status-codes' const HMSTATUS = require('../core/status-codes');
FS = require 'fs' const FS = require('fs');
PATH = require 'path' const PATH = require('path');
WRAP = require 'word-wrap' const WRAP = require('word-wrap');
M2C = require '../utils/md2chalk' const M2C = require('../utils/md2chalk');
chalk = require 'chalk' const chalk = require('chalk');
extend = require 'extend' const extend = require('extend');
printf = require 'printf' const printf = require('printf');
SyntaxErrorEx = require '../utils/syntax-error-ex' const SyntaxErrorEx = require('../utils/syntax-error-ex');
require 'string.prototype.startswith' require('string.prototype.startswith');
###* Error handler for HackMyResume. All errors are handled here. /** Error handler for HackMyResume. All errors are handled here.
@class ErrorHandler ### @class ErrorHandler */
module.exports = module.exports = {
init: ( debug, assert, silent ) -> init( debug, assert, silent ) {
@debug = debug this.debug = debug;
@assert = assert this.assert = assert;
@silent = silent this.silent = silent;
@msgs = require('./msg').errors this.msgs = require('./msg').errors;
@ return this;
},
err: ( ex, shouldExit ) -> err( ex, shouldExit ) {
# Short-circuit logging output if --silent is on // Short-circuit logging output if --silent is on
o = if @silent then () -> else _defaultLog let stack;
const o = this.silent ? function() {} : _defaultLog;
# Special case; can probably be removed. // Special case; can probably be removed.
throw ex if ex.pass if (ex.pass) { throw ex; }
# Load error messages // Load error messages
@msgs = @msgs || require('./msg').errors this.msgs = this.msgs || require('./msg').errors;
# Handle packaged HMR exceptions // Handle packaged HMR exceptions
if ex.fluenterror if (ex.fluenterror) {
# Output the error message // Output the error message
objError = assembleError.call @, ex const objError = assembleError.call(this, ex);
o( @[ 'format_' + objError.etype ]( objError.msg )) o( this[ `format_${objError.etype}` ]( objError.msg ));
# Output the stack (sometimes) // 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 ) );
}
# Quit if necessary // Quit if necessary
if shouldExit or ex.exit if (shouldExit || ex.exit) {
if @debug if (this.debug) {
o chalk.cyan('Exiting with error code ' + ex.fluenterror.toString()) o(chalk.cyan(`Exiting with error code ${ex.fluenterror.toString()}`));
if @assert }
ex.pass = true if (this.assert) {
throw ex ex.pass = true;
process.exit ex.fluenterror throw ex;
}
return process.exit(ex.fluenterror);
}
# Handle raw exceptions // Handle raw exceptions
else } else {
o ex o(ex);
stackTrace = ex.stack || (ex.inner && ex.inner.stack) const stackTrace = ex.stack || (ex.inner && ex.inner.stack);
if stackTrace && this.debug if (stackTrace && this.debug) {
o M2C(ex.stack || ex.inner.stack, 'gray') return o(M2C(ex.stack || ex.inner.stack, 'gray'));
}
}
},
format_error: ( msg ) -> format_error( msg ) {
msg = msg || '' msg = msg || '';
chalk.red.bold( if msg.toUpperCase().startsWith('ERROR:') then msg else 'Error: ' + msg ) return chalk.red.bold( msg.toUpperCase().startsWith('ERROR:') ? msg : `Error: ${msg}` );
},
format_warning: ( brief, msg ) -> format_warning( brief, msg ) {
chalk.yellow(brief) + chalk.yellow(msg || '') return chalk.yellow(brief) + chalk.yellow(msg || '');
},
format_custom: ( msg ) -> msg format_custom( msg ) { return msg; }
};
_defaultLog = () -> console.log.apply console.log, arguments # eslint-disable-line no-console var _defaultLog = function() { return console.log.apply(console.log, arguments); }; // eslint-disable-line no-console
assembleError = ( ex ) -> var assembleError = function( ex ) {
msg = '' let se;
withStack = false let msg = '';
quit = false let withStack = false;
etype = 'warning' let quit = false;
withStack = true if @debug let etype = 'warning';
if (this.debug) { withStack = true; }
switch ex.fluenterror switch (ex.fluenterror) {
when HMSTATUS.themeNotFound case HMSTATUS.themeNotFound:
msg = printf( M2C( this.msgs.themeNotFound.msg, 'yellow' ), ex.data) msg = printf( M2C( this.msgs.themeNotFound.msg, 'yellow' ), ex.data);
break;
when HMSTATUS.copyCSS case HMSTATUS.copyCSS:
msg = M2C( this.msgs.copyCSS.msg, 'red' ) msg = M2C( this.msgs.copyCSS.msg, 'red' );
quit = false quit = false;
break;
when HMSTATUS.resumeNotFound case HMSTATUS.resumeNotFound:
#msg = M2C( this.msgs.resumeNotFound.msg, 'yellow' ); //msg = M2C( this.msgs.resumeNotFound.msg, 'yellow' );
msg += M2C(FS.readFileSync( msg += M2C(FS.readFileSync(
PATH.resolve(__dirname, 'help/' + ex.verb + '.txt'), 'utf8' ), 'white', 'yellow') PATH.resolve(__dirname, `help/${ex.verb}.txt`), 'utf8' ), 'white', 'yellow');
break;
when 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( (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 += M2C(FS.readFileSync( msg += M2C(FS.readFileSync(
PATH.resolve(__dirname, 'help/use.txt'), 'utf8' ), 'white', 'yellow') PATH.resolve(__dirname, 'help/use.txt'), 'utf8' ), 'white', 'yellow');
break;
when 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 );
break;
when HMSTATUS.resumeNotFoundAlt case HMSTATUS.resumeNotFoundAlt:
msg = M2C( this.msgs.resumeNotFoundAlt.msg, 'yellow' ) msg = M2C( this.msgs.resumeNotFoundAlt.msg, 'yellow' );
break;
when HMSTATUS.inputOutputParity case HMSTATUS.inputOutputParity:
msg = M2C( this.msgs.inputOutputParity.msg ) msg = M2C( this.msgs.inputOutputParity.msg );
break;
when HMSTATUS.createNameMissing case HMSTATUS.createNameMissing:
msg = M2C( this.msgs.createNameMissing.msg ) msg = M2C( this.msgs.createNameMissing.msg );
break;
when HMSTATUS.pdfGeneration case HMSTATUS.pdfGeneration:
msg = M2C( this.msgs.pdfGeneration.msg, 'bold' ) msg = M2C( this.msgs.pdfGeneration.msg, 'bold' );
msg += chalk.red('\n' + ex.inner) if ex.inner if (ex.inner) { msg += chalk.red(`\n${ex.inner}`); }
quit = false quit = false;
etype = 'error' etype = 'error';
break;
when HMSTATUS.invalid case HMSTATUS.invalid:
msg = M2C( this.msgs.invalid.msg, 'red' ) msg = M2C( this.msgs.invalid.msg, 'red' );
etype = 'error' etype = 'error';
break;
when HMSTATUS.generateError case HMSTATUS.generateError:
msg = (ex.inner && ex.inner.toString()) || ex msg = (ex.inner && ex.inner.toString()) || ex;
quit = false quit = false;
etype = 'error' etype = 'error';
break;
when HMSTATUS.fileSaveError case HMSTATUS.fileSaveError:
msg = printf( M2C( this.msgs.fileSaveError.msg ), (ex.inner || ex).toString() ) msg = printf( M2C( this.msgs.fileSaveError.msg ), (ex.inner || ex).toString() );
etype = 'error' etype = 'error';
quit = false quit = false;
break;
when HMSTATUS.invalidFormat case HMSTATUS.invalidFormat:
ex.data.forEach( (d) -> ex.data.forEach( function(d) {
msg += printf( M2C( this.msgs.invalidFormat.msg, 'bold' ), return msg += printf( M2C( this.msgs.invalidFormat.msg, 'bold' ),
ex.theme.name.toUpperCase(), d.format.toUpperCase()) ex.theme.name.toUpperCase(), d.format.toUpperCase());
, @); }
, this);
break;
when HMSTATUS.missingParam case HMSTATUS.missingParam:
msg = printf(M2C( this.msgs.missingParam.msg ), ex.expected, ex.helper) msg = printf(M2C( this.msgs.missingParam.msg ), ex.expected, ex.helper);
break;
when HMSTATUS.invalidHelperUse case HMSTATUS.invalidHelperUse:
msg = printf( M2C( this.msgs.invalidHelperUse.msg ), ex.helper ) msg = printf( M2C( this.msgs.invalidHelperUse.msg ), ex.helper );
if ex.error if (ex.error) {
msg += '\n--> ' + assembleError.call( this, extend( true, {}, ex, {fluenterror: ex.error} )).msg; msg += `\n--> ${assembleError.call( this, extend( true, {}, ex, {fluenterror: ex.error} )).msg}`;
#msg += printf( '\n--> ' + M2C( this.msgs.invalidParamCount.msg ), ex.expected ); }
quit = false //msg += printf( '\n--> ' + M2C( this.msgs.invalidParamCount.msg ), ex.expected );
etype = 'warning' quit = false;
etype = 'warning';
break;
when HMSTATUS.notOnPath case HMSTATUS.notOnPath:
msg = printf( M2C(this.msgs.notOnPath.msg, 'bold'), ex.engine) msg = printf( M2C(this.msgs.notOnPath.msg, 'bold'), ex.engine);
quit = false quit = false;
etype = 'error' etype = 'error';
break;
when HMSTATUS.readError case HMSTATUS.readError:
if !ex.quiet if (!ex.quiet) {
# eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.error(printf( M2C(this.msgs.readError.msg, 'red'), ex.file)) console.error(printf( M2C(this.msgs.readError.msg, 'red'), ex.file));
msg = ex.inner.toString() }
etype = 'error' msg = ex.inner.toString();
etype = 'error';
break;
when HMSTATUS.mixedMerge case HMSTATUS.mixedMerge:
msg = M2C this.msgs.mixedMerge.msg msg = M2C(this.msgs.mixedMerge.msg);
quit = false quit = false;
break;
when HMSTATUS.invokeTemplate case HMSTATUS.invokeTemplate:
msg = M2C this.msgs.invokeTemplate.msg, 'red' msg = M2C(this.msgs.invokeTemplate.msg, 'red');
msg += M2C( '\n' + WRAP(ex.inner.toString(), { width: 60, indent: ' ' }), 'gray' ); msg += M2C( `\n${WRAP(ex.inner.toString(), { width: 60, indent: ' ' })}`, 'gray' );
etype = 'custom' etype = 'custom';
break;
when HMSTATUS.compileTemplate case HMSTATUS.compileTemplate:
etype = 'error' etype = 'error';
break;
when HMSTATUS.themeLoad case HMSTATUS.themeLoad:
msg = M2C( printf( this.msgs.themeLoad.msg, ex.attempted.toUpperCase() ), 'red'); msg = M2C( printf( this.msgs.themeLoad.msg, ex.attempted.toUpperCase() ), 'red');
if ex.inner && ex.inner.fluenterror if (ex.inner && ex.inner.fluenterror) {
msg += M2C('\nError: ', 'red') + assembleError.call( this, ex.inner ).msg msg += M2C('\nError: ', 'red') + assembleError.call( this, ex.inner ).msg;
quit = true }
etype = 'custom' quit = true;
etype = 'custom';
break;
when HMSTATUS.parseError case HMSTATUS.parseError:
if SyntaxErrorEx.is ex.inner if (SyntaxErrorEx.is(ex.inner)) {
# eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.error printf( M2C(this.msgs.readError.msg, 'red'), ex.file ) console.error(printf( M2C(this.msgs.readError.msg, 'red'), ex.file ));
se = new SyntaxErrorEx ex, ex.raw se = new SyntaxErrorEx(ex, ex.raw);
if se.line? and se.col? if ((se.line != null) && (se.col != null)) {
msg = printf M2C( this.msgs.parseError.msg[0], 'red' ), se.line, se.col msg = printf(M2C( this.msgs.parseError.msg[0], 'red' ), se.line, se.col);
else if se.line? } else if (se.line != null) {
msg = printf M2C( this.msgs.parseError.msg[1], 'red' ), se.line msg = printf(M2C( this.msgs.parseError.msg[1], 'red' ), se.line);
else } else {
msg = M2C @msgs.parseError.msg[2], 'red' msg = M2C(this.msgs.parseError.msg[2], 'red');
else if ex.inner && ex.inner.line? && ex.inner.col? }
msg = printf( M2C( this.msgs.parseError.msg[0], 'red' ), ex.inner.line, ex.inner.col) } else if (ex.inner && (ex.inner.line != null) && (ex.inner.col != null)) {
else msg = printf( M2C( this.msgs.parseError.msg[0], 'red' ), ex.inner.line, ex.inner.col);
msg = ex } else {
etype = 'error' msg = ex;
}
etype = 'error';
break;
when HMSTATUS.createError case HMSTATUS.createError:
# inner.code could be EPERM, EACCES, etc // 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;
when HMSTATUS.validateError case HMSTATUS.validateError:
msg = printf M2C( @msgs.validateError.msg ), ex.inner.toString() msg = printf(M2C( this.msgs.validateError.msg ), ex.inner.toString());
etype = 'error' etype = 'error';
break;
when HMSTATUS.invalidOptionsFile case HMSTATUS.invalidOptionsFile:
msg = M2C @msgs.invalidOptionsFile.msg[0] msg = M2C(this.msgs.invalidOptionsFile.msg[0]);
if SyntaxErrorEx.is ex.inner if (SyntaxErrorEx.is(ex.inner)) {
# eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.error printf( M2C(this.msgs.readError.msg, 'red'), ex.file ) console.error(printf( M2C(this.msgs.readError.msg, 'red'), ex.file ));
se = new SyntaxErrorEx ex, ex.raw se = new SyntaxErrorEx(ex, ex.raw);
if se.line? and se.col? if ((se.line != null) && (se.col != null)) {
msg += printf M2C( this.msgs.parseError.msg[0], 'red' ), se.line, se.col msg += printf(M2C( this.msgs.parseError.msg[0], 'red' ), se.line, se.col);
else if se.line? } else if (se.line != null) {
msg += printf M2C( this.msgs.parseError.msg[1], 'red' ), se.line msg += printf(M2C( this.msgs.parseError.msg[1], 'red' ), se.line);
else } else {
msg += M2C @msgs.parseError.msg[2], 'red' msg += M2C(this.msgs.parseError.msg[2], 'red');
else if ex.inner && ex.inner.line? && ex.inner.col? }
msg += printf( M2C( this.msgs.parseError.msg[0], 'red' ), ex.inner.line, ex.inner.col) } else if (ex.inner && (ex.inner.line != null) && (ex.inner.col != null)) {
else msg += printf( M2C( this.msgs.parseError.msg[0], 'red' ), ex.inner.line, ex.inner.col);
msg += ex } else {
msg += @msgs.invalidOptionsFile.msg[1] msg += ex;
etype = 'error' }
msg += this.msgs.invalidOptionsFile.msg[1];
etype = 'error';
break;
when HMSTATUS.optionsFileNotFound case HMSTATUS.optionsFileNotFound:
msg = M2C( @msgs.optionsFileNotFound.msg ) msg = M2C( this.msgs.optionsFileNotFound.msg );
etype = 'error' etype = 'error';
break;
when HMSTATUS.unknownSchema case HMSTATUS.unknownSchema:
msg = M2C( @msgs.unknownSchema.msg[0] ) msg = M2C( this.msgs.unknownSchema.msg[0] );
#msg += "\n" + M2C( @msgs.unknownSchema.msg[1], 'yellow' ) //msg += "\n" + M2C( @msgs.unknownSchema.msg[1], 'yellow' )
etype = 'error' etype = 'error';
break;
when HMSTATUS.themeHelperLoad case HMSTATUS.themeHelperLoad:
msg = printf M2C( @msgs.themeHelperLoad.msg ), ex.glob msg = printf(M2C( this.msgs.themeHelperLoad.msg ), ex.glob);
etype = 'error' etype = 'error';
break;
when HMSTATUS.invalidSchemaVersion case HMSTATUS.invalidSchemaVersion:
msg = printf M2C( @msgs.invalidSchemaVersion.msg ), ex.data msg = printf(M2C( this.msgs.invalidSchemaVersion.msg ), ex.data);
etype = 'error' etype = 'error';
break;
}
msg: msg # The error message to display return {
withStack: withStack # Whether to include the stack msg, // The error message to display
quit: quit withStack, // Whether to include the stack
etype: etype quit,
etype
};
};

View File

@ -1,51 +1,57 @@
###* /*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
/**
Definition of the `main` function. Definition of the `main` function.
@module cli/main @module cli/main
@license MIT. See LICENSE.md for details. @license MIT. See LICENSE.md for details.
### */
HMR = require '../index' const HMR = require('../index');
PKG = require '../../package.json' const PKG = require('../../package.json');
FS = require 'fs' const FS = require('fs');
EXTEND = require 'extend' const EXTEND = require('extend');
chalk = require 'chalk' const chalk = require('chalk');
PATH = require 'path' const PATH = require('path');
HMSTATUS = require '../core/status-codes' const HMSTATUS = require('../core/status-codes');
safeLoadJSON = require '../utils/safe-json-loader' const safeLoadJSON = require('../utils/safe-json-loader');
#StringUtils = require '../utils/string.js' //StringUtils = require '../utils/string.js'
_ = require 'underscore' const _ = require('underscore');
OUTPUT = require './out' const OUTPUT = require('./out');
PAD = require 'string-padding' const PAD = require('string-padding');
Command = require('commander').Command const { Command } = require('commander');
M2C = require '../utils/md2chalk' const M2C = require('../utils/md2chalk');
printf = require 'printf' const printf = require('printf');
_opts = { } const _opts = { };
_title = chalk.white.bold('\n*** HackMyResume v' +PKG.version+ ' ***') const _title = chalk.white.bold(`\n*** HackMyResume v${PKG.version} ***`);
_out = new OUTPUT( _opts ) const _out = new OUTPUT( _opts );
_err = require('./error') const _err = require('./error');
_exitCallback = null let _exitCallback = null;
### /*
A callable implementation of the HackMyResume CLI. Encapsulates the command A callable implementation of the HackMyResume CLI. Encapsulates the command
line interface as a single method accepting a parameter array. line interface as a single method accepting a parameter array.
@alias module:cli/main.main @alias module:cli/main.main
@param rawArgs {Array} An array of command-line parameters. Will either be @param rawArgs {Array} An array of command-line parameters. Will either be
process.argv (in production) or custom parameters (in test). process.argv (in production) or custom parameters (in test).
### */
module.exports = ( rawArgs, exitCallback ) -> module.exports = function( rawArgs, exitCallback ) {
initInfo = initialize( rawArgs, exitCallback ) const initInfo = initialize( rawArgs, exitCallback );
if initInfo is null if (initInfo === null) {
return return;
}
args = initInfo.args const { args } = initInfo;
# Create the top-level (application) command... // Create the top-level (application) command...
program = new Command('hackmyresume') const program = new Command('hackmyresume')
.version(PKG.version) .version(PKG.version)
.description(chalk.yellow.bold('*** HackMyResume ***')) .description(chalk.yellow.bold('*** HackMyResume ***'))
.option('-s --silent', 'Run in silent mode') .option('-s --silent', 'Run in silent mode')
@ -55,65 +61,61 @@ module.exports = ( rawArgs, exitCallback ) ->
.option('-a --assert', 'Treat warnings as errors', false) .option('-a --assert', 'Treat warnings as errors', false)
.option('-v --version', 'Show the version') .option('-v --version', 'Show the version')
.allowUnknownOption(); .allowUnknownOption();
program.jsonArgs = initInfo.options program.jsonArgs = initInfo.options;
# Create the NEW command // Create the NEW command
program program
.command 'new' .command('new')
.arguments '<sources...>' .arguments('<sources...>')
.option '-f --format <fmt>', 'FRESH or JRS format', 'FRESH' .option('-f --format <fmt>', 'FRESH or JRS format', 'FRESH')
.alias 'create' .alias('create')
.description 'Create resume(s) in FRESH or JSON RESUME format.' .description('Create resume(s) in FRESH or JSON RESUME format.')
.action (( sources ) -> .action((function( sources ) {
execute.call( this, sources, [], this.opts(), logMsg) execute.call( this, sources, [], this.opts(), logMsg);
return })
) );
# Create the VALIDATE command // Create the VALIDATE command
program program
.command('validate') .command('validate')
.arguments('<sources...>') .arguments('<sources...>')
.description('Validate a resume in FRESH or JSON RESUME format.') .description('Validate a resume in FRESH or JSON RESUME format.')
.action((sources) -> .action(function(sources) {
execute.call( this, sources, [], this.opts(), logMsg) execute.call( this, sources, [], this.opts(), logMsg);
return });
)
# Create the CONVERT command // Create the CONVERT command
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) .option('-f --format <fmt>', 'FRESH or JRS format and optional version', undefined)
.action(-> .action(function() {
x = splitSrcDest.call( this ); const x = splitSrcDest.call( this );
execute.call( this, x.src, x.dst, this.opts(), logMsg) execute.call( this, x.src, x.dst, this.opts(), logMsg);
return });
)
# Create the ANALYZE command // Create the ANALYZE command
program program
.command('analyze') .command('analyze')
.arguments('<sources...>') .arguments('<sources...>')
.option('--private', 'Include resume fields marked as private', false) .option('--private', 'Include resume fields marked as private', false)
.description('Analyze one or more resumes.') .description('Analyze one or more resumes.')
.action(( sources ) -> .action(function( sources ) {
execute.call( this, sources, [], this.opts(), logMsg) execute.call( this, sources, [], this.opts(), logMsg);
return });
)
# Create the PEEK command // Create the PEEK command
program program
.command('peek') .command('peek')
.arguments('<sources...>') .arguments('<sources...>')
.description('Peek at a resume field or section') .description('Peek at a resume field or section')
#.action(( sources, sectionOrField ) -> //.action(( sources, sectionOrField ) ->
.action(( sources ) -> .action(function( sources ) {
dst = if (sources && sources.length > 1) then [sources.pop()] else [] const dst = (sources && (sources.length > 1)) ? [sources.pop()] : [];
execute.call( this, sources, dst, this.opts(), logMsg) execute.call( this, sources, dst, this.opts(), logMsg);
return });
)
# Create the BUILD command // Create the BUILD command
program program
.command('build') .command('build')
.alias('generate') .alias('generate')
@ -126,134 +128,150 @@ module.exports = ( rawArgs, exitCallback ) ->
.option('--private', 'Include resume fields marked as private', false) .option('--private', 'Include resume fields marked as private', false)
.option('--no-escape', 'Turn off encoding in Handlebars themes.', false) .option('--no-escape', 'Turn off encoding in Handlebars themes.', false)
.description('Generate resume to multiple formats') .description('Generate resume to multiple formats')
#.action(( sources, targets, options ) -> //.action(( sources, targets, options ) ->
.action(-> .action(function() {
x = splitSrcDest.call( this ); const x = splitSrcDest.call( this );
execute.call( this, x.src, x.dst, this.opts(), logMsg) execute.call( this, x.src, x.dst, this.opts(), logMsg);
return });
)
# Create the HELP command // Create the HELP command
program program
.command('help') .command('help')
.arguments('[command]') .arguments('[command]')
.description('Get help on a HackMyResume command') .description('Get help on a HackMyResume command')
.action ( cmd ) -> .action(function( cmd ) {
cmd = cmd || 'use' cmd = cmd || 'use';
manPage = FS.readFileSync( const manPage = FS.readFileSync(
PATH.join(__dirname, 'help/' + cmd + '.txt'), PATH.join(__dirname, `help/${cmd}.txt`),
'utf8') 'utf8');
_out.log M2C(manPage, 'white', 'yellow.bold') _out.log(M2C(manPage, 'white', 'yellow.bold'));
return });
program.parse( args ) program.parse( args );
if !program.args.length if (!program.args.length) {
throw fluenterror: 4 throw {fluenterror: 4};
}
};
### Massage command-line args and setup Commander.js. ### /* Massage command-line args and setup Commander.js. */
initialize = ( ar, exitCallback ) -> var initialize = function( ar, exitCallback ) {
_exitCallback = exitCallback || process.exit _exitCallback = exitCallback || process.exit;
o = initOptions ar const o = initOptions(ar);
if o.ex if (o.ex) {
_err.init false, true, false _err.init(false, true, false);
if( o.ex.op == 'parse' ) if( o.ex.op === 'parse' ) {
_err.err _err.err({
fluenterror: if o.ex.op == 'parse' then HMSTATUS.invalidOptionsFile else HMSTATUS.optionsFileNotFound, fluenterror: o.ex.op === 'parse' ? HMSTATUS.invalidOptionsFile : HMSTATUS.optionsFileNotFound,
inner: o.ex.inner, inner: o.ex.inner,
quit: true quit: true
else });
_err.err fluenterror: HMSTATUS.optionsFileNotFound, inner: o.ex.inner, quit: true } else {
return null _err.err({fluenterror: HMSTATUS.optionsFileNotFound, inner: o.ex.inner, quit: true});
o.silent || logMsg( _title ) }
return null;
}
o.silent || logMsg( _title );
# Emit debug prelude if --debug was specified // 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('');
_out.log(chalk.cyan(PAD(' Platform:',25, null, PAD.RIGHT)) + chalk.cyan.bold( if process.platform == 'win32' then 'windows' else process.platform )) _out.log(chalk.cyan(PAD(' Platform:',25, null, PAD.RIGHT)) + chalk.cyan.bold( process.platform === 'win32' ? 'windows' : process.platform ));
_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-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(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);
# 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 ] && o.verb != 'help' 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 = () -> Command.prototype.missingArgument = function() {
if this.name() != 'help' if (this.name() !== 'help') {
_err.err _err.err({
verb: @name() verb: this.name(),
fluenterror: HMSTATUS.resumeNotFound fluenterror: HMSTATUS.resumeNotFound
, true }
return , true);
}
};
# Override the .helpInformation behavior // Override the .helpInformation behavior
Command.prototype.helpInformation = -> Command.prototype.helpInformation = function() {
manPage = FS.readFileSync( const manPage = FS.readFileSync(
PATH.join(__dirname, 'help/use.txt'), 'utf8' ) PATH.join(__dirname, 'help/use.txt'), 'utf8' );
return M2C(manPage, 'white', 'yellow') return M2C(manPage, 'white', 'yellow');
};
return { return {
args: o.args, args: o.args,
options: o.json options: o.json
};
};
/* Init options prior to setting up command infrastructure. */
var initOptions = function( ar ) {
let oJSON, oVerb;
oVerb;
let verb = '';
const args = ar.slice();
const cleanArgs = args.slice( 2 );
oJSON;
if (cleanArgs.length) {
// Support case-insensitive sub-commands (build, generate, validate, etc)
const vidx = _.findIndex(cleanArgs, v => v[0] !== '-');
if (vidx !== -1) {
oVerb = cleanArgs[ vidx ];
verb = (args[ vidx + 2 ] = oVerb.trim().toLowerCase());
}
// Remove --options --opts -o and process separately
const optsIdx = _.findIndex(cleanArgs, v => (v === '-o') || (v === '--options') || (v === '--opts'));
if (optsIdx !== -1) {
let optStr = cleanArgs[ optsIdx + 1];
args.splice( optsIdx + 2, 2 );
if (optStr && (optStr = optStr.trim())) {
//var myJSON = JSON.parse(optStr);
if( optStr[0] === '{') {
// TODO: remove use of evil(). - hacksalot
/* jshint ignore:start */
oJSON = eval(`(${optStr})`); // jshint ignore:line <-- no worky
/* jshint ignore:end */
} else {
const inf = safeLoadJSON( optStr );
if( !inf.ex ) {
oJSON = inf.json;
} else {
return inf;
}
}
}
}
} }
// Grab the --debug flag, --silent, --assert and --no-color flags
const isDebug = _.some(args, v => (v === '-d') || (v === '--debug'));
### Init options prior to setting up command infrastructure. ### const isSilent = _.some(args, v => (v === '-s') || (v === '--silent'));
initOptions = ( ar ) -> const isAssert = _.some(args, v => (v === '-a') || (v === '--assert'));
const isMono = _.some(args, v => v === '--no-color');
oVerb const isNoEscape = _.some(args, v => v === '--no-escape');
verb = ''
args = ar.slice()
cleanArgs = args.slice( 2 )
oJSON
if cleanArgs.length
# Support case-insensitive sub-commands (build, generate, validate, etc)
vidx = _.findIndex cleanArgs, (v) -> v[0] != '-'
if vidx != -1
oVerb = cleanArgs[ vidx ]
verb = args[ vidx + 2 ] = oVerb.trim().toLowerCase()
# Remove --options --opts -o and process separately
optsIdx = _.findIndex cleanArgs, (v) ->
v == '-o' || v == '--options' || v == '--opts'
if optsIdx != -1
optStr = cleanArgs[ optsIdx + 1]
args.splice( optsIdx + 2, 2 )
if optStr && (optStr = optStr.trim())
#var myJSON = JSON.parse(optStr);
if( optStr[0] == '{')
# TODO: remove use of evil(). - hacksalot
### jshint ignore:start ###
oJSON = eval('(' + optStr + ')') # jshint ignore:line <-- no worky
### jshint ignore:end ###
else
inf = safeLoadJSON( optStr )
if( !inf.ex )
oJSON = inf.json
else
return inf
# Grab the --debug flag, --silent, --assert and --no-color flags
isDebug = _.some args, (v) -> v == '-d' || v == '--debug'
isSilent = _.some args, (v) -> v == '-s' || v == '--silent'
isAssert = _.some args, (v) -> v == '-a' || v == '--assert'
isMono = _.some args, (v) -> v == '--no-color'
isNoEscape = _.some args, (v) -> v == '--no-escape'
return { return {
color: !isMono, color: !isMono,
@ -262,129 +280,142 @@ initOptions = ( ar ) ->
assert: isAssert, assert: isAssert,
noescape: isNoEscape, noescape: isNoEscape,
orgVerb: oVerb, orgVerb: oVerb,
verb: verb, verb,
json: oJSON, json: oJSON,
args: args args
};
};
/* Invoke a HackMyResume verb. */
var execute = function( src, dst, opts, log ) {
// Create the verb
const v = new (HMR.verbs[ this.name() ])();
// Initialize command-specific options
loadOptions.call(this, opts, this.parent.jsonArgs);
// Set up error/output handling
_opts.errHandler = v;
_out.init(_opts);
// Hook up event notifications
v.on('hmr:status', function() { return _out.do.apply(_out, arguments); });
v.on('hmr:error', function() { return _err.err.apply(_err, arguments); });
// Invoke the verb using promise syntax
const prom = v.invoke.call(v, src, dst, _opts, log);
prom.then(executeSuccess, executeFail);
};
/* Success handler for verb invocations. Calls process.exit by default */
var executeSuccess = function() {};
// 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 */
var executeFail = function(err) {
//console.dir err
let finalErrorCode = -1;
if (err) {
if (err.fluenterror) {
finalErrorCode = err.fluenterror;
} else if (err.length) {
finalErrorCode = err[0].fluenterror;
} else {
finalErrorCode = err;
}
} }
if (_opts.debug) {
const msgs = require('./msg').errors;
logMsg(printf(M2C( msgs.exiting.msg, 'cyan' ), finalErrorCode));
if (err.stack) { logMsg(err.stack); }
}
_exitCallback(finalErrorCode);
};
### Invoke a HackMyResume verb. ### /*
execute = ( src, dst, opts, log ) ->
# Create the verb
v = new HMR.verbs[ @name() ]()
# Initialize command-specific options
loadOptions.call this, opts, this.parent.jsonArgs
# Set up error/output handling
_opts.errHandler = v
_out.init _opts
# Hook up event notifications
v.on 'hmr:status', -> _out.do.apply _out, arguments
v.on 'hmr:error', -> _err.err.apply _err, arguments
# Invoke the verb using promise syntax
prom = v.invoke.call v, src, dst, _opts, log
prom.then executeSuccess, executeFail
return
### Success handler for verb invocations. Calls process.exit by default ###
executeSuccess = () ->
# Can't call _exitCallback here (process.exit) when PDF is running in BK
#_exitCallback 0; return
### Failure handler for verb invocations. Calls process.exit by default ###
executeFail = (err) ->
#console.dir err
finalErrorCode = -1
if err
if err.fluenterror
finalErrorCode = err.fluenterror
else if err.length
finalErrorCode = err[0].fluenterror
else
finalErrorCode = err
if _opts.debug
msgs = require('./msg').errors;
logMsg printf M2C( msgs.exiting.msg, 'cyan' ), finalErrorCode
logMsg err.stack if err.stack
_exitCallback finalErrorCode
return
###
Initialize HackMyResume options. Initialize HackMyResume options.
TODO: Options loading is a little hacky, for two reasons: TODO: Options loading is a little hacky, for two reasons:
- Commander.js idiosyncracies - Commander.js idiosyncracies
- Need to accept JSON inputs from the command line. - Need to accept JSON inputs from the command line.
### */
loadOptions = ( o, cmdO ) -> var loadOptions = function( o, cmdO ) {
# o and this.opts() seem to be the same (command-specific options) // o and this.opts() seem to be the same (command-specific options)
# Load the specified options file (if any) and apply 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 // 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 // Kludge parent-level options until piping issue is resolved
if this.parent.silent != undefined && this.parent.silent != null if ((this.parent.silent !== undefined) && (this.parent.silent !== null)) {
o.silent = this.parent.silent o.silent = this.parent.silent;
if this.parent.debug != undefined && this.parent.debug != null }
o.debug = this.parent.debug if ((this.parent.debug !== undefined) && (this.parent.debug !== null)) {
if this.parent.assert != undefined && this.parent.assert != null o.debug = this.parent.debug;
o.assert = this.parent.assert }
if ((this.parent.assert !== undefined) && (this.parent.assert !== null)) {
o.assert = this.parent.assert;
}
if o.debug if (o.debug) {
logMsg(chalk.cyan('OPTIONS:') + '\n') logMsg(chalk.cyan('OPTIONS:') + '\n');
_.each(o, (val, key) -> _.each(o, (val, key) =>
logMsg(chalk.cyan(' %s') + chalk.cyan.bold(' %s'), logMsg(chalk.cyan(' %s') + chalk.cyan.bold(' %s'),
PAD(key,22,null,PAD.RIGHT), val) PAD(key,22,null,PAD.RIGHT), val)
); );
logMsg(''); logMsg('');
}
# Cache // Cache
EXTEND( true, _opts, o ) EXTEND( true, _opts, o );
return };
### Split multiple command-line filenames by the 'TO' keyword ### /* Split multiple command-line filenames by the 'TO' keyword */
splitSrcDest = () -> var splitSrcDest = function() {
params = this.parent.args.filter((j) -> return String.is(j) ) const params = this.parent.args.filter(j => String.is(j));
if params.length == 0 if (params.length === 0) {
#tmpName = @name() //tmpName = @name()
throw { fluenterror: HMSTATUS.resumeNotFound, verb: @name(), quit: true } throw { fluenterror: HMSTATUS.resumeNotFound, verb: this.name(), quit: true };
}
# Find the TO keyword, if any // Find the TO keyword, if any
splitAt = _.findIndex( params, (p) -> return p.toLowerCase() == 'to'; ) const splitAt = _.findIndex( params, p => p.toLowerCase() === 'to');
# TO can't be the last keyword // 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 ') + logMsg(chalk.yellow('Please ') +
chalk.yellow.bold('specify an output file') + chalk.yellow.bold('specify an output file') +
chalk.yellow(' for this operation or ') + chalk.yellow(' for this operation or ') +
chalk.yellow.bold('omit the TO keyword') + chalk.yellow.bold('omit the TO keyword') +
chalk.yellow('.') ) chalk.yellow('.') );
return return;
return {
src: params.slice(0, if splitAt == -1 then undefined else splitAt ),
dst: if splitAt == -1 then [] else params.slice( splitAt + 1 )
} }
return {
src: params.slice(0, splitAt === -1 ? undefined : splitAt ),
dst: splitAt === -1 ? [] : params.slice( splitAt + 1 )
};
};
### Simple logging placeholder. ###
logMsg = () -> /* Simple logging placeholder. */
# eslint-disable-next-line no-console var logMsg = function() {
_opts.silent || console.log.apply( console.log, arguments ) // eslint-disable-next-line no-console
return _opts.silent || console.log.apply( console.log, arguments );
};

View File

@ -1,10 +1,10 @@
###* /**
Message-handling routines for HackMyResume. Message-handling routines for HackMyResume.
@module cli/msg @module cli/msg
@license MIT. See LICENSE.md for details. @license MIT. See LICENSE.md for details.
### */
PATH = require 'path' const PATH = require('path');
YAML = require 'yamljs' const YAML = require('yamljs');
module.exports = YAML.load PATH.join __dirname, 'msg.yml' module.exports = YAML.load(PATH.join(__dirname, 'msg.yml'));

View File

@ -1,182 +1,204 @@
###* /*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
/**
Output routines for HackMyResume. Output routines for HackMyResume.
@license MIT. See LICENSE.md for details. @license MIT. See LICENSE.md for details.
@module cli/out @module cli/out
### */
chalk = require('chalk') const chalk = require('chalk');
HME = require('../core/event-codes') const HME = require('../core/event-codes');
_ = require('underscore') const _ = require('underscore');
M2C = require('../utils/md2chalk.js') const M2C = require('../utils/md2chalk.js');
PATH = require('path') const PATH = require('path');
FS = require('fs') const FS = require('fs');
EXTEND = require('extend') const EXTEND = require('extend');
HANDLEBARS = require('handlebars') const HANDLEBARS = require('handlebars');
YAML = require('yamljs') const YAML = require('yamljs');
printf = require('printf') let printf = require('printf');
pad = require('string-padding') const pad = require('string-padding');
dbgStyle = 'cyan'; const dbgStyle = 'cyan';
###* A stateful output module. All HMR console output handled here. ### /** A stateful output module. All HMR console output handled here. */
class OutputHandler class OutputHandler {
constructor: ( opts ) -> constructor( opts ) {
@init opts this.init(opts);
return }
init: (opts) -> init(opts) {
@opts = EXTEND( true, @opts || { }, opts ) this.opts = EXTEND( true, this.opts || { }, opts );
@msgs = YAML.load(PATH.join( __dirname, 'msg.yml' )).events this.msgs = YAML.load(PATH.join( __dirname, 'msg.yml' )).events;
return }
log: -> log() {
printf = require('printf') printf = require('printf');
finished = printf.apply( printf, arguments ) const finished = printf.apply( printf, arguments );
@opts.silent || console.log( finished ) # eslint-disable-line no-console return this.opts.silent || console.log( finished ); // eslint-disable-line no-console
}
do: ( evt ) -> do( evt ) {
that = @ const that = this;
L = () -> that.log.apply( that, arguments ) const L = function() { return that.log.apply( that, arguments ); };
switch evt.sub switch (evt.sub) {
when HME.begin case HME.begin:
this.opts.debug && return this.opts.debug &&
L( M2C( this.msgs.begin.msg, dbgStyle), evt.cmd.toUpperCase() ) L( M2C( this.msgs.begin.msg, dbgStyle), evt.cmd.toUpperCase() );
#when HME.beforeCreate //when HME.beforeCreate
#L( M2C( this.msgs.beforeCreate.msg, 'green' ), evt.fmt, evt.file ) //L( M2C( this.msgs.beforeCreate.msg, 'green' ), evt.fmt, evt.file )
#break; //break;
when HME.afterCreate case HME.afterCreate:
L( M2C( @msgs.beforeCreate.msg, if evt.isError then 'red' else 'green' ), evt.fmt, evt.file ) L( M2C( this.msgs.beforeCreate.msg, evt.isError ? 'red' : 'green' ), evt.fmt, evt.file );
break; break;
when HME.beforeTheme case HME.beforeTheme:
this.opts.debug && return this.opts.debug &&
L( M2C( this.msgs.beforeTheme.msg, dbgStyle), evt.theme.toUpperCase() ) L( M2C( this.msgs.beforeTheme.msg, dbgStyle), evt.theme.toUpperCase() );
when HME.afterParse case HME.afterParse:
L( M2C( this.msgs.afterRead.msg, 'gray', 'white.dim'), evt.fmt.toUpperCase(), evt.file ) return L( M2C( this.msgs.afterRead.msg, 'gray', 'white.dim'), evt.fmt.toUpperCase(), evt.file );
when HME.beforeMerge case HME.beforeMerge:
msg = '' var msg = '';
evt.f.reverse().forEach ( a, idx ) -> evt.f.reverse().forEach(function( a, idx ) {
msg += printf( (if idx == 0 then @msgs.beforeMerge.msg[0] else @msgs.beforeMerge.msg[1]), a.file ) return msg += printf( (idx === 0 ? this.msgs.beforeMerge.msg[0] : this.msgs.beforeMerge.msg[1]), a.file );
, @ }
L( M2C(msg, (if evt.mixed then 'yellow' else 'gray'), 'white.dim') ) , this);
return L( M2C(msg, (evt.mixed ? 'yellow' : 'gray'), 'white.dim') );
when HME.applyTheme case HME.applyTheme:
@theme = evt.theme; this.theme = evt.theme;
numFormats = Object.keys( evt.theme.formats ).length; var numFormats = Object.keys( evt.theme.formats ).length;
L( M2C(this.msgs.applyTheme.msg, return L( M2C(this.msgs.applyTheme.msg,
if evt.status == 'error' then 'red' else 'gray', evt.status === 'error' ? 'red' : 'gray',
if evt.status == 'error' then 'bold' else 'white.dim'), evt.status === 'error' ? 'bold' : 'white.dim'),
evt.theme.name.toUpperCase(), evt.theme.name.toUpperCase(),
numFormats, if numFormats == 1 then '' else 's' ) numFormats, numFormats === 1 ? '' : 's' );
when HME.end case HME.end:
if evt.cmd == 'build' if (evt.cmd === 'build') {
themeName = this.theme.name.toUpperCase() const themeName = this.theme.name.toUpperCase();
if this.opts.tips && (this.theme.message || this.theme.render) if (this.opts.tips && (this.theme.message || this.theme.render)) {
if this.theme.message if (this.theme.message) {
L( M2C( this.msgs.afterBuild.msg[0], 'cyan' ), themeName ) L( M2C( this.msgs.afterBuild.msg[0], 'cyan' ), themeName );
L( M2C( this.theme.message, 'white' )) return L( M2C( this.theme.message, 'white' ));
else if this.theme.render } else if (this.theme.render) {
L( M2C( this.msgs.afterBuild.msg[0], 'cyan'), themeName) L( M2C( this.msgs.afterBuild.msg[0], 'cyan'), themeName);
L( M2C( this.msgs.afterBuild.msg[1], 'white')) return L( M2C( this.msgs.afterBuild.msg[1], 'white'));
}
}
}
break;
when HME.afterGenerate case HME.afterGenerate:
suffix = '' var suffix = '';
if evt.fmt == 'pdf' if (evt.fmt === 'pdf') {
if this.opts.pdf if (this.opts.pdf) {
if this.opts.pdf != 'none' if (this.opts.pdf !== 'none') {
suffix = printf( M2C( this.msgs.afterGenerate.msg[0], if evt.error then 'red' else 'green' ), this.opts.pdf ) suffix = printf( M2C( this.msgs.afterGenerate.msg[0], evt.error ? 'red' : 'green' ), this.opts.pdf );
else } else {
L( M2C( this.msgs.afterGenerate.msg[1], 'gray' ), evt.fmt.toUpperCase(), evt.file ) L( M2C( this.msgs.afterGenerate.msg[1], 'gray' ), evt.fmt.toUpperCase(), evt.file );
return return;
}
}
}
L( M2C( this.msgs.afterGenerate.msg[2] + suffix, if evt.error then 'red' else 'green' ), return L( M2C( this.msgs.afterGenerate.msg[2] + suffix, evt.error ? 'red' : 'green' ),
pad( evt.fmt.toUpperCase(),4,null,pad.RIGHT ), pad( evt.fmt.toUpperCase(),4,null,pad.RIGHT ),
PATH.relative( process.cwd(), evt.file ) ); PATH.relative( process.cwd(), evt.file ) );
when HME.beforeAnalyze case HME.beforeAnalyze:
L( M2C( this.msgs.beforeAnalyze.msg, 'green' ), evt.fmt, evt.file) return L( M2C( this.msgs.beforeAnalyze.msg, 'green' ), evt.fmt, evt.file);
when HME.afterAnalyze case HME.afterAnalyze:
info = evt.info var { info } = evt;
rawTpl = FS.readFileSync( PATH.join( __dirname, 'analyze.hbs' ), 'utf8') var rawTpl = FS.readFileSync( PATH.join( __dirname, 'analyze.hbs' ), 'utf8');
HANDLEBARS.registerHelper( require('../helpers/console-helpers') ) HANDLEBARS.registerHelper( require('../helpers/console-helpers') );
template = HANDLEBARS.compile(rawTpl, { strict: false, assumeObjects: false }) var template = HANDLEBARS.compile(rawTpl, { strict: false, assumeObjects: false });
tot = 0 var tot = 0;
info.keywords.forEach (g) -> tot += g.count info.keywords.forEach(g => tot += g.count);
info.keywords.totalKeywords = tot info.keywords.totalKeywords = tot;
output = template( info ) var output = template( info );
@log( chalk.cyan(output) ) return this.log( chalk.cyan(output) );
when HME.beforeConvert case HME.beforeConvert:
L( M2C( this.msgs.beforeConvert.msg, if evt.error then 'red' else 'green' ), return L( M2C( this.msgs.beforeConvert.msg, evt.error ? 'red' : 'green' ),
evt.srcFile, evt.srcFmt, evt.dstFile, evt.dstFmt evt.srcFile, evt.srcFmt, evt.dstFile, evt.dstFmt
); );
when HME.afterInlineConvert case HME.afterInlineConvert:
L( M2C( this.msgs.afterInlineConvert.msg, 'gray', 'white.dim' ), return L( M2C( this.msgs.afterInlineConvert.msg, 'gray', 'white.dim' ),
evt.file, evt.fmt ); evt.file, evt.fmt );
when HME.afterValidate case HME.afterValidate:
style = 'red' var style = 'red';
adj = '' var adj = '';
msgs = @msgs.afterValidate.msg; var msgs = this.msgs.afterValidate.msg;
switch evt.status switch (evt.status) {
when 'valid' then style = 'green'; adj = msgs[1] case 'valid': style = 'green'; adj = msgs[1]; break;
when 'invalid' then style = 'yellow'; adj = msgs[2] case 'invalid': style = 'yellow'; adj = msgs[2]; break;
when 'broken' then style = 'red'; adj = msgs[3] case 'broken': style = 'red'; adj = msgs[3]; break;
when 'missing' then style = 'red'; adj = msgs[4] case 'missing': style = 'red'; adj = msgs[4]; break;
when 'unknown' then style = 'red'; adj = msgs[5] case 'unknown': style = 'red'; adj = msgs[5]; break;
evt.schema = evt.schema.replace('jars','JSON Resume').toUpperCase() }
L(M2C( msgs[0], 'white' ) + chalk[style].bold(adj), evt.file, evt.schema) evt.schema = evt.schema.replace('jars','JSON Resume').toUpperCase();
L(M2C( msgs[0], 'white' ) + chalk[style].bold(adj), evt.file, evt.schema);
if evt.violations if (evt.violations) {
_.each evt.violations, (err) -> _.each(evt.violations, function(err) {
L( chalk.yellow.bold('--> ') + L( chalk.yellow.bold('--> ') +
chalk.yellow(err.field.replace('data.','resume.').toUpperCase() + chalk.yellow(err.field.replace('data.','resume.').toUpperCase() +
' ' + err.message)) ' ' + err.message));
return }
, @ , this);
return }
return;
when HME.afterPeek case HME.afterPeek:
sty = if evt.error then 'red' else ( if evt.target != undefined then 'green' else 'yellow' ) var sty = evt.error ? 'red' : ( evt.target !== undefined ? 'green' : 'yellow' );
# "Peeking at 'someKey' in 'someFile'." // "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 the key was present, print it
if evt.target != undefined and !evt.error if ((evt.target !== undefined) && !evt.error) {
# eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.dir( evt.target, { depth: null, colors: true } ) return console.dir( evt.target, { depth: null, colors: true } );
# If the key was not present, but no error occurred, print it // If the key was not present, but no error occurred, print it
else if !evt.error } else if (!evt.error) {
L M2C( this.msgs.afterPeek.msg, 'yellow'), evt.requested, evt.file return L(M2C( this.msgs.afterPeek.msg, 'yellow'), evt.requested, evt.file);
else if evt.error } else if (evt.error) {
L chalk.red( evt.error.inner.inner ) return L(chalk.red( evt.error.inner.inner ));
}
break;
}
}
}
module.exports = OutputHandler module.exports = OutputHandler;

View File

@ -1,10 +1,10 @@
### /*
Event code definitions. Event code definitions.
@module core/default-formats @module core/default-formats
@license MIT. See LICENSE.md for details. @license MIT. See LICENSE.md for details.
### */
###* Supported resume formats. ### /** Supported resume formats. */
module.exports = [ module.exports = [
{ name: 'html', ext: 'html', gen: new (require('../generators/html-generator'))() }, { name: 'html', ext: 'html', gen: new (require('../generators/html-generator'))() },
{ name: 'txt', ext: 'txt', gen: new (require('../generators/text-generator'))() }, { name: 'txt', ext: 'txt', gen: new (require('../generators/text-generator'))() },
@ -15,4 +15,4 @@ module.exports = [
{ name: 'json', ext: 'json', gen: new (require('../generators/json-generator'))() }, { name: 'json', ext: 'json', gen: new (require('../generators/json-generator'))() },
{ name: 'yml', ext: 'yml', fmt: 'yml', gen: new (require('../generators/json-yaml-generator'))() }, { name: 'yml', ext: 'yml', fmt: 'yml', gen: new (require('../generators/json-yaml-generator'))() },
{ name: 'latex', ext: 'tex', fmt: 'latex', gen: new (require('../generators/latex-generator'))() } { name: 'latex', ext: 'tex', fmt: 'latex', gen: new (require('../generators/latex-generator'))() }
] ];

View File

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

View File

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

View File

@ -1,15 +1,22 @@
###* /*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* DS104: Avoid inline assignments
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
/**
The HackMyResume date representation. The HackMyResume date representation.
@license MIT. See LICENSE.md for details. @license MIT. See LICENSE.md for details.
@module core/fluent-date @module core/fluent-date
### */
moment = require 'moment' const 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.
1. The words "Present" and "Now", referring to the current date 1. The words "Present" and "Now", referring to the current date
@ -24,54 +31,65 @@ to specify a date format...but for maximum parsing safety and to avoid Moment
deprecation warnings, it's recommended to either a) explicitly specify the date 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
### */
class FluentDate class FluentDate {
constructor: (dt) -> constructor(dt) {
@rep = this.fmt dt this.rep = this.fmt(dt);
}
@isCurrent: (dt) -> static isCurrent(dt) {
!dt || (String.is(dt) and /^(present|now|current)$/.test(dt)) return !dt || (String.is(dt) && /^(present|now|current)$/.test(dt));
}
}
months = {} const months = {};
abbr = {} const abbr = {};
moment.months().forEach((m,idx) -> months[m.toLowerCase()] = idx+1 ) moment.months().forEach((m,idx) => months[m.toLowerCase()] = idx+1);
moment.monthsShort().forEach((m,idx) -> abbr[m.toLowerCase()]=idx+1 ) moment.monthsShort().forEach((m,idx) => abbr[m.toLowerCase()]=idx+1);
abbr.sept = 9 abbr.sept = 9;
module.exports = FluentDate module.exports = FluentDate;
FluentDate.fmt = ( dt, throws ) -> FluentDate.fmt = function( dt, throws ) {
throws = (throws == undefined || throws == null) || throws throws = ((throws === undefined) || (throws === null)) || throws;
if typeof dt == 'string' or dt instanceof String if ((typeof dt === 'string') || dt instanceof String) {
dt = dt.toLowerCase().trim() dt = dt.toLowerCase().trim();
if /^(present|now|current)$/.test(dt) # "Present", "Now" if (/^(present|now|current)$/.test(dt)) { // "Present", "Now"
return moment() return moment();
else if /^\D+\s+\d{4}$/.test(dt) # "Mar 2015" } else if (/^\D+\s+\d{4}$/.test(dt)) { // "Mar 2015"
parts = dt.split(' '); let left;
month = (months[parts[0]] || abbr[parts[0]]); const parts = dt.split(' ');
temp = parts[1] + '-' + (month < 10 ? '0' + month : month.toString()); const month = (months[parts[0]] || abbr[parts[0]]);
return moment temp, 'YYYY-MM' const temp = parts[1] + '-' + ((left = month < 10) != null ? left : `0${{month : month.toString()}}`);
else if /^\d{4}-\d{1,2}$/.test(dt) # "2015-03", "1998-4" return moment(temp, 'YYYY-MM');
return moment dt, 'YYYY-MM' } else if (/^\d{4}-\d{1,2}$/.test(dt)) { // "2015-03", "1998-4"
else if /^\s*\d{4}\s*$/.test(dt) # "2015" return moment(dt, 'YYYY-MM');
return moment dt, 'YYYY' } else if (/^\s*\d{4}\s*$/.test(dt)) { // "2015"
else if /^\s*$/.test(dt) # "", " " return moment(dt, 'YYYY');
return moment() } else if (/^\s*$/.test(dt)) { // "", " "
else return moment();
mt = moment dt } else {
if mt.isValid() const mt = moment(dt);
return mt if (mt.isValid()) {
if throws return mt;
throw 'Invalid date format encountered.' }
return null if (throws) {
else throw 'Invalid date format encountered.';
if !dt }
return moment() return null;
else if dt.isValid and dt.isValid() }
return dt } else {
if throws if (!dt) {
throw 'Unknown date object encountered.' return moment();
return null } else if (dt.isValid && dt.isValid()) {
return dt;
}
if (throws) {
throw 'Unknown date object encountered.';
}
return null;
}
};

View File

@ -1,43 +1,50 @@
###* /*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
/**
Definition of the FRESHResume class. Definition of the FRESHResume class.
@license MIT. See LICENSE.md for details. @license MIT. See LICENSE.md for details.
@module core/fresh-resume @module core/fresh-resume
### */
FS = require 'fs' const FS = require('fs');
extend = require 'extend' const extend = require('extend');
validator = require 'is-my-json-valid' let validator = require('is-my-json-valid');
_ = require 'underscore' const _ = require('underscore');
__ = require 'lodash' const __ = require('lodash');
PATH = require 'path' const PATH = require('path');
moment = require 'moment' const moment = require('moment');
XML = require 'xml-escape' const XML = require('xml-escape');
MD = require 'marked' const MD = require('marked');
CONVERTER = require 'fresh-jrs-converter' const CONVERTER = require('fresh-jrs-converter');
JRSResume = require './jrs-resume' const JRSResume = require('./jrs-resume');
FluentDate = require './fluent-date' const FluentDate = require('./fluent-date');
###* /**
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
### */
class FreshResume# extends AbstractResume class FreshResume {// extends AbstractResume
###* Initialize the the FreshResume from JSON string data. ### /** Initialize the the FreshResume from JSON string data. */
parse: ( stringData, opts ) -> parse( stringData, opts ) {
@imp = @imp ? raw: stringData this.imp = this.imp != null ? this.imp : {raw: stringData};
this.parseJSON JSON.parse( stringData ), opts return this.parseJSON(JSON.parse( stringData ), opts);
}
###* /**
Initialize the FreshResume from JSON. Initialize the FreshResume from JSON.
Open and parse the specified FRESH resume. Merge the JSON object model onto Open and parse the specified FRESH resume. Merge the JSON object model onto
this Sheet instance with extend() and convert sheet dates to a safe & this Sheet instance with extend() and convert sheet dates to a safe &
@ -49,390 +56,429 @@ class FreshResume# extends AbstractResume
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 ) -> parseJSON( rep, opts ) {
if opts and opts.privatize let scrubbed;
# Ignore any element with the 'ignore: true' or 'private: true' designator. if (opts && opts.privatize) {
scrubber = require '../utils/resume-scrubber' // Ignore any element with the 'ignore: true' or 'private: true' designator.
{ scrubbed, ignoreList, privateList } = scrubber.scrubResume rep, opts let ignoreList, privateList;
const 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, this, opts && opts.privatize ? scrubbed : rep);
# If the resume has already been processed, then we are being called from // If the resume has already been processed, then we are being called from
# the .dupe method, and there's no need to do any post processing // the .dupe method, and there's no need to do any post processing
if !@imp?.processed if (!(this.imp != null ? this.imp.processed : undefined)) {
# Set up metadata TODO: Clean up metadata on the object model. // Set up metadata TODO: Clean up metadata on the object model.
opts = opts || { } opts = opts || { };
if opts.imp == undefined || opts.imp if ((opts.imp === undefined) || opts.imp) {
@imp = @imp || { } this.imp = this.imp || { };
@imp.title = (opts.title || @imp.title) || @name this.imp.title = (opts.title || this.imp.title) || this.name;
unless @imp.raw if (!this.imp.raw) {
@imp.raw = JSON.stringify rep this.imp.raw = JSON.stringify(rep);
@imp.processed = true }
# Parse dates, sort dates, and calculate computed values }
(opts.date == undefined || opts.date) && _parseDates.call( this ); this.imp.processed = true;
(opts.sort == undefined || opts.sort) && this.sort(); // Parse dates, sort dates, and calculate computed values
(opts.compute == undefined || opts.compute) && (@computed = { ((opts.date === undefined) || opts.date) && _parseDates.call( this );
((opts.sort === undefined) || opts.sort) && this.sort();
((opts.compute === undefined) || opts.compute) && (this.computed = {
numYears: this.duration(), numYears: this.duration(),
keywords: this.keywords() 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 ) -> save( filename ) {
@imp.file = filename || @imp.file this.imp.file = filename || this.imp.file;
FS.writeFileSync @imp.file, @stringify(), 'utf8' FS.writeFileSync(this.imp.file, this.stringify(), 'utf8');
@ 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 ) -> saveAs( filename, format ) {
# If format isn't specified, default to FRESH // If format isn't specified, default to FRESH
safeFormat = (format && format.trim()) || 'FRESH' const safeFormat = (format && format.trim()) || 'FRESH';
# Validate against the FRESH version regex // Validate against the FRESH version regex
# freshVersionReg = require '../utils/fresh-version-regex' // freshVersionReg = require '../utils/fresh-version-regex'
# if (not freshVersionReg().test( safeFormat )) // if (not freshVersionReg().test( safeFormat ))
# throw badVer: safeFormat // throw badVer: safeFormat
parts = safeFormat.split '@' const parts = safeFormat.split('@');
if parts[0] == 'FRESH' if (parts[0] === 'FRESH') {
@imp.file = filename || @imp.file this.imp.file = filename || this.imp.file;
FS.writeFileSync @imp.file, @stringify(), 'utf8' FS.writeFileSync(this.imp.file, this.stringify(), 'utf8');
else if parts[0] == 'JRS' } else if (parts[0] === 'JRS') {
useEdgeSchema = if parts.length > 1 then parts[1] == '1' else false const useEdgeSchema = parts.length > 1 ? parts[1] === '1' : false;
newRep = CONVERTER.toJRS @, edge: useEdgeSchema const newRep = CONVERTER.toJRS(this, {edge: useEdgeSchema});
FS.writeFileSync filename, JRSResume.stringify( newRep ), 'utf8' FS.writeFileSync(filename, JRSResume.stringify( newRep ), 'utf8');
else } else {
throw badVer: safeFormat throw {badVer: safeFormat};
@ }
return this;
}
###* /**
Duplicate this FreshResume instance. Duplicate this FreshResume instance.
This method first extend()s this object onto an empty, creating a deep copy, This method first extend()s this object onto an empty, creating a deep copy,
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: () -> dupe() {
jso = extend true, { }, @ const jso = extend(true, { }, this);
rnew = new FreshResume() const rnew = new FreshResume();
rnew.parseJSON jso, { } rnew.parseJSON(jso, { });
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.stringify @ stringify() { 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 ) -> transformStrings( filt, transformer ) {
ret = this.dupe() const ret = this.dupe();
trx = require '../utils/string-transformer' const trx = require('../utils/string-transformer');
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: () -> markdownify() {
MDIN = ( txt ) -> const MDIN = txt => MD(txt || '' ).replace(/^\s*<p>|<\/p>\s*$/gi, '');
return MD(txt || '' ).replace(/^\s*<p>|<\/p>\s*$/gi, '')
trx = ( key, val ) -> const trx = function( key, val ) {
if key == 'summary' if (key === 'summary') {
return MD val return MD(val);
MDIN(val) }
return MDIN(val);
};
return @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: () -> xmlify() {
trx = (key, val) -> XML val const trx = (key, val) => XML(val);
return @transformStrings [], trx return this.transformStrings([], trx);
}
###* Return the resume format. ### /** Return the resume format. */
format: () -> 'FRESH' format() { return 'FRESH'; }
###* /**
Return internal metadata. Create if it doesn't exist. Return internal metadata. Create if it doesn't exist.
### */
i: () -> this.imp = this.imp || { } i() { return this.imp = this.imp || { }; }
###* /**
Return a unique list of all skills declared in the resume. Return a unique list of all skills declared in the resume.
### */
# TODO: Several problems here: // TODO: Several problems here:
# 1) Confusing name. Easily confused with the keyword-inspector module, which // 1) Confusing name. Easily confused with the keyword-inspector module, which
# parses resume body text looking for these same keywords. This should probably // parses resume body text looking for these same keywords. This should probably
# be renamed. // be renamed.
# //
# 2) Doesn't bother trying to integrate skills.list with skills.sets if they // 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 // happen to declare different skills, and if skills.sets declares ONE skill and
# skills.list declared 50, only 1 skill will be registered. // 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 // 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 // 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 // declared skills. skills.sets is just a way of grouping those into skillsets
# for easier consumption. // for easier consumption.
keywords: () -> keywords() {
flatSkills = [] let flatSkills = [];
if @skills if (this.skills) {
if @skills.sets if (this.skills.sets) {
flatSkills = @skills.sets.map((sk) -> sk.skills ).reduce( (a,b) -> a.concat(b) ) flatSkills = this.skills.sets.map(sk => sk.skills).reduce( (a,b) => a.concat(b));
else if @skills.list } else if (this.skills.list) {
flatSkills = flatSkills.concat( this.skills.list.map (sk) -> return sk.name ) flatSkills = flatSkills.concat( this.skills.list.map(sk => sk.name) );
flatSkills = _.uniq flatSkills }
flatSkills flatSkills = _.uniq(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 ) -> clear( clearMeta ) {
clearMeta = ((clearMeta == undefined) && true) || clearMeta clearMeta = ((clearMeta === undefined) && true) || clearMeta;
delete this.imp if clearMeta if (clearMeta) { delete this.imp; }
delete this.computed # Don't use Object.keys() here 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;
delete this.recognition delete this.recognition;
delete this.reading delete this.reading;
delete this.writing delete this.writing;
delete this.interests delete this.interests;
delete this.skills delete this.skills;
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 ) -> count( obj ) {
return 0 if !obj if (!obj) { return 0; }
return obj.history.length if obj.history if (obj.history) { return obj.history.length; }
return obj.sets.length if obj.sets if (obj.sets) { return obj.sets.length; }
obj.length || 0; return obj.length || 0;
}
###* Add work experience to the sheet. ### /** Add work experience to the sheet. */
add: ( moniker ) -> add( moniker ) {
defSheet = FreshResume.default() const defSheet = FreshResume.default();
newObject = const newObject =
if defSheet[moniker].history defSheet[moniker].history
then $.extend( true, {}, defSheet[ moniker ].history[0] ) ? $.extend( true, {}, defSheet[ moniker ].history[0] )
else :
if moniker == 'skills' moniker === 'skills'
then $.extend( true, {}, defSheet.skills.sets[0] ) ? $.extend( true, {}, defSheet.skills.sets[0] )
else $.extend( true, {}, defSheet[ moniker ][0] ) : $.extend( true, {}, defSheet[ moniker ][0] );
@[ moniker ] = @[ moniker ] || [] this[ moniker ] = this[ moniker ] || [];
if @[ moniker ].history if (this[ moniker ].history) {
@[ moniker ].history.push newObject this[ moniker ].history.push(newObject);
else if moniker == 'skills' } else if (moniker === 'skills') {
@skills.sets.push newObject this.skills.sets.push(newObject);
else } else {
@[ moniker ].push newObject this[ moniker ].push(newObject);
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 ) -> hasProfile( socialNetwork ) {
socialNetwork = socialNetwork.trim().toLowerCase() socialNetwork = socialNetwork.trim().toLowerCase();
@social && _.some @social, (p) -> return this.social && _.some(this.social, p => p.network.trim().toLowerCase() === socialNetwork);
p.network.trim().toLowerCase() == socialNetwork }
###* Return the specified network profile. ### /** Return the specified network profile. */
getProfile: ( socialNetwork ) -> getProfile( socialNetwork ) {
socialNetwork = socialNetwork.trim().toLowerCase() socialNetwork = socialNetwork.trim().toLowerCase();
@social && _.find @social, (sn) -> return this.social && _.find(this.social, sn => sn.network.trim().toLowerCase() === socialNetwork);
sn.network.trim().toLowerCase() == socialNetwork }
###* /**
Return an array of profiles for the specified network, for when the user Return an array of profiles for the specified network, for when the user
has multiple eg. GitHub accounts. has multiple eg. GitHub accounts.
### */
getProfiles: ( socialNetwork ) -> getProfiles( socialNetwork ) {
socialNetwork = socialNetwork.trim().toLowerCase() socialNetwork = socialNetwork.trim().toLowerCase();
@social && _.filter @social, (sn) -> return this.social && _.filter(this.social, sn => sn.network.trim().toLowerCase() === socialNetwork);
sn.network.trim().toLowerCase() == socialNetwork }
###* Determine if the sheet includes a specific skill. ### /** Determine if the sheet includes a specific skill. */
hasSkill: ( skill ) -> hasSkill( skill ) {
skill = skill.trim().toLowerCase() skill = skill.trim().toLowerCase();
@skills && _.some @skills, (sk) -> return this.skills && _.some(this.skills, sk =>
sk.keywords && _.some sk.keywords, (kw) -> sk.keywords && _.some(sk.keywords, kw => kw.trim().toLowerCase() === skill)
kw.trim().toLowerCase() == skill );
}
###* Validate the sheet against the FRESH Resume schema. ### /** Validate the sheet against the FRESH Resume schema. */
isValid: ( info ) -> isValid( info ) {
schemaObj = require 'fresh-resume-schema' const schemaObj = require('fresh-resume-schema');
validator = require 'is-my-json-valid' validator = require('is-my-json-valid');
validate = validator( schemaObj, { # See Note [1]. const validate = validator( schemaObj, { // See Note [1].
formats: { date: /^\d{4}(?:-(?:0[0-9]{1}|1[0-2]{1})(?:-[0-9]{2})?)?$/ } formats: { date: /^\d{4}(?:-(?:0[0-9]{1}|1[0-2]{1})(?:-[0-9]{2})?)?$/ }
}) });
ret = validate @ const ret = validate(this);
if !ret if (!ret) {
this.imp = this.imp || { }; this.imp = this.imp || { };
this.imp.validationErrors = validate.errors; this.imp.validationErrors = validate.errors;
ret }
return ret;
}
duration: (unit) -> duration(unit) {
inspector = require '../inspectors/duration-inspector' const inspector = require('../inspectors/duration-inspector');
inspector.run @, 'employment.history', 'start', 'end', unit 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: () -> sort() {
byDateDesc = (a,b) -> const byDateDesc = function(a,b) {
if a.safe.start.isBefore(b.safe.start) if (a.safe.start.isBefore(b.safe.start)) {
then 1 return 1;
else ( if a.safe.start.isAfter(b.safe.start) then -1 else 0 ) } else { if (a.safe.start.isAfter(b.safe.start)) { return -1; } else { return 0; } }
};
sortSection = ( key ) -> const sortSection = function( key ) {
ar = __.get this, key const ar = __.get(this, key);
if ar && ar.length if (ar && ar.length) {
datedThings = obj.filter (o) -> o.start const datedThings = obj.filter(o => o.start);
datedThings.sort( byDateDesc ); return datedThings.sort( byDateDesc );
}
};
sortSection 'employment.history' sortSection('employment.history');
sortSection 'education.history' sortSection('education.history');
sortSection 'service.history' sortSection('service.history');
sortSection 'projects' sortSection('projects');
@writing && @writing.sort (a, b) -> return this.writing && this.writing.sort(function(a, b) {
if a.safe.date.isBefore b.safe.date if (a.safe.date.isBefore(b.safe.date)) {
then 1 return 1;
else ( a.safe.date.isAfter(b.safe.date) && -1 ) || 0 } else { return ( a.safe.date.isAfter(b.safe.date) && -1 ) || 0; }
});
}
}
###* /**
Get the default (starter) sheet. Get the default (starter) sheet.
### */
FreshResume.default = () -> FreshResume.default = () => new FreshResume().parseJSON(require('fresh-resume-starter').fresh);
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 = ( obj ) -> FreshResume.stringify = function( obj ) {
replacer = ( key,value ) -> # Exclude these keys from stringification const replacer = function( key,value ) { // Exclude these keys from stringification
exKeys = ['imp', 'warnings', 'computed', 'filt', 'ctrl', 'index', const exKeys = ['imp', 'warnings', 'computed', 'filt', 'ctrl', 'index',
'safe', 'result', 'isModified', 'htmlPreview', 'display_progress_bar'] 'safe', 'result', 'isModified', 'htmlPreview', 'display_progress_bar'];
return if _.some( exKeys, (val) -> key.trim() == val ) if (_.some( exKeys, val => key.trim() === val)) {
then undefined else value return undefined; } else { return value; }
JSON.stringify obj, replacer, 2 };
return JSON.stringify(obj, replacer, 2);
};
###* /**
Convert human-friendly dates into formal Moment.js dates for all collections. Convert human-friendly dates into formal Moment.js dates for all collections.
We don't want to lose the raw textual date as entered by the user, so we store We don't want to lose the raw textual date as entered by the user, so we store
the Moment-ified date as a separate property with a prefix of .safe. For ex: 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 job.startDate is the date as entered by the user. job.safeStartDate is the
parsed Moment.js date that we actually use in processing. parsed Moment.js date that we actually use in processing.
### */
_parseDates = () -> var _parseDates = function() {
_fmt = require('./fluent-date').fmt const _fmt = require('./fluent-date').fmt;
that = @ const that = this;
# TODO: refactor recursion // TODO: refactor recursion
replaceDatesInObject = ( obj ) -> var replaceDatesInObject = function( obj ) {
return if !obj if (!obj) { return; }
if Object.prototype.toString.call( obj ) == '[object Array]' if (Object.prototype.toString.call( obj ) === '[object Array]') {
obj.forEach (elem) -> replaceDatesInObject( elem ) obj.forEach(elem => replaceDatesInObject( elem ));
return return;
else if typeof obj == 'object' } else if (typeof obj === 'object') {
if obj._isAMomentObject || obj.safe if (obj._isAMomentObject || obj.safe) {
return return;
Object.keys( obj ).forEach (key) -> replaceDatesInObject obj[key] }
['start','end','date'].forEach (val) -> Object.keys( obj ).forEach(key => replaceDatesInObject(obj[key]));
if (obj[val] != undefined) && (!obj.safe || !obj.safe[val]) ['start','end','date'].forEach(function(val) {
obj.safe = obj.safe || { } if ((obj[val] !== undefined) && (!obj.safe || !obj.safe[val])) {
obj.safe[ val ] = _fmt obj[val] obj.safe = obj.safe || { };
if obj[val] && (val == 'start') && !obj.end obj.safe[ val ] = _fmt(obj[val]);
obj.safe.end = _fmt 'current' if (obj[val] && (val === 'start') && !obj.end) {
return obj.safe.end = _fmt('current');
return return;
Object.keys( this ).forEach (member) -> }
replaceDatesInObject(that[member]) }
return });
return return;
}
};
Object.keys( this ).forEach(function(member) {
replaceDatesInObject(that[member]);
});
};
###* Export the Sheet function/ctor. ### /** Export the Sheet function/ctor. */
module.exports = FreshResume module.exports = FreshResume;
# Note 1: Adjust default date validation to allow YYYY and YYYY-MM formats // Note 1: Adjust default date validation to allow YYYY and YYYY-MM formats
# in addition to YYYY-MM-DD. The original regex: // in addition to YYYY-MM-DD. The original regex:
# //
# /^\d{4}-(?:0[0-9]{1}|1[0-2]{1})-[0-9]{2}$/ // /^\d{4}-(?:0[0-9]{1}|1[0-2]{1})-[0-9]{2}$/
# //

View File

@ -1,231 +1,264 @@
###* /*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* DS103: Rewrite code to no longer use __guard__
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
/**
Definition of the FRESHTheme class. Definition of the FRESHTheme class.
@module core/fresh-theme @module core/fresh-theme
@license MIT. See LICENSE.md for details. @license MIT. See LICENSE.md for details.
### */
FS = require 'fs' const FS = require('fs');
validator = require 'is-my-json-valid' const validator = require('is-my-json-valid');
_ = require 'underscore' const _ = require('underscore');
PATH = require 'path' const PATH = require('path');
parsePath = require 'parse-filepath' const parsePath = require('parse-filepath');
pathExists = require('path-exists').sync const pathExists = require('path-exists').sync;
EXTEND = require 'extend' const EXTEND = require('extend');
HMSTATUS = require './status-codes' const HMSTATUS = require('./status-codes');
moment = require 'moment' const moment = require('moment');
loadSafeJson = require '../utils/safe-json-loader' const loadSafeJson = require('../utils/safe-json-loader');
READFILES = require 'recursive-readdir-sync' const READFILES = require('recursive-readdir-sync');
### A representation of a FRESH theme asset. /* A representation of a FRESH theme asset.
@class FRESHTheme ### @class FRESHTheme */
class FRESHTheme class FRESHTheme {
constructor: () -> constructor() {
@baseFolder = 'src' this.baseFolder = 'src';
return }
### Open and parse the specified theme. ### /* Open and parse the specified theme. */
open: ( themeFolder ) -> open( themeFolder ) {
@folder = themeFolder this.folder = themeFolder;
# Open the [theme-name].json file; should have the same name as folder // Open the [theme-name].json file; should have the same name as folder
pathInfo = parsePath themeFolder const pathInfo = parsePath(themeFolder);
# Set up a formats hash for the theme // Set up a formats hash for the theme
formatsHash = { } let formatsHash = { };
# Load the theme // Load the theme
themeFile = PATH.join themeFolder, 'theme.json' const themeFile = PATH.join(themeFolder, 'theme.json');
themeInfo = loadSafeJson themeFile const themeInfo = loadSafeJson(themeFile);
if themeInfo.ex if (themeInfo.ex) {
throw throw{
fluenterror: fluenterror:
if themeInfo.ex.op == 'parse' themeInfo.ex.op === 'parse'
then HMSTATUS.parseError ? HMSTATUS.parseError
else HMSTATUS.readError : HMSTATUS.readError,
inner: themeInfo.ex.inner inner: themeInfo.ex.inner
};
}
that = this const that = this;
# Move properties from the theme JSON file to the theme object // Move properties from the theme JSON file to the theme object
EXTEND true, @, themeInfo.json EXTEND(true, this, themeInfo.json);
# Check for an "inherits" entry in the theme JSON. // Check for an "inherits" entry in the theme JSON.
if @inherits if (this.inherits) {
cached = { } const cached = { };
_.each @inherits, (th, key) -> _.each(this.inherits, function(th, key) {
# First, see if this is one of the predefined FRESH themes. There are // 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 // 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 // query the official source of truth: the fresh-themes repository, which
# mounts the themes conveniently by name to the module object, and which // mounts the themes conveniently by name to the module object, and which
# is embedded locally inside the HackMyResume installation. // is embedded locally inside the HackMyResume installation.
# TODO: merge this code with // TODO: merge this code with
themesObj = require 'fresh-themes' let themePath;
if _.has themesObj.themes, th const themesObj = require('fresh-themes');
if (_.has(themesObj.themes, th)) {
themePath = PATH.join( themePath = PATH.join(
parsePath( require.resolve('fresh-themes') ).dirname, parsePath( require.resolve('fresh-themes') ).dirname,
'/themes/', '/themes/',
th th
) );
else } else {
d = parsePath( th ).dirname const d = parsePath( th ).dirname;
themePath = PATH.join d, th 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 ) return formatsHash[ key ] = cached[ th ].getFormat( key );
});
}
# Load theme files // Load theme files
formatsHash = _load.call @, formatsHash formatsHash = _load.call(this, formatsHash);
# Cache // Cache
@formats = formatsHash this.formats = formatsHash;
# Set the official theme name // Set the official theme name
@name = parsePath( @folder ).name this.name = parsePath( this.folder ).name;
@ return this;
}
### Determine if the theme supports the specified output format. ### /* Determine if the theme supports the specified output format. */
hasFormat: ( fmt ) -> _.has @formats, fmt hasFormat( 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 ) -> @formats[ fmt ] getFormat( fmt ) { return this.formats[ fmt ]; }
}
### Load and parse theme source files. ### /* Load and parse theme source files. */
_load = (formatsHash) -> var _load = function(formatsHash) {
that = @ const that = this;
major = false const major = false;
tplFolder = PATH.join @folder, @baseFolder const tplFolder = PATH.join(this.folder, this.baseFolder);
copyOnly = ['.ttf','.otf', '.png','.jpg','.jpeg','.pdf'] const copyOnly = ['.ttf','.otf', '.png','.jpg','.jpeg','.pdf'];
# Iterate over all files in the theme folder, producing an array, fmts, // Iterate over all files in the theme folder, producing an array, fmts,
# containing info for each file. While we're doing that, also build up // containing info for each file. While we're doing that, also build up
# the formatsHash object. // the formatsHash object.
fmts = READFILES(tplFolder).map (absPath) -> const fmts = READFILES(tplFolder).map(function(absPath) {
_loadOne.call @, absPath, formatsHash, tplFolder return _loadOne.call(this, absPath, formatsHash, tplFolder);
, @ }
, this);
# Now, get all the CSS files... // Now, get all the CSS files...
@cssFiles = fmts.filter (fmt) -> fmt and (fmt.ext == 'css') this.cssFiles = fmts.filter(fmt => fmt && (fmt.ext === 'css'));
# For each CSS file, get its corresponding HTML file. It's possible that // 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 // 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. // creates a pure CSS override of an existing theme.
@cssFiles.forEach (cssf) -> this.cssFiles.forEach(function(cssf) {
idx = _.findIndex fmts, ( fmt ) -> const idx = _.findIndex(fmts, fmt => fmt && (fmt.pre === cssf.pre) && (fmt.ext === 'html'));
fmt && fmt.pre == cssf.pre && fmt.ext == 'html' cssf.major = false;
cssf.major = false if (idx > -1) {
if idx > -1 fmts[ idx ].css = cssf.data;
fmts[ idx ].css = cssf.data return fmts[ idx ].cssPath = cssf.path;
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
# Found a CSS file without an HTML file in a theme that inherits // from another theme. This is the override CSS file.
# from another theme. This is the override CSS file. return that.overrides = { file: cssf.path, data: cssf.data };
that.overrides = { file: cssf.path, data: cssf.data } }
}});
# Now, save all the javascript file paths to a theme property. // Now, save all the javascript file paths to a theme property.
jsFiles = fmts.filter (fmt) -> fmt and (fmt.ext == 'js') const jsFiles = fmts.filter(fmt => fmt && (fmt.ext === 'js'));
@.jsFiles = jsFiles.map (jsf) -> jsf['path'] this.jsFiles = jsFiles.map(jsf => jsf['path']);
formatsHash return formatsHash;
};
### Load a single theme file. ### /* Load a single theme file. */
_loadOne = ( absPath, formatsHash, tplFolder ) -> var _loadOne = function( absPath, formatsHash, tplFolder ) {
pathInfo = parsePath absPath const pathInfo = parsePath(absPath);
return if pathInfo.basename.toLowerCase() == 'theme.json' if (pathInfo.basename.toLowerCase() === 'theme.json') { return; }
absPathSafe = absPath.trim().toLowerCase() const absPathSafe = absPath.trim().toLowerCase();
outFmt = '' let outFmt = '';
act = 'copy' let act = 'copy';
isPrimary = false let isPrimary = false;
# If this is an "explicit" theme, all files of importance are specified in // If this is an "explicit" theme, all files of importance are specified in
# the "transform" section of the theme.json file. // the "transform" section of the theme.json file.
if @explicit if (this.explicit) {
outFmt = _.find Object.keys( @formats ), ( fmtKey ) -> outFmt = _.find(Object.keys( this.formats ), function( fmtKey ) {
fmtVal = @formats[ fmtKey ] const fmtVal = this.formats[ fmtKey ];
_.some fmtVal.transform, (fpath) -> return _.some(fmtVal.transform, function(fpath) {
absPathB = PATH.join( @folder, fpath ).trim().toLowerCase() const absPathB = PATH.join( this.folder, fpath ).trim().toLowerCase();
absPathB == absPathSafe return absPathB === absPathSafe;
, @ }
, @ , this);
act = 'transform' if outFmt }
, this);
if (outFmt) { act = 'transform'; }
}
if !outFmt if (!outFmt) {
# If this file lives in a specific format folder within the theme, // If this file lives in a specific format folder within the theme,
# such as "/latex" or "/html", then that format is the implicit output // such as "/latex" or "/html", then that format is the implicit output
# format for all files within the folder // format for all files within the folder
portion = pathInfo.dirname.replace tplFolder,'' const portion = pathInfo.dirname.replace(tplFolder,'');
if portion && portion.trim() if (portion && portion.trim()) {
return if portion[1] == '_' if (portion[1] === '_') { return; }
reg = /^(?:\/|\\)(html|latex|doc|pdf|png|partials)(?:\/|\\)?/ig const reg = /^(?:\/|\\)(html|latex|doc|pdf|png|partials)(?:\/|\\)?/ig;
res = reg.exec( portion ) const res = reg.exec( portion );
if res if (res) {
if res[1] != 'partials' if (res[1] !== 'partials') {
outFmt = res[1] outFmt = res[1];
act = 'transform' if !@explicit if (!this.explicit) { act = 'transform'; }
else } else {
@partials = @partials || [] this.partials = this.partials || [];
@partials.push( { name: pathInfo.name, path: absPath } ) this.partials.push( { name: pathInfo.name, path: absPath } );
return null return null;
}
}
}
}
# Otherwise, the output format is inferred from the filename, as in // Otherwise, the output format is inferred from the filename, as in
# compact-[outputformat].[extension], for ex, compact-pdf.html // compact-[outputformat].[extension], for ex, compact-pdf.html
if !outFmt if (!outFmt) {
idx = pathInfo.name.lastIndexOf '-' const idx = pathInfo.name.lastIndexOf('-');
outFmt = if idx == -1 then pathInfo.name else pathInfo.name.substr idx+1 outFmt = idx === -1 ? pathInfo.name : pathInfo.name.substr(idx+1);
act = 'transform' if !@explicit if (!this.explicit) { act = 'transform'; }
defFormats = require './default-formats' const defFormats = require('./default-formats');
isPrimary = _.some defFormats, (form) -> isPrimary = _.some(defFormats, form => (form.name === outFmt) && (pathInfo.extname !== '.css'));
form.name == outFmt and pathInfo.extname != '.css' }
# Make sure we have a valid formatsHash // 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 (__guard__(this.formats != null ? this.formats[outFmt ] : undefined, x => x.symLinks)) {
formatsHash[ outFmt ].symLinks = this.formats[ outFmt ].symLinks;
} }
# Move symlink descriptions from theme.json to the format // Create the file representation object
if @formats?[ outFmt ]?.symLinks const obj = {
formatsHash[ outFmt ].symLinks = @formats[ outFmt ].symLinks action: act,
primary: isPrimary,
# Create the file representation object path: absPath,
obj = orgPath: PATH.relative(tplFolder, absPath),
action: act ext: pathInfo.extname.slice(1),
primary: isPrimary title: friendlyName(outFmt),
path: absPath pre: outFmt,
orgPath: PATH.relative tplFolder, absPath // outFormat: outFmt || pathInfo.name,
ext: pathInfo.extname.slice 1 data: FS.readFileSync(absPath, 'utf8'),
title: friendlyName outFmt
pre: outFmt
# outFormat: outFmt || pathInfo.name,
data: FS.readFileSync absPath, 'utf8'
css: null css: null
};
# Add this file to the list of files for this format type. // Add this file to the list of files for this format type.
formatsHash[ outFmt ].files.push( obj ) formatsHash[ outFmt ].files.push( obj );
obj return obj;
};
### Return a more friendly name for certain formats. ### /* Return a more friendly name for certain formats. */
friendlyName = ( val ) -> var friendlyName = function( val ) {
val = (val && val.trim().toLowerCase()) || '' val = (val && val.trim().toLowerCase()) || '';
friendly = { yml: 'yaml', md: 'markdown', txt: 'text' } const friendly = { yml: 'yaml', md: 'markdown', txt: 'text' };
friendly[val] || val return friendly[val] || val;
};
module.exports = FRESHTheme module.exports = FRESHTheme;
function __guard__(value, transform) {
return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined;
}

View File

@ -1,304 +1,352 @@
###* /*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* DS206: Consider reworking classes to avoid initClass
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
/**
Definition of the JRSResume class. Definition of the JRSResume class.
@license MIT. See LICENSE.md for details. @license MIT. See LICENSE.md for details.
@module core/jrs-resume @module core/jrs-resume
### */
FS = require('fs') const FS = require('fs');
extend = require('extend') const extend = require('extend');
validator = require('is-my-json-valid') let validator = require('is-my-json-valid');
_ = require('underscore') const _ = require('underscore');
PATH = require('path') const PATH = require('path');
MD = require('marked') const MD = require('marked');
CONVERTER = require('fresh-jrs-converter') const CONVERTER = require('fresh-jrs-converter');
moment = require('moment') const moment = require('moment');
###* /**
A JRS resume or CV. JRS resumes are backed by JSON, and each JRSResume object A JRS resume or CV. JRS resumes are backed by JSON, and each JRSResume object
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 var JRSResume = (function() {
let clear = undefined;
JRSResume = class JRSResume {
static initClass() {
/** Reset the sheet to an empty state. */
clear = function( clearMeta ) {
clearMeta = ((clearMeta === undefined) && true) || clearMeta;
if (clearMeta) { delete this.imp; }
delete this.basics.computed; // Don't use Object.keys() here
delete this.work;
delete this.volunteer;
delete this.education;
delete this.awards;
delete this.publications;
delete this.interests;
delete this.skills;
return delete this.basics.profiles;
};
// extends AbstractResume
}
/** Initialize the the JSResume from string. */
parse( stringData, opts ) {
this.imp = this.imp != null ? this.imp : {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.
sort: Sort resume items by date.
compute: Prepare computed resume totals.
}
*/
parseJSON( rep, opts ) {
let scrubbed;
opts = opts || { };
if (opts.privatize) {
let ignoreList, privateList;
const scrubber = require('../utils/resume-scrubber');
// Ignore any element with the 'ignore: true' or 'private: true' designator.
({ scrubbed, ignoreList, privateList } = scrubber.scrubResume(rep, opts));
}
// Extend resume properties onto ourself.
extend(true, this, opts.privatize ? scrubbed : rep);
// Set up metadata
if (!(this.imp != null ? this.imp.processed : undefined)) {
// Set up metadata TODO: Clean up metadata on the object model.
opts = opts || { };
if ((opts.imp === undefined) || 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 === undefined) || opts.date) && _parseDates.call( this );
((opts.sort === undefined) || opts.sort) && this.sort();
if ((opts.compute === undefined) || 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( 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. */
saveAs( filename, format ) {
if (format === 'JRS') {
this.imp.file = filename || this.imp.file;
FS.writeFileSync( this.imp.file, this.stringify(), 'utf8' );
} else {
const newRep = CONVERTER.toFRESH(this);
const stringRep = CONVERTER.toSTRING(newRep);
FS.writeFileSync(filename, stringRep, 'utf8');
}
return this;
}
/** Return the resume format. */
format() { return 'JRS'; }
stringify() { return JRSResume.stringify( this ); }
/** Return a unique list of all keywords across all skills. */
keywords() {
let flatSkills = [];
if (this.skills && this.skills.length) {
this.skills.forEach( s => 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() {
return this.imp = this.imp != null ? this.imp : { };
}
/** Add work experience to the sheet. */
add( moniker ) {
const defSheet = JRSResume.default();
const newObject = $.extend( true, {}, defSheet[ moniker ][0] );
this[ moniker ] = this[ moniker ] || [];
this[ moniker ].push( newObject );
return newObject;
}
###* Initialize the the JSResume from string. ### /** Determine if the sheet includes a specific social profile (eg, GitHub). */
parse: ( stringData, opts ) -> hasProfile( socialNetwork ) {
@imp = @imp ? raw: stringData socialNetwork = socialNetwork.trim().toLowerCase();
this.parseJSON JSON.parse( stringData ), opts return this.basics.profiles && _.some(this.basics.profiles, p => p.network.trim().toLowerCase() === socialNetwork);
}
###* /** Determine if the sheet includes a specific skill. */
Initialize the JRSResume object from JSON. hasSkill( skill ) {
Open and parse the specified JRS resume. Merge the JSON object model onto skill = skill.trim().toLowerCase();
this Sheet instance with extend() and convert sheet dates to a safe & return this.skills && _.some(this.skills, sk =>
consistent format. Then sort each section by startDate descending. sk.keywords && _.some(sk.keywords, kw => kw.trim().toLowerCase() === skill)
@param rep {Object} The raw JSON representation. );
@param opts {Object} Resume loading and parsing options. }
{
date: Perform safe date conversion.
sort: Sort resume items by date.
compute: Prepare computed resume totals.
}
###
parseJSON: ( rep, opts ) ->
opts = opts || { };
if opts.privatize
scrubber = require '../utils/resume-scrubber'
# Ignore any element with the 'ignore: true' or 'private: true' designator.
{ scrubbed, ignoreList, privateList } = scrubber.scrubResume rep, opts
# Extend resume properties onto ourself.
extend true, this, if opts.privatize then scrubbed else rep
# Set up metadata
if !@imp?.processed
# Set up metadata TODO: Clean up metadata on the object model.
opts = opts || { }
if opts.imp == undefined || opts.imp
@imp = @imp || { }
@imp.title = (opts.title || @imp.title) || @basics.name
unless @imp.raw
@imp.raw = JSON.stringify rep
@imp.processed = true
# Parse dates, sort dates, and calculate computed values
(opts.date == undefined || opts.date) && _parseDates.call( this )
(opts.sort == undefined || opts.sort) && this.sort()
if opts.compute == undefined || opts.compute
@basics.computed =
numYears: this.duration()
keywords: this.keywords()
@
/** Validate the sheet against the JSON Resume schema. */
isValid( ) { // TODO: ↓ fix this path ↓
const schema = FS.readFileSync(PATH.join( __dirname, 'resume.json' ), 'utf8');
const schemaObj = JSON.parse(schema);
validator = require('is-my-json-valid');
const validate = validator( schemaObj, { // Note [1]
formats: { date: /^\d{4}(?:-(?:0[0-9]{1}|1[0-2]{1})(?:-[0-9]{2})?)?$/ }
});
const temp = this.imp;
delete this.imp;
const ret = validate(this);
this.imp = temp;
if (!ret) {
this.imp = this.imp || { };
this.imp.validationErrors = validate.errors;
}
return ret;
}
###* Save the sheet to disk (for environments that have disk access). ###
save: ( filename ) ->
@imp.file = filename || @imp.file
FS.writeFileSync @imp.file, @stringify( this ), 'utf8'
@
duration(unit) {
const inspector = require('../inspectors/duration-inspector');
return inspector.run(this, 'work', 'startDate', 'endDate', unit);
}
###* Save the sheet to disk in a specific format, either FRESH or JRS. ###
saveAs: ( filename, format ) ->
if format == 'JRS'
@imp.file = filename || @imp.file;
FS.writeFileSync( @imp.file, @stringify(), 'utf8' );
else
newRep = CONVERTER.toFRESH @
stringRep = CONVERTER.toSTRING newRep
FS.writeFileSync filename, stringRep, 'utf8'
@
/**
Sort dated things on the sheet by start date descending. Assumes that dates
on the sheet have been processed with _parseDates().
*/
sort( ) {
const byDateDesc = function(a,b) {
if (a.safeStartDate.isBefore(b.safeStartDate)) {
return 1;
} else { return ( a.safeStartDate.isAfter(b.safeStartDate) && -1 ) || 0; }
};
###* Return the resume format. ### this.work && this.work.sort(byDateDesc);
format: () -> 'JRS' 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; }
});
}
stringify: () -> JRSResume.stringify( @ )
dupe() {
const rnew = new JRSResume();
rnew.parse(this.stringify(), { });
return rnew;
}
###* Return a unique list of all keywords across all skills. ###
keywords: () ->
flatSkills = []
if @skills && this.skills.length
@skills.forEach ( s ) -> flatSkills = _.union flatSkills, s.keywords
flatSkills
/**
Create a copy of this resume in which all fields have been interpreted as
Markdown.
*/
harden() {
###* const ret = this.dupe();
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: () ->
@imp = @imp ? { }
const HD = txt => `@@@@~${txt}~@@@@`;
const HDIN = txt =>
//return MD(txt || '' ).replace(/^\s*<p>|<\/p>\s*$/gi, '');
HD(txt)
;
###* Reset the sheet to an empty state. ### const transformer = require('../utils/string-transformer');
clear = ( clearMeta ) -> return transformer(ret,
clearMeta = ((clearMeta == undefined) && true) || clearMeta; [ 'skills','url','website','startDate','endDate', 'releaseDate', 'date',
delete this.imp if clearMeta 'phone','email','address','postalCode','city','country','region',
delete this.basics.computed # Don't use Object.keys() here 'safeStartDate','safeEndDate' ],
delete this.work (key, val) => HD(val));
delete this.volunteer }
delete this.education };
delete this.awards JRSResume.initClass();
delete this.publications return JRSResume;
delete this.interests })();
delete this.skills
delete this.basics.profiles
###* Add work experience to the sheet. ### /** Get the default (empty) sheet. */
add: ( moniker ) -> JRSResume.default = () => new JRSResume().parseJSON(require('fresh-resume-starter').jrs);
defSheet = JRSResume.default()
newObject = $.extend( true, {}, defSheet[ moniker ][0] )
this[ moniker ] = this[ moniker ] || []
this[ moniker ].push( newObject )
newObject
###* Determine if the sheet includes a specific social profile (eg, GitHub). ### /**
hasProfile: ( socialNetwork ) ->
socialNetwork = socialNetwork.trim().toLowerCase()
return @basics.profiles && _.some @basics.profiles, (p) ->
return p.network.trim().toLowerCase() == socialNetwork
###* Determine if the sheet includes a specific skill. ###
hasSkill: ( skill ) ->
skill = skill.trim().toLowerCase()
return this.skills && _.some this.skills, (sk) ->
return sk.keywords && _.some sk.keywords, (kw) ->
kw.trim().toLowerCase() == skill
###* Validate the sheet against the JSON Resume schema. ###
isValid: ( ) -> # TODO: fix this path
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 = @imp
delete @imp
ret = validate @
@imp = temp
if !ret
@imp = @imp || { };
@imp.validationErrors = validate.errors;
ret
duration: (unit) ->
inspector = require '../inspectors/duration-inspector';
inspector.run @, '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: ( ) ->
byDateDesc = (a,b) ->
if a.safeStartDate.isBefore(b.safeStartDate)
then 1
else ( a.safeStartDate.isAfter(b.safeStartDate) && -1 ) || 0
@work && @work.sort byDateDesc
@education && @education.sort byDateDesc
@volunteer && @volunteer.sort byDateDesc
@awards && @awards.sort (a, b) ->
if a.safeDate.isBefore b.safeDate
then 1
else (a.safeDate.isAfter(b.safeDate) && -1 ) || 0;
@publications && @publications.sort (a, b) ->
if ( a.safeReleaseDate.isBefore(b.safeReleaseDate) )
then 1
else ( a.safeReleaseDate.isAfter(b.safeReleaseDate) && -1 ) || 0
dupe: () ->
rnew = new JRSResume()
rnew.parse this.stringify(), { }
rnew
###*
Create a copy of this resume in which all fields have been interpreted as
Markdown.
###
harden: () ->
ret = @dupe()
HD = (txt) -> '@@@@~' + txt + '~@@@@'
HDIN = (txt) ->
#return MD(txt || '' ).replace(/^\s*<p>|<\/p>\s*$/gi, '');
return HD txt
transformer = require '../utils/string-transformer'
transformer ret,
[ 'skills','url','website','startDate','endDate', 'releaseDate', 'date',
'phone','email','address','postalCode','city','country','region',
'safeStartDate','safeEndDate' ],
(key, val) -> HD val
###* Get the default (empty) sheet. ###
JRSResume.default = () ->
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 = ( obj ) -> JRSResume.stringify = function( obj ) {
replacer = ( key,value ) -> # Exclude these keys from stringification const replacer = function( key,value ) { // Exclude these keys from stringification
temp = _.some ['imp', 'warnings', 'computed', 'filt', 'ctrl', 'index', const temp = _.some(['imp', 'warnings', 'computed', 'filt', 'ctrl', 'index',
'safeStartDate', 'safeEndDate', 'safeDate', 'safeReleaseDate', 'result', 'safeStartDate', 'safeEndDate', 'safeDate', 'safeReleaseDate', 'result',
'isModified', 'htmlPreview', 'display_progress_bar'], 'isModified', 'htmlPreview', 'display_progress_bar'],
( val ) -> return key.trim() == val val => key.trim() === val);
return if temp then undefined else value if (temp) { return undefined; } else { return value; }
JSON.stringify obj, replacer, 2 };
return JSON.stringify(obj, replacer, 2);
};
###* /**
Convert human-friendly dates into formal Moment.js dates for all collections. Convert human-friendly dates into formal Moment.js dates for all collections.
We don't want to lose the raw textual date as entered by the user, so we store We don't want to lose the raw textual date as entered by the user, so we store
the Moment-ified date as a separate property with a prefix of .safe. For ex: 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 job.startDate is the date as entered by the user. job.safeStartDate is the
parsed Moment.js date that we actually use in processing. parsed Moment.js date that we actually use in processing.
### */
_parseDates = () -> var _parseDates = function() {
_fmt = require('./fluent-date').fmt const _fmt = require('./fluent-date').fmt;
@work && @work.forEach (job) -> this.work && this.work.forEach(function(job) {
job.safeStartDate = _fmt( job.startDate ) job.safeStartDate = _fmt( job.startDate );
job.safeEndDate = _fmt( job.endDate ) return job.safeEndDate = _fmt( job.endDate );
@education && @education.forEach (edu) -> });
edu.safeStartDate = _fmt( edu.startDate ) this.education && this.education.forEach(function(edu) {
edu.safeEndDate = _fmt( edu.endDate ) edu.safeStartDate = _fmt( edu.startDate );
@volunteer && @volunteer.forEach (vol) -> return edu.safeEndDate = _fmt( edu.endDate );
vol.safeStartDate = _fmt( vol.startDate ) });
vol.safeEndDate = _fmt( vol.endDate ) this.volunteer && this.volunteer.forEach(function(vol) {
@awards && @awards.forEach (awd) -> vol.safeStartDate = _fmt( vol.startDate );
awd.safeDate = _fmt( awd.date ) return vol.safeEndDate = _fmt( vol.endDate );
@publications && @publications.forEach (pub) -> });
pub.safeReleaseDate = _fmt( pub.releaseDate ) this.awards && this.awards.forEach(awd => awd.safeDate = _fmt( awd.date ));
return this.publications && this.publications.forEach(pub => pub.safeReleaseDate = _fmt( pub.releaseDate ));
};
###* /**
Export the JRSResume function/ctor. Export the JRSResume function/ctor.
### */
module.exports = JRSResume module.exports = JRSResume;

View File

@ -1,51 +1,56 @@
###* /*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
/**
Definition of the JRSTheme class. Definition of the JRSTheme class.
@module core/jrs-theme @module core/jrs-theme
@license MIT. See LICENSE.MD for details. @license MIT. See LICENSE.MD for details.
### */
_ = require 'underscore' const _ = require('underscore');
PATH = require 'path' const PATH = require('path');
parsePath = require 'parse-filepath' const parsePath = require('parse-filepath');
pathExists = require('path-exists').sync const pathExists = require('path-exists').sync;
errors = require './status-codes' const 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
### */
class JRSTheme class JRSTheme {
###* /**
Open and parse the specified JRS theme. Open and parse the specified JRS theme.
@method open @method open
### */
open: ( thFolder ) -> open( thFolder ) {
@folder = thFolder this.folder = thFolder;
pathInfo = parsePath thFolder const pathInfo = parsePath(thFolder);
# Open and parse the theme's package.json file // Open and parse the theme's package.json file
pkgJsonPath = PATH.join thFolder, 'package.json' const pkgJsonPath = PATH.join(thFolder, 'package.json');
if pathExists pkgJsonPath if (pathExists(pkgJsonPath)) {
thApi = require thFolder # Requiring the folder yields whatever the package.json's "main" is set to const thApi = require(thFolder); // Requiring the folder yields whatever the package.json's "main" is set to
thPkg = require pkgJsonPath # Get the package.json as JSON const thPkg = require(pkgJsonPath); // Get the package.json as JSON
this.name = thPkg.name this.name = thPkg.name;
this.render = (thApi && thApi.render) || undefined this.render = (thApi && thApi.render) || undefined;
this.engine = 'jrs' this.engine = 'jrs';
# Create theme formats (HTML and PDF). Just add the bare minimum mix of // Create theme formats (HTML and PDF). Just add the bare minimum mix of
# properties necessary to allow JSON Resume themes to share a rendering // properties necessary to allow JSON Resume themes to share a rendering
# path with FRESH themes. // path with FRESH themes.
this.formats = this.formats = {
html: html: {
outFormat: 'html' outFormat: 'html',
files: [{ files: [{
action: 'transform', action: 'transform',
render: this.render, render: this.render,
@ -53,8 +58,9 @@ class JRSTheme
ext: 'html', ext: 'html',
css: null css: null
}] }]
pdf: },
outFormat: 'pdf' pdf: {
outFormat: 'pdf',
files: [{ files: [{
action: 'transform', action: 'transform',
render: this.render, render: this.render,
@ -62,25 +68,30 @@ class JRSTheme
ext: 'pdf', ext: 'pdf',
css: null css: null
}] }]
else }
throw fluenterror: errors.missingPackageJSON };
@ } else {
throw {fluenterror: errors.missingPackageJSON};
}
return this;
}
###* /**
Determine if the theme supports the output format. Determine if the theme supports the output format.
@method hasFormat @method hasFormat
### */
hasFormat: ( fmt ) -> _.has this.formats, fmt hasFormat( fmt ) { return _.has(this.formats, fmt); }
###* /**
Return the requested output format. Return the requested output format.
@method getFormat @method getFormat
### */
getFormat: ( fmt ) -> @formats[ fmt ] getFormat( fmt ) { return this.formats[ fmt ]; }
}
module.exports = JRSTheme; module.exports = JRSTheme;

View File

@ -1,33 +1,38 @@
###* /*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
/**
Definition of the ResumeFactory class. Definition of the ResumeFactory class.
@license MIT. See LICENSE.md for details. @license MIT. See LICENSE.md for details.
@module core/resume-factory @module core/resume-factory
### */
FS = require 'fs' const FS = require('fs');
HMS = require './status-codes' const HMS = require('./status-codes');
HME = require './event-codes' const HME = require('./event-codes');
ResumeConverter = require 'fresh-jrs-converter' const ResumeConverter = require('fresh-jrs-converter');
chalk = require 'chalk' const chalk = require('chalk');
SyntaxErrorEx = require '../utils/syntax-error-ex' const SyntaxErrorEx = require('../utils/syntax-error-ex');
_ = require 'underscore' const _ = require('underscore');
resumeDetect = require '../utils/resume-detector' const resumeDetect = require('../utils/resume-detector');
require 'string.prototype.startswith' require('string.prototype.startswith');
###* /**
A simple factory class for FRESH and JSON Resumes. A simple factory class for FRESH and JSON Resumes.
@class ResumeFactory @class ResumeFactory
### */
ResumeFactory = module.exports = const 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
@ -41,72 +46,85 @@ ResumeFactory = module.exports =
} }
} }
### */
load: ( sources, opts, emitter ) -> load( sources, opts, emitter ) {
sources.map( (src) -> return sources.map( function(src) {
@loadOne( src, opts, emitter ) return this.loadOne( src, opts, emitter );
, @) }
, this);
},
###* Load a single resume from disk. ### /** Load a single resume from disk. */
loadOne: ( src, opts, emitter ) -> loadOne( src, opts, emitter ) {
toFormat = opts.format # Can be null let toFormat = opts.format; // Can be null
# Get the destination format. Can be 'fresh', 'jrs', or null/undefined. // 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 // Load and parse the resume JSON
info = _parse src, opts, emitter const info = _parse(src, opts, emitter);
return info if info.fluenterror if (info.fluenterror) { return info; }
# Determine the resume format: FRESH or JRS // Determine the resume format: FRESH or JRS
json = info.json let { json } = info;
orgFormat = resumeDetect json const 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 // Convert between formats if necessary
if toFormat and ( 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 // Objectify the resume, that is, convert it from JSON to a FRESHResume
# or JRSResume object. // or JRSResume object.
rez = null let rez = null;
if opts.objectify if (opts.objectify) {
reqLib = '../core/' + (toFormat || orgFormat) + '-resume' const reqLib = `../core/${toFormat || orgFormat}-resume`;
ResumeClass = require reqLib const ResumeClass = require(reqLib);
rez = new ResumeClass().parseJSON( json, opts.inner ) rez = new ResumeClass().parseJSON( json, opts.inner );
rez.i().file = src rez.i().file = src;
}
file: src return {
json: info.json file: src,
rez: rez json: info.json,
rez
};
}
});
_parse = ( fileName, opts, eve ) -> var _parse = function( fileName, opts, eve ) {
rawData = null let rawData = null;
try try {
# Read the file // Read the file
eve && eve.stat( HME.beforeRead, { file: fileName }); eve && eve.stat( HME.beforeRead, { file: fileName });
rawData = FS.readFileSync( fileName, 'utf8' ); rawData = FS.readFileSync( fileName, 'utf8' );
eve && eve.stat( HME.afterRead, { file: fileName, data: rawData }); eve && eve.stat( HME.afterRead, { file: fileName, data: rawData });
# Parse the file // Parse the file
eve && eve.stat HME.beforeParse, { data: rawData } eve && eve.stat(HME.beforeParse, { data: rawData });
ret = { json: JSON.parse( rawData ) } const ret = { json: JSON.parse( rawData ) };
orgFormat = const orgFormat =
if ret.json.meta && ret.json.meta.format && ret.json.meta.format.startsWith('FRESH@') ret.json.meta && ret.json.meta.format && ret.json.meta.format.startsWith('FRESH@')
then 'fresh' else 'jrs' ? 'fresh' : 'jrs';
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 err } catch (err) {
# Can be ENOENT, EACCES, SyntaxError, etc. // Can be ENOENT, EACCES, SyntaxError, etc.
fluenterror: if rawData then HMS.parseError else HMS.readError return {
inner: err fluenterror: rawData ? HMS.parseError : HMS.readError,
raw: rawData inner: err,
file: fileName raw: rawData,
file: fileName
};
}
};

View File

@ -1,40 +1,41 @@
###* /**
Status codes for HackMyResume. Status codes for HackMyResume.
@module core/status-codes @module core/status-codes
@license MIT. See LICENSE.MD for details. @license MIT. See LICENSE.MD for details.
### */
module.exports = module.exports = {
success: 0 success: 0,
themeNotFound: 1 themeNotFound: 1,
copyCss: 2 copyCss: 2,
resumeNotFound: 3 resumeNotFound: 3,
missingCommand: 4 missingCommand: 4,
invalidCommand: 5 invalidCommand: 5,
resumeNotFoundAlt: 6 resumeNotFoundAlt: 6,
inputOutputParity: 7 inputOutputParity: 7,
createNameMissing: 8 createNameMissing: 8,
pdfGeneration: 9 pdfGeneration: 9,
missingPackageJSON: 10 missingPackageJSON: 10,
invalid: 11 invalid: 11,
invalidFormat: 12 invalidFormat: 12,
notOnPath: 13 notOnPath: 13,
readError: 14 readError: 14,
parseError: 15 parseError: 15,
fileSaveError: 16 fileSaveError: 16,
generateError: 17 generateError: 17,
invalidHelperUse: 18 invalidHelperUse: 18,
mixedMerge: 19 mixedMerge: 19,
invokeTemplate: 20 invokeTemplate: 20,
compileTemplate: 21 compileTemplate: 21,
themeLoad: 22 themeLoad: 22,
invalidParamCount: 23 invalidParamCount: 23,
missingParam: 24 missingParam: 24,
createError: 25 createError: 25,
validateError: 26 validateError: 26,
invalidOptionsFile: 27 invalidOptionsFile: 27,
optionsFileNotFound: 28 optionsFileNotFound: 28,
unknownSchema: 29 unknownSchema: 29,
themeHelperLoad: 30 themeHelperLoad: 30,
invalidSchemaVersion: 31 invalidSchemaVersion: 31
};

View File

@ -1,22 +1,37 @@
###* /*
* decaffeinate suggestions:
* DS206: Consider reworking classes to avoid initClass
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
/**
Definition of the BaseGenerator class. Definition of the BaseGenerator class.
@module generators/base-generator @module generators/base-generator
@license MIT. See LICENSE.md for details. @license MIT. See LICENSE.md for details.
### */
###* /**
The BaseGenerator class is the root of the generator hierarchy. Functionality The BaseGenerator class is the root of the generator hierarchy. Functionality
common to ALL generators lives here. common to ALL generators lives here.
### */
module.exports = class BaseGenerator let BaseGenerator;
module.exports = (BaseGenerator = (function() {
BaseGenerator = class BaseGenerator {
static initClass() {
/** Status codes. */
this.prototype.codes = require('../core/status-codes');
/** Generator options. */
this.prototype.opts = { };
}
###* Base-class initialize. ### /** Base-class initialize. */
constructor: ( @format ) -> constructor( format ) {
this.format = format;
###* Status codes. ### }
codes: require '../core/status-codes' };
BaseGenerator.initClass();
###* Generator options. ### return BaseGenerator;
opts: { } })());

View File

@ -1,30 +1,39 @@
###* /*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
/**
Definition of the HTMLGenerator class. Definition of the HTMLGenerator class.
@module generators/html-generator @module generators/html-generator
@license MIT. See LICENSE.md for details. @license MIT. See LICENSE.md for details.
### */
TemplateGenerator = require './template-generator' let HtmlGenerator;
FS = require 'fs-extra' const TemplateGenerator = require('./template-generator');
HTML = require 'html' const FS = require('fs-extra');
PATH = require 'path' const HTML = require('html');
require 'string.prototype.endswith' const PATH = require('path');
require('string.prototype.endswith');
module.exports = class HtmlGenerator extends TemplateGenerator module.exports = (HtmlGenerator = class HtmlGenerator extends TemplateGenerator {
constructor: -> super 'html' constructor() { super('html'); }
###* /**
Copy satellite CSS files to the destination and optionally pretty-print Copy satellite CSS files to the destination and optionally pretty-print
the HTML resume prior to saving. the HTML resume prior to saving.
### */
onBeforeSave: ( info ) -> onBeforeSave( info ) {
if info.outputFile.endsWith '.css' if (info.outputFile.endsWith('.css')) {
return info.mk return info.mk;
if @opts.prettify }
then HTML.prettyPrint info.mk, this.opts.prettify if (this.opts.prettify) {
else info.mk return HTML.prettyPrint(info.mk, this.opts.prettify);
} else { return info.mk; }
}
});

View File

@ -1,112 +1,130 @@
###* /*
* decaffeinate suggestions:
* DS103: Rewrite code to no longer use __guard__
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
/**
Definition of the HtmlPdfCLIGenerator class. Definition of the HtmlPdfCLIGenerator class.
@module generators/html-pdf-generator.js @module generators/html-pdf-generator.js
@license MIT. See LICENSE.md for details. @license MIT. See LICENSE.md for details.
### */
TemplateGenerator = require './template-generator' let HtmlPdfCLIGenerator;
FS = require 'fs-extra' const TemplateGenerator = require('./template-generator');
PATH = require 'path' const FS = require('fs-extra');
SLASH = require 'slash' const PATH = require('path');
_ = require 'underscore' const SLASH = require('slash');
HMSTATUS = require '../core/status-codes' const _ = require('underscore');
SPAWN = require '../utils/safe-spawn' const HMSTATUS = require('../core/status-codes');
const 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 = class HtmlPdfCLIGenerator extends TemplateGenerator module.exports = (HtmlPdfCLIGenerator = class HtmlPdfCLIGenerator extends TemplateGenerator {
constructor: () -> super 'pdf', 'html' constructor() { super('pdf', 'html'); }
###* Generate the binary PDF. ### /** Generate the binary PDF. */
onBeforeSave: ( info ) -> onBeforeSave( info ) {
#console.dir _.omit( info, 'mk' ), depth: null, colors: true //console.dir _.omit( info, 'mk' ), depth: null, colors: true
return info.mk if info.ext != 'html' and info.ext != 'pdf' if ((info.ext !== 'html') && (info.ext !== 'pdf')) { return info.mk; }
safe_eng = info.opts.pdf || 'wkhtmltopdf' let safe_eng = info.opts.pdf || 'wkhtmltopdf';
safe_eng = 'phantomjs' if safe_eng == 'phantom' if (safe_eng === 'phantom') { safe_eng = 'phantomjs'; }
if _.has engines, safe_eng if (_.has(engines, safe_eng)) {
@errHandler = info.opts.errHandler this.errHandler = info.opts.errHandler;
engines[ safe_eng ].call @, info.mk, info.outputFile, info.opts, @onError engines[ safe_eng ].call(this, info.mk, info.outputFile, info.opts, this.onError);
return null # halt further processing 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) -> onError(ex, param) {
param.errHandler?.err? HMSTATUS.pdfGeneration, ex __guardMethod__(param.errHandler, 'err', o => o.err(HMSTATUS.pdfGeneration, ex));
return }
});
# TODO: Move each engine to a separate module // TODO: Move each engine to a separate module
engines = var 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: (markup, fOut, opts, on_error) -> wkhtmltopdf(markup, fOut, opts, on_error) {
# Save the markup to a temporary file // Save the markup to a temporary file
tempFile = fOut.replace /\.pdf$/i, '.pdf.html' const tempFile = fOut.replace(/\.pdf$/i, '.pdf.html');
FS.writeFileSync tempFile, markup, 'utf8' FS.writeFileSync(tempFile, markup, 'utf8');
# Prepare wkhtmltopdf arguments. // Prepare wkhtmltopdf arguments.
wkopts = _.extend 'margin-top': '10mm', 'margin-bottom': '10mm', opts.wkhtmltopdf let wkopts = _.extend({'margin-top': '10mm', 'margin-bottom': '10mm'}, opts.wkhtmltopdf);
wkopts = _.flatten _.map wkopts, (v, k) -> ['--' + k, v] wkopts = _.flatten(_.map(wkopts, (v, k) => [`--${k}`, v]));
wkargs = wkopts.concat [ tempFile, fOut ] const wkargs = wkopts.concat([ tempFile, fOut ]);
SPAWN 'wkhtmltopdf', wkargs , false, on_error, @ SPAWN('wkhtmltopdf', wkargs , false, on_error, this);
return },
###* /**
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: ( markup, fOut, opts, on_error ) -> phantomjs( markup, fOut, opts, on_error ) {
# Save the markup to a temporary file // Save the markup to a temporary file
tempFile = fOut.replace /\.pdf$/i, '.pdf.html' const 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' ) let scriptPath = PATH.relative(process.cwd(), PATH.resolve( __dirname, '../utils/rasterize.js' ));
scriptPath = SLASH scriptPath scriptPath = SLASH(scriptPath);
sourcePath = SLASH PATH.relative( process.cwd(), tempFile) const sourcePath = SLASH(PATH.relative( process.cwd(), tempFile));
destPath = SLASH PATH.relative( process.cwd(), fOut) const destPath = SLASH(PATH.relative( process.cwd(), fOut));
SPAWN 'phantomjs', [ scriptPath, sourcePath, destPath ], false, on_error, @ SPAWN('phantomjs', [ scriptPath, sourcePath, destPath ], false, on_error, this);
return },
###* /**
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: ( markup, fOut, opts, on_error ) -> weasyprint( markup, fOut, opts, on_error ) {
# Save the markup to a temporary file // Save the markup to a temporary file
tempFile = fOut.replace /\.pdf$/i, '.pdf.html' const tempFile = fOut.replace(/\.pdf$/i, '.pdf.html');
FS.writeFileSync tempFile, markup, 'utf8' FS.writeFileSync(tempFile, markup, 'utf8');
SPAWN 'weasyprint', [tempFile, fOut], false, on_error, @ SPAWN('weasyprint', [tempFile, fOut], false, on_error, this);
return }
};
function __guardMethod__(obj, methodName, transform) {
if (typeof obj !== 'undefined' && obj !== null && typeof obj[methodName] === 'function') {
return transform(obj, methodName);
} else {
return undefined;
}
}

View File

@ -1,52 +1,58 @@
###* /*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
/**
Definition of the HtmlPngGenerator class. Definition of the HtmlPngGenerator class.
@module generators/html-png-generator @module generators/html-png-generator
@license MIT. See LICENSE.MD for details. @license MIT. See LICENSE.MD for details.
### */
TemplateGenerator = require './template-generator' let HtmlPngGenerator;
FS = require 'fs-extra' const TemplateGenerator = require('./template-generator');
HTML = require 'html' const FS = require('fs-extra');
SLASH = require 'slash' const HTML = require('html');
SPAWN = require '../utils/safe-spawn' const SLASH = require('slash');
PATH = require 'path' const SPAWN = require('../utils/safe-spawn');
const PATH = require('path');
###* /**
An HTML-based PNG resume generator for HackMyResume. An HTML-based PNG resume generator for HackMyResume.
### */
module.exports = class HtmlPngGenerator extends TemplateGenerator module.exports = (HtmlPngGenerator = class HtmlPngGenerator extends TemplateGenerator {
constructor: -> super 'png', 'html' constructor() { super('png', 'html'); }
invoke: ( rez, themeMarkup, cssInfo, opts ) -> invoke( rez, themeMarkup, cssInfo, opts ) {}
# TODO: Not currently called or callable. // TODO: Not currently called or callable.
generate: ( rez, f, opts ) -> generate( rez, f, opts ) {
htmlResults = opts.targets.filter (t) -> t.fmt.outFormat == 'html' const htmlResults = opts.targets.filter(t => t.fmt.outFormat === 'html');
htmlFile = htmlResults[0].final.files.filter (fl) -> const htmlFile = htmlResults[0].final.files.filter(fl => fl.info.ext === 'html');
fl.info.ext == 'html' phantom(htmlFile[0].data, f);
phantom htmlFile[0].data, f }
return });
###* /**
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
### */
phantom = ( markup, fOut ) -> var phantom = function( markup, fOut ) {
# Save the markup to a temporary file // Save the markup to a temporary file
tempFile = fOut.replace(/\.png$/i, '.png.html'); const tempFile = fOut.replace(/\.png$/i, '.png.html');
FS.writeFileSync tempFile, markup, 'utf8' FS.writeFileSync(tempFile, markup, 'utf8');
scriptPath = SLASH( PATH.relative( process.cwd(), const scriptPath = SLASH( PATH.relative( process.cwd(),
PATH.resolve( __dirname, '../utils/rasterize.js' ) ) ); PATH.resolve( __dirname, '../utils/rasterize.js' ) ) );
sourcePath = SLASH( PATH.relative( process.cwd(), tempFile) ); const sourcePath = SLASH( PATH.relative( process.cwd(), tempFile) );
destPath = SLASH( PATH.relative( process.cwd(), fOut) ); const destPath = SLASH( PATH.relative( process.cwd(), fOut) );
info = SPAWN('phantomjs', [ scriptPath, sourcePath, destPath ]); const info = SPAWN('phantomjs', [ scriptPath, sourcePath, destPath ]);
return };

View File

@ -1,25 +1,33 @@
###* /*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
/**
Definition of the JsonGenerator class. Definition of the JsonGenerator class.
@module generators/json-generator @module generators/json-generator
@license MIT. See LICENSE.md for details. @license MIT. See LICENSE.md for details.
### */
BaseGenerator = require './base-generator' let JsonGenerator;
FS = require 'fs' const BaseGenerator = require('./base-generator');
_ = require 'underscore' const FS = require('fs');
FJCV = require 'fresh-jrs-converter' const _ = require('underscore');
const 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 = class JsonGenerator extends BaseGenerator module.exports = (JsonGenerator = class JsonGenerator extends BaseGenerator {
constructor: () -> super 'json' constructor() { super('json'); }
invoke: ( rez ) -> invoke( rez ) {
altRez = FJCV[ 'to' + if rez.format() == 'FRESH' then 'JRS' else 'FRESH' ] rez let altRez = FJCV[ `to${rez.format() === 'FRESH' ? 'JRS' : 'FRESH'}` ](rez);
altRez = FJCV.toSTRING( altRez ) return altRez = FJCV.toSTRING( altRez );
#altRez.stringify() }
//altRez.stringify()
generate: ( rez, f ) -> generate( rez, f ) {
FS.writeFileSync f, @invoke(rez), 'utf8' FS.writeFileSync(f, this.invoke(rez), 'utf8');
return }
});

View File

@ -1,31 +1,40 @@
###* /*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
/**
Definition of the JsonYamlGenerator class. Definition of the JsonYamlGenerator class.
@module generators/json-yaml-generator @module generators/json-yaml-generator
@license MIT. See LICENSE.md for details. @license MIT. See LICENSE.md for details.
### */
BaseGenerator = require('./base-generator') let JsonYamlGenerator;
FS = require('fs') const BaseGenerator = require('./base-generator');
YAML = require('yamljs') const FS = require('fs');
const 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 = class JsonYamlGenerator extends BaseGenerator module.exports = (JsonYamlGenerator = class JsonYamlGenerator extends BaseGenerator {
constructor: () -> super 'yml' constructor() { super('yml'); }
invoke: ( rez, themeMarkup, cssInfo, opts ) -> invoke( rez, themeMarkup, cssInfo, opts ) {
YAML.stringify JSON.parse( rez.stringify() ), Infinity, 2 return YAML.stringify(JSON.parse( rez.stringify() ), Infinity, 2);
}
generate: ( rez, f, opts ) -> generate( rez, f, opts ) {
data = YAML.stringify JSON.parse( rez.stringify() ), Infinity, 2 const data = YAML.stringify(JSON.parse( rez.stringify() ), Infinity, 2);
FS.writeFileSync f, data, 'utf8' FS.writeFileSync(f, data, 'utf8');
data return data;
}
});

View File

@ -1,14 +1,16 @@
###* /**
Definition of the LaTeXGenerator class. Definition of the LaTeXGenerator class.
@module generators/latex-generator @module generators/latex-generator
@license MIT. See LICENSE.md for details. @license MIT. See LICENSE.md for details.
### */
TemplateGenerator = require './template-generator' let LaTeXGenerator;
const TemplateGenerator = require('./template-generator');
###* /**
LaTeXGenerator generates a LaTeX resume via TemplateGenerator. LaTeXGenerator generates a LaTeX resume via TemplateGenerator.
### */
module.exports = class LaTeXGenerator extends TemplateGenerator module.exports = (LaTeXGenerator = class LaTeXGenerator extends TemplateGenerator {
constructor: () -> super 'latex', 'tex' constructor() { super('latex', 'tex'); }
});

View File

@ -1,14 +1,16 @@
###* /**
Definition of the MarkdownGenerator class. Definition of the MarkdownGenerator class.
@module generators/markdown-generator @module generators/markdown-generator
@license MIT. See LICENSE.md for details. @license MIT. See LICENSE.md for details.
### */
TemplateGenerator = require './template-generator' let MarkdownGenerator;
const TemplateGenerator = require('./template-generator');
###* /**
MarkdownGenerator generates a Markdown-formatted resume via TemplateGenerator. MarkdownGenerator generates a Markdown-formatted resume via TemplateGenerator.
### */
module.exports = class MarkdownGenerator extends TemplateGenerator module.exports = (MarkdownGenerator = class MarkdownGenerator extends TemplateGenerator {
constructor: () -> super 'md', 'txt' constructor() { super('md', 'txt'); }
});

View File

@ -1,237 +1,279 @@
###* /*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
/**
Definition of the TemplateGenerator class. TODO: Refactor Definition of the TemplateGenerator class. TODO: Refactor
@module generators/template-generator @module generators/template-generator
@license MIT. See LICENSE.md for details. @license MIT. See LICENSE.md for details.
### */
FS = require 'fs-extra' let TemplateGenerator;
_ = require 'underscore' const FS = require('fs-extra');
MD = require 'marked' const _ = require('underscore');
XML = require 'xml-escape' const MD = require('marked');
PATH = require 'path' const XML = require('xml-escape');
parsePath = require 'parse-filepath' const PATH = require('path');
MKDIRP = require 'mkdirp' const parsePath = require('parse-filepath');
BaseGenerator = require './base-generator' const MKDIRP = require('mkdirp');
EXTEND = require 'extend' const BaseGenerator = require('./base-generator');
FRESHTheme = require '../core/fresh-theme' const EXTEND = require('extend');
JRSTheme = require '../core/jrs-theme' const FRESHTheme = require('../core/fresh-theme');
const 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 = class TemplateGenerator extends BaseGenerator module.exports = (TemplateGenerator = class TemplateGenerator extends BaseGenerator {
###* Constructor. Set the output format and template format for this /** Constructor. Set the output format and template format for this
generator. Will usually be called by a derived generator such as generator. Will usually be called by a derived generator such as
HTMLGenerator or MarkdownGenerator. ### HTMLGenerator or MarkdownGenerator. */
constructor: ( outputFormat, templateFormat, cssFile ) -> constructor( outputFormat, templateFormat, cssFile ) {
super outputFormat super(outputFormat);
@tplFormat = templateFormat || outputFormat this.tplFormat = templateFormat || outputFormat;
return }
###* Generate a resume using string-based inputs and outputs without touching /** Generate a resume using string-based inputs and outputs without touching
the filesystem. the filesystem.
@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 ) -> invoke( rez, opts ) {
opts = opts =
if opts opts
then (@opts = EXTEND( true, { }, _defaultOpts, opts )) ? (this.opts = EXTEND( true, { }, _defaultOpts, opts ))
else @opts : this.opts;
# Sort such that CSS files are processed before others // Sort such that CSS files are processed before others
curFmt = opts.themeObj.getFormat( this.format ) const curFmt = opts.themeObj.getFormat( this.format );
curFmt.files = _.sortBy curFmt.files, (fi) -> fi.ext != 'css' curFmt.files = _.sortBy(curFmt.files, fi => fi.ext !== 'css');
# Run the transformation! // Run the transformation!
results = curFmt.files.map ( tplInfo, idx ) -> const results = curFmt.files.map(function( tplInfo, idx ) {
if tplInfo.action == 'transform' let trx;
trx = @transform rez, tplInfo.data, @format, opts, opts.themeObj, curFmt if (tplInfo.action === 'transform') {
if tplInfo.ext == 'css' trx = this.transform(rez, tplInfo.data, this.format, opts, opts.themeObj, curFmt);
curFmt.files[idx].data = trx if (tplInfo.ext === 'css') {
else tplInfo.ext == 'html' curFmt.files[idx].data = trx;
#tplInfo.css contains the CSS data loaded by theme } else { tplInfo.ext === 'html'; }
#tplInfo.cssPath contains the absolute path to the source CSS File }
else //tplInfo.css contains the CSS data loaded by theme
# Images and non-transformable binary files //tplInfo.cssPath contains the absolute path to the source CSS File
opts.onTransform? tplInfo else {}
return info: tplInfo, data: trx // Images and non-transformable binary files
, @ if (typeof opts.onTransform === 'function') {
opts.onTransform(tplInfo);
}
return {info: tplInfo, data: trx};
}
, this);
files: results return {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 ) -> generate( rez, f, opts ) {
# Prepare // Prepare
@opts = EXTEND true, { }, _defaultOpts, opts this.opts = EXTEND(true, { }, _defaultOpts, opts);
# Call the string-based generation method // Call the string-based generation method
genInfo = @invoke rez, null const genInfo = this.invoke(rez, null);
outFolder = parsePath( f ).dirname const outFolder = parsePath( f ).dirname;
curFmt = opts.themeObj.getFormat @format const curFmt = opts.themeObj.getFormat(this.format);
# Process individual files within this format. For example, the HTML // Process individual files within this format. For example, the HTML
# output format for a theme may have multiple HTML files, CSS files, // output format for a theme may have multiple HTML files, CSS files,
# etc. Process them here. // etc. Process them here.
genInfo.files.forEach ( file ) -> genInfo.files.forEach(function( file ) {
# console.dir _.omit(file.info,'cssData','data','css' ) // console.dir _.omit(file.info,'cssData','data','css' )
# Pre-processing // Pre-processing
file.info.orgPath = file.info.orgPath || '' file.info.orgPath = file.info.orgPath || '';
thisFilePath = const thisFilePath =
if file.info.primary file.info.primary
then f ? f
else PATH.join outFolder, file.info.orgPath : PATH.join(outFolder, file.info.orgPath);
if file.info.action != 'copy' and @onBeforeSave if ((file.info.action !== 'copy') && this.onBeforeSave) {
file.data = this.onBeforeSave file.data = this.onBeforeSave({
theme: opts.themeObj theme: opts.themeObj,
outputFile: thisFilePath outputFile: thisFilePath,
mk: file.data mk: file.data,
opts: @opts, opts: this.opts,
ext: file.info.ext ext: file.info.ext
if !file.data });
return if (!file.data) {
return;
}
}
# Write the file // Write the file
opts.beforeWrite? data: thisFilePath if (typeof opts.beforeWrite === 'function') {
MKDIRP.sync PATH.dirname( thisFilePath ) opts.beforeWrite({data: 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? data: thisFilePath }
if (typeof opts.afterWrite === 'function') {
opts.afterWrite({data: thisFilePath});
}
# Post-processing // Post-processing
if @onAfterSave if (this.onAfterSave) {
@onAfterSave outputFile: fileName, mk: file.data, opts: this.opts return this.onAfterSave({outputFile: fileName, mk: file.data, opts: this.opts});
}
}
, @ , this);
# Some themes require a symlink structure. If so, create it. // Some themes require a symlink structure. If so, create it.
createSymLinks curFmt, outFolder createSymLinks(curFmt, outFolder);
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.
@param json A FRESH or JRS resume object. @param json A FRESH or JRS resume object.
@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 ) -> transform( json, jst, format, opts, theme, curFmt ) {
if @opts.freezeBreaks if (this.opts.freezeBreaks) {
jst = freeze jst jst = freeze(jst);
eng = require '../renderers/' + theme.engine + '-generator' }
result = eng.generate json, jst, format, curFmt, opts, theme const eng = require(`../renderers/${theme.engine}-generator`);
if this.opts.freezeBreaks let result = eng.generate(json, jst, format, curFmt, opts, theme);
result = unfreeze result if (this.opts.freezeBreaks) {
result result = unfreeze(result);
}
return result;
}
});
createSymLinks = ( curFmt, outFolder ) -> var createSymLinks = function( curFmt, outFolder ) {
# Some themes require a symlink structure. If so, create it. // Some themes require a symlink structure. If so, create it.
if curFmt.symLinks if (curFmt.symLinks) {
Object.keys( curFmt.symLinks ).forEach (loc) -> Object.keys( curFmt.symLinks ).forEach(function(loc) {
absLoc = PATH.join outFolder, loc const absLoc = PATH.join(outFolder, loc);
absTarg = PATH.join PATH.dirname(absLoc), curFmt.symLinks[loc] const absTarg = PATH.join(PATH.dirname(absLoc), curFmt.symLinks[loc]);
# Set type to 'file', 'dir', or 'junction' (Windows only) // Set type to 'file', 'dir', or 'junction' (Windows only)
type = if parsePath( absLoc ).extname then 'file' else 'junction' const type = parsePath( absLoc ).extname ? 'file' : 'junction';
try try {
FS.symlinkSync absTarg, absLoc, type return FS.symlinkSync(absTarg, absLoc, type);
catch err } catch (err) {
succeeded = false let succeeded = false;
if err.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;
if !succeeded } catch (error) {}
throw ex }
return if (!succeeded) {
throw ex;
}
}
});
return;
}
};
###* Freeze newlines for protection against errant JST parsers. ### /** Freeze newlines for protection against errant JST parsers. */
freeze = ( markup ) -> var freeze = function( markup ) {
markup.replace( _reg.regN, _defaultOpts.nSym ) markup.replace( _reg.regN, _defaultOpts.nSym );
markup.replace( _reg.regR, _defaultOpts.rSym ) return markup.replace( _reg.regR, _defaultOpts.rSym );
};
###* Unfreeze newlines when the coast is clear. ### /** Unfreeze newlines when the coast is clear. */
unfreeze = ( markup ) -> var unfreeze = function( markup ) {
markup.replace _reg.regSymR, '\r' markup.replace(_reg.regSymR, '\r');
markup.replace _reg.regSymN, '\n' return markup.replace(_reg.regSymN, '\n');
};
###* Default template generator options. ### /** Default template generator options. */
_defaultOpts = var _defaultOpts = {
engine: 'underscore' engine: 'underscore',
keepBreaks: true keepBreaks: true,
freezeBreaks: false freezeBreaks: false,
nSym: '&newl;' # newline entity nSym: '&newl;', // newline entity
rSym: '&retn;' # return entity rSym: '&retn;', // return entity
template: template: {
interpolate: /\{\{(.+?)\}\}/g interpolate: /\{\{(.+?)\}\}/g,
escape: /\{\{\=(.+?)\}\}/g escape: /\{\{\=(.+?)\}\}/g,
evaluate: /\{\%(.+?)\%\}/g evaluate: /\{\%(.+?)\%\}/g,
comment: /\{\#(.+?)\#\}/g comment: /\{\#(.+?)\#\}/g
filters: },
out: ( txt ) -> txt filters: {
raw: ( txt ) -> txt out( txt ) { return txt; },
xml: ( txt ) -> XML(txt) raw( txt ) { return txt; },
md: ( txt ) -> MD( txt || '' ) xml( txt ) { return XML(txt); },
mdin: ( txt ) -> MD(txt || '' ).replace(/^\s*<p>|<\/p>\s*$/gi, '') md( txt ) { return MD( txt || '' ); },
lower: ( txt ) -> txt.toLowerCase() mdin( txt ) { return MD(txt || '' ).replace(/^\s*<p>|<\/p>\s*$/gi, ''); },
link: ( name, url ) -> lower( txt ) { return txt.toLowerCase(); },
return if url then '<a href="' + url + '">' + name + '</a>' else name link( name, url ) {
prettify: # See https://github.com/beautify-web/js-beautify#options if (url) { return `<a href="${url}">${name}</a>`; } else { return name; }
indent_size: 2 }
unformatted: ['em','strong','a'] },
max_char: 80 # See lib/html.js in above-linked repo prettify: { // ← See https://github.com/beautify-web/js-beautify#options
#wrap_line_length: 120, <-- Don't use this indent_size: 2,
unformatted: ['em','strong','a'],
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 = var _reg = {
regN: new RegExp( '\n', 'g' ) regN: new RegExp( '\n', 'g' ),
regR: new RegExp( '\r', 'g' ) regR: new RegExp( '\r', 'g' ),
regSymN: new RegExp( _defaultOpts.nSym, 'g' ) regSymN: new RegExp( _defaultOpts.nSym, 'g' ),
regSymR: new RegExp( _defaultOpts.rSym, 'g' ) regSymR: new RegExp( _defaultOpts.rSym, 'g' )
};

View File

@ -1,14 +1,16 @@
###* /**
Definition of the TextGenerator class. Definition of the TextGenerator class.
@module generators/text-generator @module generators/text-generator
@license MIT. See LICENSE.md for details. @license MIT. See LICENSE.md for details.
### */
TemplateGenerator = require './template-generator' let TextGenerator;
const 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 = class TextGenerator extends TemplateGenerator module.exports = (TextGenerator = class TextGenerator extends TemplateGenerator {
constructor: () -> super 'txt' constructor() { super('txt'); }
});

View File

@ -1,12 +1,14 @@
### /*
Definition of the WordGenerator class. Definition of the WordGenerator class.
@module generators/word-generator @module generators/word-generator
@license MIT. See LICENSE.md for details. @license MIT. See LICENSE.md for details.
### */
TemplateGenerator = require './template-generator' let WordGenerator;
const TemplateGenerator = require('./template-generator');
module.exports = class WordGenerator extends TemplateGenerator module.exports = (WordGenerator = class WordGenerator extends TemplateGenerator {
constructor: () -> super 'doc', 'xml' constructor() { super('doc', 'xml'); }
});

View File

@ -1,12 +1,14 @@
###* /**
Definition of the XMLGenerator class. Definition of the XMLGenerator class.
@license MIT. See LICENSE.md for details. @license MIT. See LICENSE.md for details.
@module generatprs/xml-generator @module generatprs/xml-generator
### */
BaseGenerator = require './base-generator' let XMLGenerator;
const BaseGenerator = require('./base-generator');
###* The XmlGenerator generates an XML resume via the TemplateGenerator. ### /** The XmlGenerator generates an XML resume via the TemplateGenerator. */
module.exports = class XMLGenerator extends BaseGenerator module.exports = (XMLGenerator = class XMLGenerator extends BaseGenerator {
constructor: () -> super 'xml' constructor() { super('xml'); }
});

View File

@ -1,16 +1,18 @@
###* /**
Definition of the YAMLGenerator class. Definition of the YAMLGenerator class.
@module yaml-generator.js @module yaml-generator.js
@license MIT. See LICENSE.md for details. @license MIT. See LICENSE.md for details.
### */
TemplateGenerator = require './template-generator' let YAMLGenerator;
const TemplateGenerator = require('./template-generator');
###* /**
YamlGenerator generates a YAML-formatted resume via TemplateGenerator. YamlGenerator generates a YAML-formatted resume via TemplateGenerator.
### */
module.exports = class YAMLGenerator extends TemplateGenerator module.exports = (YAMLGenerator = class YAMLGenerator extends TemplateGenerator {
constructor: () -> super 'yml', 'yml' constructor() { super('yml', 'yml'); }
});

View File

@ -1,66 +1,78 @@
###* /*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
/**
Block helper definitions for HackMyResume / FluentCV. Block helper definitions for HackMyResume / FluentCV.
@license MIT. See LICENSE.md for details. @license MIT. See LICENSE.md for details.
@module helpers/generic-helpers @module helpers/generic-helpers
### */
HMSTATUS = require '../core/status-codes' const HMSTATUS = require('../core/status-codes');
LO = require 'lodash' const LO = require('lodash');
_ = require 'underscore' const _ = require('underscore');
unused = require '../utils/string' const unused = require('../utils/string');
###* Block helper function definitions. ### /** Block helper function definitions. */
BlockHelpers = module.exports = const 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: ( title, options ) -> section( title, options ) {
title = title.trim().toLowerCase() title = title.trim().toLowerCase();
obj = LO.get this.r, title const obj = LO.get(this.r, title);
ret = '' let ret = '';
if obj if (obj) {
if _.isArray obj if (_.isArray(obj)) {
if obj.length if (obj.length) {
ret = options.fn @ ret = options.fn(this);
else if _.isObject obj }
if (obj.history && obj.history.length) || (obj.sets && obj.sets.length) } else if (_.isObject(obj)) {
ret = options.fn @ if ((obj.history && obj.history.length) || (obj.sets && obj.sets.length)) {
ret ret = options.fn(this);
}
}
}
return ret;
},
ifHasSkill: ( rez, skill, options ) -> ifHasSkill( rez, skill, options ) {
skUp = skill.toUpperCase() const skUp = skill.toUpperCase();
ret = _.some rez.skills.list, (sk) -> const ret = _.some(rez.skills.list, sk => (skUp.toUpperCase() === sk.name.toUpperCase()) && sk.years
(skUp.toUpperCase() == sk.name.toUpperCase()) and sk.years , this);
, @ if (ret) { return options.fn(this); }
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.
### */
has: ( title, options ) -> has( 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 }
},
###* /**
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 ) { if (lhs || rhs) { return options.fn(this); } }
});

View File

@ -1,51 +1,67 @@
###* /*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
/**
Generic template helper definitions for command-line output. Generic template helper definitions for command-line output.
@module console-helpers.js @module console-helpers.js
@license MIT. See LICENSE.md for details. @license MIT. See LICENSE.md for details.
### */
PAD = require 'string-padding' const PAD = require('string-padding');
LO = require 'lodash' const LO = require('lodash');
CHALK = require 'chalk' const CHALK = require('chalk');
_ = require 'underscore' const _ = require('underscore');
require '../utils/string' require('../utils/string');
consoleFormatHelpers = module.exports = const consoleFormatHelpers = (module.exports = {
v: ( val, defaultVal, padding, style ) -> v( val, defaultVal, padding, style ) {
retVal = if ( val is null || val is undefined ) then defaultVal else val let retVal = ( (val === null) || (val === undefined) ) ? defaultVal : val;
spaces = 0 let spaces = 0;
if String.is padding if (String.is(padding)) {
spaces = parseInt padding, 10 spaces = parseInt(padding, 10);
spaces = 0 if isNaN spaces if (isNaN(spaces)) { spaces = 0; }
else if _.isNumber padding } else if (_.isNumber(padding)) {
spaces = padding spaces = padding;
}
if spaces != 0 if (spaces !== 0) {
retVal = PAD retVal, Math.abs(spaces), null, if spaces > 0 then PAD.LEFT else PAD.RIGHT retVal = PAD(retVal, Math.abs(spaces), null, spaces > 0 ? PAD.LEFT : PAD.RIGHT);
}
if style && String.is( style ) if (style && String.is( style )) {
retVal = LO.get( CHALK, style )( retVal ) retVal = LO.get( CHALK, style )( retVal );
retVal }
return retVal;
},
gapLength: (val) -> gapLength(val) {
if val < 35 if (val < 35) {
return CHALK.green.bold val return CHALK.green.bold(val);
else if val < 95 } else if (val < 95) {
return CHALK.yellow.bold val return CHALK.yellow.bold(val);
else } else {
return CHALK.red.bold val return CHALK.red.bold(val);
}
},
style: ( val, style ) -> style( val, style ) {
LO.get( CHALK, style )( val ) return LO.get( CHALK, style )( val );
},
isPlural: ( val, options ) -> isPlural( val, options ) {
if val > 1 if (val > 1) {
return options.fn(this) return options.fn(this);
}
},
pad: ( val, spaces ) -> pad( val, spaces ) {
PAD val, Math.abs(spaces), null, if spaces > 0 then PAD.LEFT else PAD.RIGHT return PAD(val, Math.abs(spaces), null, spaces > 0 ? PAD.LEFT : PAD.RIGHT);
}
});

File diff suppressed because it is too large Load Diff

View File

@ -1,77 +1,90 @@
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
###* /**
Template helper definitions for Handlebars. Template helper definitions for Handlebars.
@license MIT. See LICENSE.md for details. @license MIT. See LICENSE.md for details.
@module handlebars-helpers.js @module handlebars-helpers.js
### */
HANDLEBARS = require 'handlebars' const HANDLEBARS = require('handlebars');
_ = require 'underscore' const _ = require('underscore');
helpers = require './generic-helpers' const helpers = require('./generic-helpers');
path = require 'path' const path = require('path');
blockHelpers = require './block-helpers' const blockHelpers = require('./block-helpers');
HMS = require '../core/status-codes' const HMS = require('../core/status-codes');
###* /**
Register useful Handlebars helpers. Register useful Handlebars helpers.
@method registerHelpers @method registerHelpers
### */
module.exports = ( theme, rez, opts ) -> module.exports = function( 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 // Prepare generic helpers for use with Handlebars. We do this by wrapping them
# in a Handlebars-aware wrapper which calls the helper internally. // in a Handlebars-aware wrapper which calls the helper internally.
wrappedHelpers = _.mapObject helpers, ( hVal, hKey ) -> const wrappedHelpers = _.mapObject(helpers, function( hVal, hKey ) {
if _.isFunction hVal if (_.isFunction(hVal)) {
return _.wrap hVal, (func) -> return _.wrap(hVal, function(func) {
args = Array.prototype.slice.call arguments const args = Array.prototype.slice.call(arguments);
args.shift() # lose the 1st element (func) [^1] args.shift(); // lose the 1st element (func) [^1]
#args.pop() # lose the last element (HB options hash) //args.pop() # lose the last element (HB options hash)
args[ args.length - 1 ] = rez # replace w/ resume object args[ args.length - 1 ] = rez; // replace w/ resume object
func.apply @, args # call the generic helper return func.apply(this, args);
hVal }); // call the generic helper
, @ }
HANDLEBARS.registerHelper wrappedHelpers return hVal;
}
, this);
HANDLEBARS.registerHelper(wrappedHelpers);
# Prepare Handlebars-specific helpers - "blockHelpers" is really a misnomer // Prepare Handlebars-specific helpers - "blockHelpers" is really a misnomer
# since any kind of Handlebars-specific helper can live here // 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...
# Normalize "theme.helpers" (string or array) to an array // Normalize "theme.helpers" (string or array) to an array
theme.helpers = [ theme.helpers ] if _.isString theme.helpers if (_.isString(theme.helpers)) { theme.helpers = [ theme.helpers ]; }
if _.isArray theme.helpers if (_.isArray(theme.helpers)) {
glob = require 'glob' const glob = require('glob');
slash = require 'slash' const slash = require('slash');
curGlob = null let curGlob = null;
try try {
_.each theme.helpers, (fGlob) -> # foreach theme.helpers entry _.each(theme.helpers, function(fGlob) { // foreach theme.helpers entry
curGlob = fGlob # ..cache in case of exception curGlob = fGlob; // ..cache in case of exception
fGlob = path.join theme.folder, fGlob # ..make relative to theme fGlob = path.join(theme.folder, fGlob); // ..make relative to theme
files = glob.sync slash fGlob # ..expand the glob const files = glob.sync(slash(fGlob)); // ..expand the glob
if files.length > 0 # ..guard against empty glob if (files.length > 0) { // ..guard against empty glob
_.each files, (f) -> # ..loop over concrete paths _.each(files, function(f) { // ..loop over concrete paths
HANDLEBARS.registerHelper require f # ..register the path HANDLEBARS.registerHelper(require(f)); // ..register the path
return });
else } else {
throw fluenterror: HMS.themeHelperLoad, inner: er, glob: fGlob throw {fluenterror: HMS.themeHelperLoad, inner: er, glob: fGlob};
return }
return });
catch ex return;
throw } catch (ex) {
fluenterror: HMS.themeHelperLoad throw{
inner: ex fluenterror: HMS.themeHelperLoad,
inner: ex,
glob: curGlob, exit: true glob: curGlob, exit: true
return };
return;
}
}
};
# [^1]: This little bit of acrobatics ensures that our generic helpers are // [^1]: This little bit of acrobatics ensures that our generic helpers are
# called as generic helpers, not as Handlebars-specific helpers. This allows // 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 // 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 // Handlebars-specific helper with normal Handlebars context and options, put it
# in block-helpers.coffee. // in block-helpers.coffee.

View File

@ -1,29 +1,36 @@
###* /*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
/**
Template helper definitions for Underscore. Template helper definitions for Underscore.
@license MIT. See LICENSE.md for details. @license MIT. See LICENSE.md for details.
@module handlebars-helpers.js @module handlebars-helpers.js
### */
HANDLEBARS = require('handlebars') const HANDLEBARS = require('handlebars');
_ = require('underscore') const _ = require('underscore');
helpers = require('./generic-helpers') const helpers = require('./generic-helpers');
###* /**
Register useful Underscore helpers. Register useful Underscore helpers.
@method registerHelpers @method registerHelpers
### */
module.exports = ( 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;
helpers.cssInfo = cssInfo helpers.cssInfo = cssInfo;
helpers.engine = eng helpers.engine = eng;
ctx.h = helpers ctx.h = helpers;
_.each helpers, ( hVal, hKey ) -> _.each(helpers, function( hVal, hKey ) {
if _.isFunction hVal if (_.isFunction(hVal)) {
_.bind hVal, ctx return _.bind(hVal, ctx);
, @ }
return }
, this);
};

View File

@ -1,43 +1,46 @@
###* /**
External API surface for HackMyResume. External API surface for HackMyResume.
@license MIT. See LICENSE.md for details. @license MIT. See LICENSE.md for details.
@module hackmycore/index @module hackmycore/index
### */
###* API facade for HackMyResume. ### /** 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: {
generate: require './verbs/build' generate: require('./verbs/build'),
create: require './verbs/create' create: require('./verbs/create')
},
options: require './core/default-options' options: require('./core/default-options'),
formats: require './core/default-formats' formats: require('./core/default-formats'),
Sheet: require './core/fresh-resume' Sheet: require('./core/fresh-resume'),
FRESHResume: require './core/fresh-resume' FRESHResume: require('./core/fresh-resume'),
JRSResume: require './core/jrs-resume' JRSResume: require('./core/jrs-resume'),
FRESHTheme: require './core/fresh-theme' FRESHTheme: require('./core/fresh-theme'),
JRSTheme: require './core/jrs-theme' JRSTheme: require('./core/jrs-theme'),
ResumeFactory: require './core/resume-factory' ResumeFactory: require('./core/resume-factory'),
FluentDate: require './core/fluent-date' FluentDate: require('./core/fluent-date'),
HtmlGenerator: require './generators/html-generator' HtmlGenerator: require('./generators/html-generator'),
TextGenerator: require './generators/text-generator' TextGenerator: require('./generators/text-generator'),
HtmlPdfCliGenerator: require './generators/html-pdf-cli-generator' HtmlPdfCliGenerator: require('./generators/html-pdf-cli-generator'),
WordGenerator: require './generators/word-generator' WordGenerator: require('./generators/word-generator'),
MarkdownGenerator: require './generators/markdown-generator' MarkdownGenerator: require('./generators/markdown-generator'),
JsonGenerator: require './generators/json-generator' JsonGenerator: require('./generators/json-generator'),
YamlGenerator: require './generators/yaml-generator' YamlGenerator: require('./generators/yaml-generator'),
JsonYamlGenerator: require './generators/json-yaml-generator' JsonYamlGenerator: require('./generators/json-yaml-generator'),
LaTeXGenerator: require './generators/latex-generator' LaTeXGenerator: require('./generators/latex-generator'),
HtmlPngGenerator: require './generators/html-png-generator' HtmlPngGenerator: require('./generators/html-png-generator')
};

View File

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

View File

@ -1,26 +1,31 @@
###* /*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
/**
Employment gap analysis for HackMyResume. Employment gap analysis for HackMyResume.
@license MIT. See LICENSE.md for details. @license MIT. See LICENSE.md for details.
@module inspectors/gap-inspector @module inspectors/gap-inspector
### */
_ = require 'underscore' const _ = require('underscore');
FluentDate = require '../core/fluent-date' const FluentDate = require('../core/fluent-date');
moment = require 'moment' const moment = require('moment');
LO = require 'lodash' const LO = require('lodash');
###* /**
Identify gaps in the candidate's employment history. Identify gaps in the candidate's employment history.
### */
gapInspector = module.exports = const 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
@return {Array} An array of object representing gaps in the candidate's @return {Array} An array of object representing gaps in the candidate's
@ -31,109 +36,124 @@ gapInspector = module.exports =
end: // A Moment.js date end: // A Moment.js date
duration: // Gap length duration: // Gap length
} }
### */
run: (rez) -> run(rez) {
# This is what we'll return // This is what we'll return
coverage = const coverage = {
gaps: [] gaps: [],
overlaps: [] overlaps: [],
pct: '0%' pct: '0%',
duration: duration: {
total: 0 total: 0,
work: 0 work: 0,
gaps: 0 gaps: 0
}
};
# Missing employment section? Bye bye. // Missing employment section? Bye bye.
hist = LO.get rez, 'employment.history' const hist = LO.get(rez, 'employment.history');
return coverage if !hist || !hist.length if (!hist || !hist.length) { return coverage; }
# Convert the candidate's employment history to an array of dates, // 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 // where each element in the array is a start date or an end date of a
# job -- it doesn't matter which. // job -- it doesn't matter which.
new_e = hist.map( ( job ) -> let new_e = hist.map( function( job ) {
obj = _.pick( job, ['start', 'end'] ) let obj = _.pick( job, ['start', 'end'] );
if obj && (obj.start || obj.end) if (obj && (obj.start || obj.end)) {
obj = _.pairs( obj ) obj = _.pairs( obj );
obj[0][1] = FluentDate.fmt( obj[0][1] ) obj[0][1] = FluentDate.fmt( obj[0][1] );
if obj.length > 1 if (obj.length > 1) {
obj[1][1] = FluentDate.fmt( obj[1][1] ) obj[1][1] = FluentDate.fmt( obj[1][1] );
return obj }
) }
return obj;
});
# Flatten the array, remove empties, and sort // Flatten the array, remove empties, and sort
new_e = _.filter _.flatten( new_e, true ), (v) -> new_e = _.filter(_.flatten( new_e, true ), v => v && v.length && v[0] && v[0].length);
return v && v.length && v[0] && v[0].length
return coverage if !new_e || !new_e.length if (!new_e || !new_e.length) { return coverage; }
new_e = _.sortBy new_e, ( elem ) -> return elem[1].unix() new_e = _.sortBy(new_e, elem => elem[1].unix());
# Iterate over elements in the array. Each time a start date is found, // 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 // 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. // 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 // When the reference count is > 0, the candidate is employed. When the
# reference count reaches 2, the candidate is overlapped. // reference count reaches 2, the candidate is overlapped.
num_gaps = 0 const num_gaps = 0;
ref_count = 0 let ref_count = 0;
total_gap_days = 0 let total_gap_days = 0;
gap_start = null const gap_start = null;
new_e.forEach (point) -> new_e.forEach(function(point) {
inc = if point[0] == 'start' then 1 else -1 const inc = point[0] === 'start' ? 1 : -1;
ref_count += inc ref_count += inc;
# If the ref count just reached 0, start a new GAP // If the ref count just reached 0, start a new GAP
if ref_count == 0 if (ref_count === 0) {
coverage.gaps.push( { start: point[1], end: null }) return coverage.gaps.push( { start: point[1], end: null });
# If the ref count reached 1 by rising, end the last GAP // 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 ) const lastGap = _.last( coverage.gaps );
if lastGap if (lastGap) {
lastGap.end = point[1] lastGap.end = point[1];
lastGap.duration = lastGap.end.diff( lastGap.start, 'days' ) lastGap.duration = lastGap.end.diff( lastGap.start, 'days' );
total_gap_days += lastGap.duration return total_gap_days += lastGap.duration;
}
# If the ref count reaches 2 by rising, start a new OVERLAP // 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)) {
coverage.overlaps.push( { start: point[1], end: null }) return coverage.overlaps.push( { start: point[1], end: null });
# If the ref count reaches 1 by falling, end the last OVERLAP // 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 ) const lastOver = _.last( coverage.overlaps );
if lastOver if (lastOver) {
lastOver.end = point[1] lastOver.end = point[1];
lastOver.duration = lastOver.end.diff( lastOver.start, 'days' ) lastOver.duration = lastOver.end.diff( lastOver.start, 'days' );
if lastOver.duration == 0 if (lastOver.duration === 0) {
coverage.overlaps.pop() return coverage.overlaps.pop();
}
}
}
});
# It's possible that the last gap/overlap didn't have an explicit .end // 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 // date.If so, set the end date to the present date and compute the
# duration normally. // duration normally.
if coverage.overlaps.length if (coverage.overlaps.length) {
o = _.last( coverage.overlaps ) const o = _.last( coverage.overlaps );
if o && !o.end if (o && !o.end) {
o.end = moment() o.end = moment();
o.duration = o.end.diff( o.start, 'days' ) o.duration = o.end.diff( o.start, 'days' );
}
}
if coverage.gaps.length if (coverage.gaps.length) {
g = _.last( coverage.gaps ) const g = _.last( coverage.gaps );
if g && !g.end if (g && !g.end) {
g.end = moment() g.end = moment();
g.duration = g.end.diff( g.start, 'days' ) g.duration = g.end.diff( g.start, 'days' );
}
}
# Package data for return to the client // Package data for return to the client
tdur = rez.duration('days') const tdur = rez.duration('days');
dur = const dur = {
total: tdur total: tdur,
work: tdur - total_gap_days work: tdur - total_gap_days,
gaps: total_gap_days gaps: total_gap_days
};
coverage.pct = if dur.total > 0 && dur.work > 0 then ((((dur.total - dur.gaps) / dur.total) * 100)).toFixed(1) + '%' else '???' coverage.pct = (dur.total > 0) && (dur.work > 0) ? ((((dur.total - dur.gaps) / dur.total) * 100)).toFixed(1) + '%' : '???';
coverage.duration = dur coverage.duration = dur;
coverage return coverage;
}
});

View File

@ -1,68 +1,78 @@
###* /*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
/**
Keyword analysis for HackMyResume. Keyword analysis for HackMyResume.
@license MIT. See LICENSE.md for details. @license MIT. See LICENSE.md for details.
@module inspectors/keyword-inspector @module inspectors/keyword-inspector
### */
_ = require('underscore') const _ = require('underscore');
FluentDate = require('../core/fluent-date') const FluentDate = require('../core/fluent-date');
###* /**
Analyze the resume's use of keywords. Analyze the resume's use of keywords.
TODO: BUG: Keyword search regex is inaccurate, especially for one or two TODO: BUG: Keyword search regex is inaccurate, especially for one or two
letter keywords like "C" or "CLI". letter keywords like "C" or "CLI".
@class keywordInspector @class keywordInspector
### */
keywordInspector = module.exports = const 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: ( rez ) -> run( rez ) {
# "Quote" or safely escape a keyword so it can be used as a regex. For // "Quote" or safely escape a keyword so it can be used as a regex. For
# example, if the keyword is "C++", yield "C\+\+". // example, if the keyword is "C++", yield "C\+\+".
# http://stackoverflow.com/a/2593661/4942583 // http://stackoverflow.com/a/2593661/4942583
regex_quote = (str) -> (str + '').replace(/[.?*+^$[\]\\(){}|-]/ig, "\\$&") const regex_quote = str => (str + '').replace(/[.?*+^$[\]\\(){}|-]/ig, "\\$&");
# Create a searchable plain-text digest of the resume // Create a searchable plain-text digest of the resume
# TODO: BUG: Don't search within keywords for other keywords. Job A // TODO: BUG: Don't search within keywords for other keywords. Job A
# declares the "foo" keyword. Job B declares the "foo & bar" keyword. Job // 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". // 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 // To achieve this, remove keywords from the search digest and treat them
# separately. // separately.
searchable = '' let searchable = '';
rez.transformStrings ['imp', 'computed', 'safe'], ( key, val ) -> rez.transformStrings(['imp', 'computed', 'safe'], ( key, val ) => searchable += ` ${val}`);
searchable += ' ' + val
# Assemble a regex skeleton we can use to test for keywords with a bit // Assemble a regex skeleton we can use to test for keywords with a bit
# more // more
prefix = '(?:' + ['^', '\\s+', '[\\.,]+'].join('|') + ')' const prefix = `(?:${['^', '\\s+', '[\\.,]+'].join('|')})`;
suffix = '(?:' + ['$', '\\s+', '[\\.,]+'].join('|') + ')' const suffix = `(?:${['$', '\\s+', '[\\.,]+'].join('|')})`;
return rez.keywords().map (kw) -> return rez.keywords().map(function(kw) {
# 1. Using word boundary or other regex class is inaccurate // 1. Using word boundary or other regex class is inaccurate
# //
# var regex = new RegExp( '\\b' + regex_quote( kw )/* + '\\b'*/, 'ig'); // var regex = new RegExp( '\\b' + regex_quote( kw )/* + '\\b'*/, 'ig');
# //
# 2. Searching for the raw keyword is inaccurate ("C" will match any // 2. Searching for the raw keyword is inaccurate ("C" will match any
# word containing a 'c'!). // word containing a 'c'!).
# //
# var regex = new RegExp( regex_quote( kw ), 'ig'); // var regex = new RegExp( regex_quote( kw ), 'ig');
# //
# 3. Instead, use a custom regex with special delimeters. // 3. Instead, use a custom regex with special delimeters.
regex_str = prefix + regex_quote( kw ) + suffix const regex_str = prefix + regex_quote( kw ) + suffix;
regex = new RegExp( regex_str, 'ig') const regex = new RegExp( regex_str, 'ig');
myArray = null let myArray = null;
count = 0 let count = 0;
while (myArray = regex.exec( searchable )) != null while ((myArray = regex.exec( searchable )) !== null) {
count++ count++;
name: kw }
count: count return {
name: kw,
count
};
});
}
});

View File

@ -1,36 +1,47 @@
###* /*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
/**
Section analysis for HackMyResume. Section analysis for HackMyResume.
@license MIT. See LICENSE.md for details. @license MIT. See LICENSE.md for details.
@module inspectors/totals-inspector @module inspectors/totals-inspector
### */
_ = require 'underscore' const _ = require('underscore');
FluentDate = require '../core/fluent-date' const FluentDate = require('../core/fluent-date');
###* /**
Retrieve sectional overview and summary information. Retrieve sectional overview and summary information.
@class totalsInspector @class totalsInspector
### */
totalsInspector = module.exports = const 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: ( rez ) -> run( rez ) {
sectionTotals = { } const sectionTotals = { };
_.each rez, (val, key) -> _.each(rez, function(val, key) {
if _.isArray( val ) && !_.isString(val) if (_.isArray( val ) && !_.isString(val)) {
sectionTotals[ key ] = val.length return sectionTotals[ key ] = val.length;
else if val.history && _.isArray( val.history ) } else if (val.history && _.isArray( val.history )) {
sectionTotals[ key ] = val.history.length; return sectionTotals[ key ] = val.history.length;
else if val.sets && _.isArray( val.sets ) } else if (val.sets && _.isArray( val.sets )) {
sectionTotals[ key ] = val.sets.length; return sectionTotals[ key ] = val.sets.length;
}
});
totals: sectionTotals, return {
numSections: Object.keys( sectionTotals ).length totals: sectionTotals,
numSections: Object.keys( sectionTotals ).length
};
}
});

View File

@ -1,99 +1,119 @@
###* /*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
/**
Definition of the HandlebarsGenerator class. Definition of the HandlebarsGenerator class.
@license MIT. See LICENSE.md for details. @license MIT. See LICENSE.md for details.
@module renderers/handlebars-generator @module renderers/handlebars-generator
### */
_ = require 'underscore' const _ = require('underscore');
HANDLEBARS = require 'handlebars' const HANDLEBARS = require('handlebars');
FS = require 'fs' const FS = require('fs');
registerHelpers = require '../helpers/handlebars-helpers' const registerHelpers = require('../helpers/handlebars-helpers');
PATH = require 'path' const PATH = require('path');
parsePath = require 'parse-filepath' const parsePath = require('parse-filepath');
READFILES = require 'recursive-readdir-sync' const READFILES = require('recursive-readdir-sync');
HMSTATUS = require '../core/status-codes' const HMSTATUS = require('../core/status-codes');
SLASH = require 'slash' const SLASH = require('slash');
###* /**
Perform template-based resume generation using Handlebars.js. Perform template-based resume generation using Handlebars.js.
@class HandlebarsGenerator @class HandlebarsGenerator
### */
HandlebarsGenerator = module.exports = const HandlebarsGenerator = (module.exports = {
generateSimple: ( data, tpl ) -> generateSimple( data, tpl ) {
try let template;
# Compile and run the Handlebars template. try {
template = HANDLEBARS.compile tpl, // Compile and run the Handlebars template.
strict: false template = HANDLEBARS.compile(tpl, {
assumeObjects: false strict: false,
assumeObjects: false,
noEscape: data.opts.noescape noEscape: data.opts.noescape
return template data }
catch err );
throw return template(data);
} catch (err) {
throw{
fluenterror: fluenterror:
HMSTATUS[ if template then 'invokeTemplate' else 'compileTemplate' ] HMSTATUS[ template ? 'invokeTemplate' : 'compileTemplate' ],
inner: err inner: err
};
}
},
generate: ( json, jst, format, curFmt, opts, theme ) -> generate( json, jst, format, curFmt, opts, theme ) {
# Preprocess text // Preprocess text
encData = json let encData = json;
if format == 'html' || format == 'pdf' if ((format === 'html') || (format === 'pdf')) {
encData = json.markdownify() encData = json.markdownify();
if( format == 'doc' ) }
encData = json.xmlify() if( format === 'doc' ) {
encData = json.xmlify();
}
# Set up partials and helpers // Set up partials and helpers
registerPartials format, theme registerPartials(format, theme);
registerHelpers theme, encData, opts registerHelpers(theme, encData, opts);
# Set up the context // Set up the context
ctx = const ctx = {
r: encData r: encData,
RAW: json RAW: json,
filt: opts.filters filt: opts.filters,
format: format format,
opts: opts opts,
engine: @ engine: this,
results: curFmt.files results: curFmt.files,
headFragment: opts.headFragment || '' headFragment: opts.headFragment || ''
};
# Render the template // Render the template
return this.generateSimple ctx, jst return this.generateSimple(ctx, jst);
}
});
registerPartials = (format, theme) -> var registerPartials = function(format, theme) {
if _.contains( ['html','doc','md','txt','pdf'], format ) if (_.contains( ['html','doc','md','txt','pdf'], format )) {
# Locate the global partials folder // Locate the global partials folder
partialsFolder = PATH.join( const partialsFolder = PATH.join(
parsePath( require.resolve('fresh-themes') ).dirname, parsePath( require.resolve('fresh-themes') ).dirname,
'/partials/', '/partials/',
if format == 'pdf' then 'html' else format format === 'pdf' ? 'html' : format
) );
# Register global partials in the /partials/[format] folder // Register global partials in the /partials/[format] folder
# TODO: Only do this once per HMR invocation. // TODO: Only do this once per HMR invocation.
_.each READFILES( partialsFolder, (error)->{ }), ( el ) -> _.each(READFILES( partialsFolder, error=> ({ })), function( el ) {
pathInfo = parsePath el const pathInfo = parsePath(el);
name = SLASH PATH.relative( partialsFolder, el ).replace(/\.(?:html|xml|hbs|md|txt)$/i, '') const name = SLASH(PATH.relative( partialsFolder, el ).replace(/\.(?:html|xml|hbs|md|txt)$/i, ''));
tplData = FS.readFileSync el, 'utf8' const tplData = FS.readFileSync(el, 'utf8');
compiledTemplate = HANDLEBARS.compile tplData const compiledTemplate = HANDLEBARS.compile(tplData);
HANDLEBARS.registerPartial name, compiledTemplate HANDLEBARS.registerPartial(name, compiledTemplate);
theme.partialsInitialized = true return theme.partialsInitialized = true;
});
}
# Register theme-specific partials // Register theme-specific partials
_.each theme.partials, ( el ) -> return _.each(theme.partials, function( el ) {
tplData = FS.readFileSync el.path, 'utf8' const tplData = FS.readFileSync(el.path, 'utf8');
compiledTemplate = HANDLEBARS.compile tplData const compiledTemplate = HANDLEBARS.compile(tplData);
HANDLEBARS.registerPartial el.name, compiledTemplate return HANDLEBARS.registerPartial(el.name, compiledTemplate);
});
};

View File

@ -1,45 +1,53 @@
###* /*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
/**
Definition of the JRSGenerator class. Definition of the JRSGenerator class.
@license MIT. See LICENSE.md for details. @license MIT. See LICENSE.md for details.
@module renderers/jrs-generator @module renderers/jrs-generator
### */
_ = require('underscore') const _ = require('underscore');
HANDLEBARS = require('handlebars') const HANDLEBARS = require('handlebars');
FS = require('fs') const FS = require('fs');
registerHelpers = require('../helpers/handlebars-helpers') const registerHelpers = require('../helpers/handlebars-helpers');
PATH = require('path') const PATH = require('path');
parsePath = require('parse-filepath') const parsePath = require('parse-filepath');
READFILES = require('recursive-readdir-sync') const READFILES = require('recursive-readdir-sync');
SLASH = require('slash') const SLASH = require('slash');
MD = require('marked') const MD = require('marked');
###* /**
Perform template-based resume generation for JSON Resume themes. Perform template-based resume generation for JSON Resume themes.
@class JRSGenerator @class JRSGenerator
### */
JRSGenerator = module.exports = const JRSGenerator = (module.exports = {
generate: ( json, jst, format, cssInfo, opts, theme ) -> generate( json, jst, format, cssInfo, opts, theme ) {
# Disable JRS theme chatter (console.log, console.error, etc.) // Disable JRS theme chatter (console.log, console.error, etc.)
turnoff = ['log', 'error', 'dir']; const turnoff = ['log', 'error', 'dir'];
org = turnoff.map (c) -> const org = turnoff.map(function(c) {
ret = console[c] const ret = console[c];
console[c] = () -> console[c] = function() {};
ret return ret;
});
# Freeze and render // Freeze and render
rezHtml = theme.render json.harden() let rezHtml = theme.render(json.harden());
# Turn logging back on // Turn logging back on
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 /@@@@~[\s\S]*?~@@@@/g, (val) -> return rezHtml = rezHtml.replace(/@@@@~[\s\S]*?~@@@@/g, val => MDIN( val.replace( /~@@@@/g,'' ).replace( /@@@@~/g,'' ) ));
MDIN( val.replace( /~@@@@/g,'' ).replace( /@@@@~/g,'' ) ) }
});
MDIN = (txt) -> # TODO: Move this var MDIN = txt => // TODO: Move this
MD(txt || '' ).replace(/^\s*<p>|<\/p>\s*$/gi, '') MD(txt || '' ).replace(/^\s*<p>|<\/p>\s*$/gi, '')
;

View File

@ -1,73 +1,90 @@
###* /*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
/**
Definition of the UnderscoreGenerator class. Definition of the UnderscoreGenerator class.
@license MIT. See LICENSE.md for details. @license MIT. See LICENSE.md for details.
@module underscore-generator.js @module underscore-generator.js
### */
_ = require 'underscore' const _ = require('underscore');
registerHelpers = require '../helpers/underscore-helpers' const registerHelpers = require('../helpers/underscore-helpers');
require '../utils/string' require('../utils/string');
escapeLaTeX = require 'escape-latex' const escapeLaTeX = require('escape-latex');
###* /**
Perform template-based resume generation using Underscore.js. Perform template-based resume generation using Underscore.js.
@class UnderscoreGenerator @class UnderscoreGenerator
### */
UnderscoreGenerator = module.exports = const UnderscoreGenerator = (module.exports = {
generateSimple: ( data, tpl ) -> generateSimple( data, tpl ) {
try let t;
# Compile and run the Handlebars template. try {
t = _.template tpl // Compile and run the Handlebars template.
t data t = _.template(tpl);
catch err return t(data);
#console.dir _error } catch (err) {
HMS = require '../core/status-codes' //console.dir _error
throw const HMS = require('../core/status-codes');
fluenterror: HMS[if t then 'invokeTemplate' else 'compileTemplate'] throw{
fluenterror: HMS[t ? 'invokeTemplate' : 'compileTemplate'],
inner: err inner: err
};
}
},
generate: ( json, jst, format, cssInfo, opts, theme ) -> generate( json, jst, format, cssInfo, opts, theme ) {
# Tweak underscore's default template delimeters // Tweak underscore's default template delimeters
delims = (opts.themeObj && opts.themeObj.delimeters) || opts.template; let delims = (opts.themeObj && opts.themeObj.delimeters) || opts.template;
if opts.themeObj && opts.themeObj.delimeters if (opts.themeObj && opts.themeObj.delimeters) {
delims = _.mapObject delims, (val,key) -> new RegExp val, "ig" delims = _.mapObject(delims, (val,key) => new RegExp(val, "ig"));
}
_.templateSettings = delims; _.templateSettings = delims;
# Massage resume strings / text // Massage resume strings / text
r = null let r = null;
switch format switch (format) {
when 'html' then r = json.markdownify() case 'html': r = json.markdownify(); break;
when 'pdf' then r = json.markdownify() case 'pdf': r = json.markdownify(); break;
when 'png' then r = json.markdownify() case 'png': r = json.markdownify(); break;
when 'latex' case 'latex':
traverse = require 'traverse' var traverse = require('traverse');
r = traverse(json).map (x) -> r = traverse(json).map(function(x) {
if @isLeaf && String.is @node if (this.isLeaf && String.is(this.node)) {
return escapeLaTeX @node return escapeLaTeX(this.node);
@node }
else r = json return this.node;
});
break;
default: r = json;
}
# Set up the context // Set up the context
ctx = const ctx = {
r: r r,
filt: opts.filters filt: opts.filters,
XML: require 'xml-escape' XML: require('xml-escape'),
RAW: json RAW: json,
cssInfo: cssInfo cssInfo,
#engine: @ //engine: @
headFragment: opts.headFragment || '' headFragment: opts.headFragment || '',
opts: opts opts
};
# Link to our helpers // Link to our helpers
registerHelpers theme, opts, cssInfo, ctx, @ registerHelpers(theme, opts, cssInfo, ctx, this);
# Generate! // Generate!
@generateSimple ctx, jst return this.generateSimple(ctx, jst);
}
});

View File

@ -1,7 +1,11 @@
###* /*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
/**
Definition of the SyntaxErrorEx class. Definition of the SyntaxErrorEx class.
@module file-contains.js @module file-contains.js
### */
module.exports = ( file, needle ) -> module.exports = ( file, needle ) => require('fs').readFileSync(file,'utf-8').indexOf( needle ) > -1;
require('fs').readFileSync(file,'utf-8').indexOf( needle ) > -1

View File

@ -1,23 +1,27 @@
###* /*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
/**
Defines a regex suitable for matching FRESH versions. Defines a regex suitable for matching FRESH versions.
@module file-contains.js @module file-contains.js
### */
# Set up a regex that matches all of the following: // Set up a regex that matches all of the following:
# //
# - FRESH // - FRESH
# - JRS // - JRS
# - FRESCA // - FRESCA
# - FRESH@1.0.0 // - FRESH@1.0.0
# - FRESH@1.0 // - FRESH@1.0
# - FRESH@1 // - FRESH@1
# - JRS@0.16.0 // - JRS@0.16.0
# - JRS@0.16 // - JRS@0.16
# - JRS@0 // - JRS@0
# //
# Don't use a SEMVER regex (eg, NPM's semver-regex) because a) we want to // 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 partial semvers like "0" or "1.2" and b) we'll expand this later to
# support fully scoped FRESH versions. // support fully scoped FRESH versions.
module.exports = () -> module.exports = () => RegExp('^(FRESH|FRESCA|JRS)(?:@(\\d+(?:\\.\\d+)?(?:\\.\\d+)?))?$');
RegExp '^(FRESH|FRESCA|JRS)(?:@(\\d+(?:\\.\\d+)?(?:\\.\\d+)?))?$'

View File

@ -1,50 +1,65 @@
###* /*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
/**
Definition of the Markdown to WordProcessingML conversion routine. Definition of the Markdown to WordProcessingML conversion routine.
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk. @license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
@module utils/html-to-wpml @module utils/html-to-wpml
### */
XML = require 'xml-escape' const XML = require('xml-escape');
_ = require 'underscore' const _ = require('underscore');
HTML5Tokenizer = require 'simple-html-tokenizer' const HTML5Tokenizer = require('simple-html-tokenizer');
module.exports = ( html ) -> module.exports = function( html ) {
# Tokenize the HTML stream. // Tokenize the HTML stream.
tokens = HTML5Tokenizer.tokenize( html ) let is_bold, is_italic, is_link, link_url;
final = is_bold = is_italic = is_link = link_url = '' const tokens = HTML5Tokenizer.tokenize( html );
let final = (is_bold = (is_italic = (is_link = (link_url = ''))));
# Process <em>, <strong>, and <a> elements in the HTML stream, producing // Process <em>, <strong>, and <a> elements in the HTML stream, producing
# equivalent WordProcessingML that can be dumped into a <w:p> or other // equivalent WordProcessingML that can be dumped into a <w:p> or other
# text container element. // text container element.
_.each tokens, ( tok ) -> _.each(tokens, function( tok ) {
switch tok.type switch (tok.type) {
when 'StartTag' case 'StartTag':
switch tok.tagName switch (tok.tagName) {
when 'p' then final += '<w:p>' case 'p': return final += '<w:p>';
when 'strong' then is_bold = true case 'strong': return is_bold = true;
when 'em' then is_italic = true case 'em': return is_italic = true;
when 'a' case 'a':
is_link = true; is_link = true;
link_url = tok.attributes.filter((attr) -> attr[0] == 'href' )[0][1]; return link_url = tok.attributes.filter(attr => attr[0] === 'href')[0][1];
}
break;
when 'EndTag' case 'EndTag':
switch tok.tagName switch (tok.tagName) {
when 'p' then final += '</w:p>' case 'p': return final += '</w:p>';
when 'strong' then is_bold = false case 'strong': return is_bold = false;
when 'em' then is_italic = false case 'em': return is_italic = false;
when 'a' then is_link = false case 'a': return is_link = false;
}
break;
when 'Chars' case 'Chars':
if( tok.chars.trim().length ) if( tok.chars.trim().length ) {
style = if is_bold then '<w:b/>' else '' let style = is_bold ? '<w:b/>' : '';
style += if is_italic then '<w:i/>' else '' style += is_italic ? '<w:i/>' : '';
style += if is_link then '<w:rStyle w:val="Hyperlink"/>' else '' style += is_link ? '<w:rStyle w:val="Hyperlink"/>' : '';
final += return final +=
(if is_link then ('<w:hlink w:dest="' + link_url + '">') else '') + (is_link ? (`<w:hlink w:dest="${link_url}">`) : '') +
'<w:r><w:rPr>' + style + '</w:rPr><w:t>' + XML(tok.chars) + '<w:r><w:rPr>' + style + '</w:rPr><w:t>' + XML(tok.chars) +
'</w:t></w:r>' + (if is_link then '</w:hlink>' else '') '</w:t></w:r>' + (is_link ? '</w:hlink>' : '');
final }
break;
}
});
return final;
};

View File

@ -1,15 +1,21 @@
###* /*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
/**
Inline Markdown-to-Chalk conversion routines. Inline Markdown-to-Chalk conversion routines.
@license MIT. See LICENSE.md for details. @license MIT. See LICENSE.md for details.
@module utils/md2chalk @module utils/md2chalk
### */
MD = require 'marked' const MD = require('marked');
CHALK = require 'chalk' const CHALK = require('chalk');
LO = require 'lodash' const LO = require('lodash');
module.exports = ( v, style, boldStyle ) -> module.exports = function( v, style, boldStyle ) {
boldStyle = boldStyle || 'bold' boldStyle = boldStyle || 'bold';
temp = v.replace(/\*\*(.*?)\*\*/g, LO.get( CHALK, boldStyle )('$1')) const temp = v.replace(/\*\*(.*?)\*\*/g, LO.get( CHALK, boldStyle )('$1'));
if style then LO.get( CHALK, style )(temp) else temp if (style) { return LO.get( CHALK, style )(temp); } else { return temp; }
};

View File

@ -1,58 +1,71 @@
# Exemplar script for generating documents with Phantom.js. /*
# https://raw.githubusercontent.com/ariya/phantomjs/master/examples/rasterize.js * decaffeinate suggestions:
# Converted to CoffeeScript by hacksalot * DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
// 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";let output, size;
page = require('webpage').create()
system = require('system')
address = output = size = null
if system.args.length < 3 || system.args.length > 5 const page = require('webpage').create();
const system = require('system');
let address = (output = (size = null));
console.log 'Usage: rasterize.js URL filename [paperwidth*paperheight|paperformat] [zoom]' if ((system.args.length < 3) || (system.args.length > 5)) {
console.log ' paper (pdf output) examples: "5in*7.5in", "10cm*20cm", "A4", "Letter"'
console.log ' image (png/jpg output) examples: "1920px" entire page, window width 1920px'
console.log ' "800px*600px" window, clipped to 800x600'
phantom.exit 1
else console.log('Usage: rasterize.js URL filename [paperwidth*paperheight|paperformat] [zoom]');
console.log(' paper (pdf output) examples: "5in*7.5in", "10cm*20cm", "A4", "Letter"');
console.log(' image (png/jpg output) examples: "1920px" entire page, window width 1920px');
console.log(' "800px*600px" window, clipped to 800x600');
phantom.exit(1);
address = system.args[1] } else {
output = system.args[2]
page.viewportSize = width: 600, height: 600
if system.args.length > 3 and system.args[2].substr(-4) == ".pdf" address = system.args[1];
output = system.args[2];
page.viewportSize = {width: 600, height: 600};
size = system.args[3].split('*') if ((system.args.length > 3) && (system.args[2].substr(-4) === ".pdf")) {
size = system.args[3].split('*');
page.paperSize = page.paperSize =
if size.length == 2 then width: size[0], height: size[1], margin: '0px' size.length === 2 ? {width: size[0], height: size[1], margin: '0px'}
else format: system.args[3], orientation: 'portrait', margin: '1cm' : {format: system.args[3], orientation: 'portrait', margin: '1cm'};
else if system.args.length > 3 && system.args[3].substr(-2) == "px" } else if ((system.args.length > 3) && (system.args[3].substr(-2) === "px")) {
size = system.args[3].split '*' let pageHeight, pageWidth;
if size.length == 2 size = system.args[3].split('*');
pageWidth = parseInt size[0], 10 if (size.length === 2) {
pageHeight = parseInt size[1], 10 pageWidth = parseInt(size[0], 10);
page.viewportSize = width: pageWidth, height: pageHeight pageHeight = parseInt(size[1], 10);
page.clipRect = top: 0, left: 0, width: pageWidth, height: pageHeight page.viewportSize = {width: pageWidth, height: pageHeight};
else page.clipRect = {top: 0, left: 0, width: pageWidth, height: pageHeight};
console.log "size:", system.args[3] } else {
pageWidth = parseInt system.args[3], 10 console.log("size:", system.args[3]);
pageHeight = parseInt pageWidth * 3/4, 10 # it's as good an assumption as any pageWidth = parseInt(system.args[3], 10);
console.log "pageHeight:", pageHeight pageHeight = parseInt((pageWidth * 3)/4, 10); // it's as good an assumption as any
page.viewportSize = width: pageWidth, height: pageHeight console.log("pageHeight:", pageHeight);
page.viewportSize = {width: pageWidth, height: pageHeight};
}
}
if system.args.length > 4 if (system.args.length > 4) {
page.zoomFactor = system.args[4] page.zoomFactor = system.args[4];
}
page.open address, (status) -> page.open(address, function(status) {
if status != 'success' if (status !== 'success') {
console.log 'Unable to load the address!' console.log('Unable to load the address!');
phantom.exit(1) phantom.exit(1);
return return;
else } else {
window.setTimeout () -> return window.setTimeout(function() {
page.render(output) page.render(output);
phantom.exit() phantom.exit();
return }
, 200 , 200);
}
});
}

View File

@ -1,13 +1,20 @@
###* /*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
/**
Definition of the ResumeDetector class. Definition of the ResumeDetector class.
@module utils/resume-detector @module utils/resume-detector
@license MIT. See LICENSE.md for details. @license MIT. See LICENSE.md for details.
### */
module.exports = ( rez ) -> module.exports = function( rez ) {
if rez.meta && rez.meta.format #&& rez.meta.format.substr(0, 5).toUpperCase() == 'FRESH' if (rez.meta && rez.meta.format) { //&& rez.meta.format.substr(0, 5).toUpperCase() == 'FRESH'
'fresh' return 'fresh';
else if rez.basics } else if (rez.basics) {
'jrs' return 'jrs';
else } else {
'unk' return 'unk';
}
};

View File

@ -1,6 +1,11 @@
module.exports = /*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
module.exports = {
###* /**
Removes ignored or private fields from a resume object Removes ignored or private fields from a resume object
@returns an object with the following structure: @returns an object with the following structure:
{ {
@ -8,40 +13,47 @@ module.exports =
ignoreList: an array of ignored nodes that were removed ignoreList: an array of ignored nodes that were removed
privateList: an array of private nodes that were removed privateList: an array of private nodes that were removed
} }
### */
scrubResume: (rep, opts) -> scrubResume(rep, opts) {
traverse = require 'traverse' const traverse = require('traverse');
ignoreList = [] const ignoreList = [];
privateList = [] const privateList = [];
includePrivates = opts && opts.private const includePrivates = opts && opts.private;
scrubbed = traverse( rep ).map () -> # [^1] const scrubbed = traverse( rep ).map(function() { // [^1]
if !@isLeaf if (!this.isLeaf) {
if @node.ignore == true || @node.ignore == 'true' if ((this.node.ignore === true) || (this.node.ignore === 'true')) {
ignoreList.push @node ignoreList.push(this.node);
@delete() this.delete();
else if (@node.private == true || @node.private == 'true') && !includePrivates } else if (((this.node.private === true) || (this.node.private === 'true')) && !includePrivates) {
privateList.push @node privateList.push(this.node);
@delete() this.delete();
if _.isArray(@node) # [^2] }
@after () -> }
@update _.compact this.node if (_.isArray(this.node)) { // [^2]
return this.after(function() {
return this.update(_.compact(this.node));
});
}
});
scrubbed: scrubbed return {
ingoreList: ignoreList scrubbed,
privateList: privateList ingoreList: ignoreList,
privateList
};
}
};
# [^1]: As of v0.6.6, the NPM traverse library has a quirk when attempting // [^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: // to remove array elements directly using traverse's `this.remove`. See:
# //
# https://github.com/substack/js-traverse/issues/48 // https://github.com/substack/js-traverse/issues/48
# //
# [^2]: The workaround is to use traverse's 'this.delete' to nullify the value // [^2]: The workaround is to use traverse's 'this.delete' to nullify the value
# first, followed by removal with something like _.compact. // first, followed by removal with something like _.compact.
# //
# https://github.com/substack/js-traverse/issues/48#issuecomment-142607200 // https://github.com/substack/js-traverse/issues/48#issuecomment-142607200
# //

View File

@ -1,26 +1,34 @@
###* /*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
/**
Definition of the SafeJsonLoader class. Definition of the SafeJsonLoader class.
@module utils/safe-json-loader @module utils/safe-json-loader
@license MIT. See LICENSE.md for details. @license MIT. See LICENSE.md for details.
### */
FS = require('fs') const FS = require('fs');
SyntaxErrorEx = require('./syntax-error-ex') const SyntaxErrorEx = require('./syntax-error-ex');
module.exports = ( file ) -> module.exports = function( file ) {
ret = { } const 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 err } catch (err) {
# If we get here, either FS.readFileSync or JSON.parse failed. // If we get here, either FS.readFileSync or JSON.parse failed.
# We'll return HMSTATUS.readError or HMSTATUS.parseError. // We'll return HMSTATUS.readError or HMSTATUS.parseError.
retRaw = ret.raw && ret.raw.trim() const retRaw = ret.raw && ret.raw.trim();
ret.ex = ret.ex = {
op: if retRaw then 'parse' else 'read' op: retRaw ? 'parse' : 'read',
inner: inner:
if SyntaxErrorEx.is( err ) SyntaxErrorEx.is( err )
then (new SyntaxErrorEx( err, retRaw )) ? (new SyntaxErrorEx( err, retRaw ))
else err : err,
file: file file
ret };
}
return ret;
};

View File

@ -1,29 +1,44 @@
###* /*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
/**
Safe spawn utility for HackMyResume / FluentCV. Safe spawn utility for HackMyResume / FluentCV.
@module utils/safe-spawn @module utils/safe-spawn
@license MIT. See LICENSE.md for details. @license MIT. See LICENSE.md for details.
### */
###* Safely spawn a process synchronously or asynchronously without throwing an /** Safely spawn a process synchronously or asynchronously without throwing an
exception ### exception */
module.exports = ( cmd, args, isSync, callback, param ) -> module.exports = function( cmd, args, isSync, callback, param ) {
try try {
# .spawnSync not available on earlier Node.js, so default to .spawn // .spawnSync not available on earlier Node.js, so default to .spawn
spawn = require('child_process')[ if isSync then 'spawnSync' else 'spawn'] const spawn = require('child_process')[ isSync ? 'spawnSync' : 'spawn'];
info = spawn cmd, args const info = spawn(cmd, args);
# Check for error depending on whether we're sync or async TODO: Promises // Check for error depending on whether we're sync or async TODO: Promises
if !isSync if (!isSync) {
info.on 'error', (err) -> info.on('error', function(err) {
callback?(err, param) if (typeof callback === 'function') {
return callback(err, param);
return }
else });
if info.error return;
callback?(info.error, param) } else {
return cmd: cmd, inner: info.error if (info.error) {
if (typeof callback === 'function') {
callback(info.error, param);
}
return {cmd, inner: info.error};
}
}
catch ex } catch (ex) {
callback?(ex, param) if (typeof callback === 'function') {
ex callback(ex, param);
}
return ex;
}
};

View File

@ -1,43 +1,58 @@
###* /*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
/**
Object string transformation. Object string transformation.
@module utils/string-transformer @module utils/string-transformer
@license MIT. See LICENSE.md for details. @license MIT. See LICENSE.md for details.
### */
_ = require 'underscore' const _ = require('underscore');
moment = require 'moment' const 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 = ( ret, filt, transformer ) -> module.exports = function( ret, filt, transformer ) {
that = @ const that = this;
# TODO: refactor recursion // TODO: refactor recursion
transformStringsInObject = ( obj, filters ) -> var transformStringsInObject = function( obj, filters ) {
return if !obj if (!obj) { return; }
return if moment.isMoment obj if (moment.isMoment(obj)) { return; }
if _.isArray( obj ) if (_.isArray( obj )) {
obj.forEach (elem, idx, ar) -> return obj.forEach(function(elem, idx, ar) {
if typeof elem == 'string' || elem instanceof String if ((typeof elem === 'string') || elem instanceof String) {
ar[idx] = transformer( null, elem ) return ar[idx] = transformer( null, elem );
else if _.isObject(elem) } else if (_.isObject(elem)) {
transformStringsInObject( elem, filters ) return transformStringsInObject( elem, filters );
else if _.isObject( obj ) }
Object.keys( obj ).forEach (k) -> });
if filters.length && _.contains(filters, k) } else if (_.isObject( obj )) {
return return Object.keys( obj ).forEach(function(k) {
sub = obj[k] if (filters.length && _.contains(filters, k)) {
if typeof sub == 'string' || sub instanceof String return;
obj[k] = transformer( k, sub ) }
else if _.isObject( sub ) const sub = obj[k];
transformStringsInObject( sub, filters ) if ((typeof sub === 'string') || sub instanceof String) {
return obj[k] = transformer( k, sub );
} else if (_.isObject( sub )) {
return transformStringsInObject( sub, filters );
}
});
}
};
Object.keys( ret ).forEach (member) -> Object.keys( ret ).forEach(function(member) {
if !filt || !filt.length || !_.contains(filt, member) if (!filt || !filt.length || !_.contains(filt, member)) {
transformStringsInObject( ret[ member ], filt || [] ) return transformStringsInObject( ret[ member ], filt || [] );
ret }
});
return ret;
};

View File

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

View File

@ -1,36 +1,45 @@
###* /*
* decaffeinate suggestions:
* DS101: Remove unnecessary use of Array.from
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
/**
Definition of the SyntaxErrorEx class. Definition of the SyntaxErrorEx class.
@module utils/syntax-error-ex @module utils/syntax-error-ex
@license MIT. See LICENSE.md for details. @license MIT. See LICENSE.md for details.
### */
###* /**
Represents a SyntaxError exception with line and column info. Represents a SyntaxError exception with line and column info.
Collect syntax error information from the provided exception object. The Collect syntax error information from the provided exception object. The
JavaScript `SyntaxError` exception isn't interpreted uniformly across environ- JavaScript `SyntaxError` exception isn't interpreted uniformly across environ-
ments, so we reparse on error to grab the line and column. ments, so we reparse on error to grab the line and column.
See: http://stackoverflow.com/q/13323356 See: http://stackoverflow.com/q/13323356
@class SyntaxErrorEx @class SyntaxErrorEx
### */
class SyntaxErrorEx class SyntaxErrorEx {
constructor: ( ex, rawData ) -> constructor( ex, rawData ) {
lineNum = null const lineNum = null;
colNum = null const colNum = null;
JSONLint = require 'json-lint' let JSONLint = require('json-lint');
lint = JSONLint rawData, { comments: false } const lint = JSONLint(rawData, { comments: false });
[@line, @col] = [lint.line, lint.character] if lint.error if (lint.error) { [this.line, this.col] = Array.from([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 err } catch (err) {
@line = (/on line (\d+)/.exec err)[1] this.line = (/on line (\d+)/.exec(err))[1];
}
}
}
}
# Return true if the supplied parameter is a JavaScript SyntaxError // Return true if the supplied parameter is a JavaScript SyntaxError
SyntaxErrorEx.is = ( ex ) -> ex instanceof SyntaxError SyntaxErrorEx.is = ex => ex instanceof SyntaxError;
module.exports = SyntaxErrorEx; module.exports = SyntaxErrorEx;

View File

@ -1,75 +1,92 @@
###* /*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
/**
Implementation of the 'analyze' verb for HackMyResume. Implementation of the 'analyze' verb for HackMyResume.
@module verbs/analyze @module verbs/analyze
@license MIT. See LICENSE.md for details. @license MIT. See LICENSE.md for details.
### */
MKDIRP = require('mkdirp') let AnalyzeVerb;
PATH = require('path') const MKDIRP = require('mkdirp');
HMEVENT = require('../core/event-codes') const PATH = require('path');
HMSTATUS = require('../core/status-codes') const HMEVENT = require('../core/event-codes');
_ = require('underscore') const HMSTATUS = require('../core/status-codes');
ResumeFactory = require('../core/resume-factory') const _ = require('underscore');
Verb = require('../verbs/verb') const ResumeFactory = require('../core/resume-factory');
chalk = require('chalk') const Verb = require('../verbs/verb');
const chalk = require('chalk');
###* An invokable resume analysis command. ### /** An invokable resume analysis command. */
module.exports = class AnalyzeVerb extends Verb module.exports = (AnalyzeVerb = class AnalyzeVerb extends Verb {
constructor: -> super 'analyze', _analyze constructor() { super('analyze', _analyze); }
});
###* Private workhorse for the 'analyze' command. ### /** Private workhorse for the 'analyze' command. */
_analyze = ( sources, dst, opts ) -> var _analyze = function( sources, dst, opts ) {
if !sources || !sources.length if (!sources || !sources.length) {
@err HMSTATUS.resumeNotFound, { quit: true } this.err(HMSTATUS.resumeNotFound, { quit: true });
return null return null;
}
nlzrs = _loadInspectors() const nlzrs = _loadInspectors();
results = _.map sources, (src) -> const results = _.map(sources, function(src) {
r = ResumeFactory.loadOne src, format: 'FRESH', objectify: true, inner: { const r = ResumeFactory.loadOne(src, { format: 'FRESH', objectify: true, inner: {
private: opts.private is true private: opts.private === true
}, @ }
return { } if opts.assert and @hasError() }, this);
if (opts.assert && this.hasError()) { return { }; }
if r.fluenterror if (r.fluenterror) {
r.quit = opts.assert r.quit = opts.assert;
@err r.fluenterror, r this.err(r.fluenterror, r);
r return r;
else } else {
_analyzeOne.call @, r, nlzrs, opts return _analyzeOne.call(this, r, nlzrs, opts);
, @ }
}
, this);
if @hasError() and !opts.assert if (this.hasError() && !opts.assert) {
@reject @errorCode this.reject(this.errorCode);
else if !@hasError() } else if (!this.hasError()) {
@resolve results this.resolve(results);
results }
return results;
};
###* Analyze a single resume. ### /** Analyze a single resume. */
_analyzeOne = ( resumeObject, nlzrs, opts ) -> var _analyzeOne = function( resumeObject, nlzrs, opts ) {
rez = resumeObject.rez const { rez } = resumeObject;
safeFormat = const safeFormat =
if rez.meta and rez.meta.format and rez.meta.format.startsWith 'FRESH' rez.meta && rez.meta.format && rez.meta.format.startsWith('FRESH')
then 'FRESH' else 'JRS' ? 'FRESH' : 'JRS';
this.stat( HMEVENT.beforeAnalyze, { fmt: safeFormat, file: resumeObject.file }) this.stat( HMEVENT.beforeAnalyze, { fmt: safeFormat, file: resumeObject.file });
info = _.mapObject nlzrs, (val, key) -> val.run rez const info = _.mapObject(nlzrs, (val, key) => val.run(rez));
this.stat HMEVENT.afterAnalyze, { info: info } this.stat(HMEVENT.afterAnalyze, { info });
info return info;
};
_loadInspectors = -> var _loadInspectors = () =>
totals: require '../inspectors/totals-inspector' ({
coverage: require '../inspectors/gap-inspector' totals: require('../inspectors/totals-inspector'),
keywords: require '../inspectors/keyword-inspector' coverage: require('../inspectors/gap-inspector'),
keywords: require('../inspectors/keyword-inspector')
})
;

View File

@ -1,277 +1,311 @@
###* /*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
/**
Implementation of the 'build' verb for HackMyResume. Implementation of the 'build' verb for HackMyResume.
@module verbs/build @module verbs/build
@license MIT. See LICENSE.md for details. @license MIT. See LICENSE.md for details.
### */
_ = require 'underscore' let BuildVerb;
PATH = require 'path' const _ = require('underscore');
FS = require 'fs' const PATH = require('path');
MD = require 'marked' const FS = require('fs');
MKDIRP = require 'mkdirp' const MD = require('marked');
extend = require 'extend' const MKDIRP = require('mkdirp');
parsePath = require 'parse-filepath' const extend = require('extend');
RConverter = require 'fresh-jrs-converter' const parsePath = require('parse-filepath');
HMSTATUS = require '../core/status-codes' const RConverter = require('fresh-jrs-converter');
HMEVENT = require '../core/event-codes' const HMSTATUS = require('../core/status-codes');
RTYPES = const HMEVENT = require('../core/event-codes');
FRESH: require '../core/fresh-resume' const RTYPES = {
JRS: require '../core/jrs-resume' FRESH: require('../core/fresh-resume'),
_opts = require '../core/default-options' JRS: require('../core/jrs-resume')
FRESHTheme = require '../core/fresh-theme' };
JRSTheme = require '../core/jrs-theme' const _opts = require('../core/default-options');
ResumeFactory = require '../core/resume-factory' const FRESHTheme = require('../core/fresh-theme');
_fmts = require '../core/default-formats' const JRSTheme = require('../core/jrs-theme');
Verb = require '../verbs/verb' const ResumeFactory = require('../core/resume-factory');
const _fmts = require('../core/default-formats');
const Verb = require('../verbs/verb');
_err = null const _err = null;
_log = null const _log = null;
_rezObj = null let _rezObj = null;
build = null const build = null;
prep = null const prep = null;
single = null const single = null;
verifyOutputs = null const verifyOutputs = null;
addFreebieFormats = null const addFreebieFormats = null;
expand = null const expand = null;
verifyTheme = null const verifyTheme = null;
loadTheme = null const loadTheme = null;
###* An invokable resume generation command. ### /** An invokable resume generation command. */
module.exports = class BuildVerb extends Verb module.exports = (BuildVerb = class BuildVerb extends Verb {
###* Create a new build verb. ### /** Create a new build verb. */
constructor: () -> super 'build', _build constructor() { super('build', _build); }
});
###* /**
Given a source resume in FRESH or JRS format, a destination resume path, and a Given a source resume in FRESH or JRS format, a destination resume path, and a
theme file, generate 0..N resumes in the desired formats. theme file, generate 0..N resumes in the desired formats.
@param src Path to the source JSON resume file: "rez/resume.json". @param src Path to the source JSON resume file: "rez/resume.json".
@param dst An array of paths to the target resume file(s). @param dst An array of paths to the target resume file(s).
@param opts Generation options. @param opts Generation options.
### */
_build = ( src, dst, opts ) -> var _build = function( src, dst, opts ) {
if !src || !src.length let err;
@err HMSTATUS.resumeNotFound, quit: true if (!src || !src.length) {
return null this.err(HMSTATUS.resumeNotFound, {quit: true});
return null;
}
_prep.call @, src, dst, opts _prep.call(this, src, dst, opts);
# Load input resumes as JSON... // Load input resumes as JSON...
sheetObjects = ResumeFactory.load src, const sheetObjects = ResumeFactory.load(src, {
format: null, objectify: false, quit: true, inner: { format: null, objectify: false, quit: true, inner: {
sort: _opts.sort sort: _opts.sort,
private: _opts.private private: _opts.private
} }
, @ }
, this);
# Explicit check for any resume loading errors... // Explicit check for any resume loading errors...
problemSheets = _.filter sheetObjects, (so) -> so.fluenterror const problemSheets = _.filter(sheetObjects, so => so.fluenterror);
if problemSheets and problemSheets.length if (problemSheets && problemSheets.length) {
problemSheets[0].quit = true # can't go on problemSheets[0].quit = true; // can't go on
@err problemSheets[0].fluenterror, problemSheets[0] this.err(problemSheets[0].fluenterror, problemSheets[0]);
return null return null;
}
# Get the collection of raw JSON sheets // Get the collection of raw JSON sheets
sheets = sheetObjects.map (r) -> r.json const sheets = sheetObjects.map(r => r.json);
# Load the theme... // Load the theme...
theme = null let theme = null;
@stat HMEVENT.beforeTheme, { theme: _opts.theme } this.stat(HMEVENT.beforeTheme, { theme: _opts.theme });
try try {
tFolder = _verifyTheme.call @, _opts.theme const tFolder = _verifyTheme.call(this, _opts.theme);
if tFolder.fluenterror if (tFolder.fluenterror) {
tFolder.quit = true tFolder.quit = true;
@err tFolder.fluenterror, tFolder this.err(tFolder.fluenterror, tFolder);
return return;
theme = _opts.themeObj = _loadTheme tFolder }
_addFreebieFormats theme theme = (_opts.themeObj = _loadTheme(tFolder));
catch err _addFreebieFormats(theme);
newEx = } catch (error) {
fluenterror: HMSTATUS.themeLoad err = error;
inner: err const newEx = {
attempted: _opts.theme fluenterror: HMSTATUS.themeLoad,
inner: err,
attempted: _opts.theme,
quit: true quit: true
@err HMSTATUS.themeLoad, newEx };
return null this.err(HMSTATUS.themeLoad, newEx);
return null;
}
@stat HMEVENT.afterTheme, theme: theme this.stat(HMEVENT.afterTheme, {theme});
# Check for invalid outputs... // Check for invalid outputs...
inv = _verifyOutputs.call @, dst, theme const inv = _verifyOutputs.call(this, dst, theme);
if inv && inv.length if (inv && inv.length) {
@err HMSTATUS.invalidFormat, data: inv, theme: theme, quit: true this.err(HMSTATUS.invalidFormat, {data: inv, theme, quit: true});
return null return null;
}
## Merge input resumes, yielding a single source resume... //# Merge input resumes, yielding a single source resume...
rez = null let rez = null;
if sheets.length > 1 if (sheets.length > 1) {
isFRESH = !sheets[0].basics const isFRESH = !sheets[0].basics;
mixed = _.any sheets, (s) -> return if isFRESH then s.basics else !s.basics const mixed = _.any(sheets, function(s) { if (isFRESH) { return s.basics; } else { return !s.basics; } });
@stat HMEVENT.beforeMerge, { f: _.clone(sheetObjects), mixed: mixed } this.stat(HMEVENT.beforeMerge, { f: _.clone(sheetObjects), mixed });
if mixed if (mixed) {
@err HMSTATUS.mixedMerge this.err(HMSTATUS.mixedMerge);
rez = _.reduceRight sheets, ( a, b, idx ) -> }
extend( true, b, a ) rez = _.reduceRight(sheets, ( a, b, idx ) => extend( true, b, a ));
@stat HMEVENT.afterMerge, { r: rez } this.stat(HMEVENT.afterMerge, { r: rez });
else } else {
rez = sheets[0]; rez = sheets[0];
}
# Convert the merged source resume to the theme's format, if necessary.. // Convert the merged source resume to the theme's format, if necessary..
orgFormat = if rez.basics then 'JRS' else 'FRESH'; const orgFormat = rez.basics ? 'JRS' : 'FRESH';
toFormat = if theme.render then 'JRS' else 'FRESH'; const toFormat = theme.render ? 'JRS' : 'FRESH';
if toFormat != orgFormat if (toFormat !== orgFormat) {
@stat HMEVENT.beforeInlineConvert this.stat(HMEVENT.beforeInlineConvert);
rez = RConverter[ 'to' + toFormat ]( rez ); rez = RConverter[ `to${toFormat}` ]( rez );
@stat HMEVENT.afterInlineConvert, { file: sheetObjects[0].file, fmt: toFormat } this.stat(HMEVENT.afterInlineConvert, { file: sheetObjects[0].file, fmt: toFormat });
}
# Announce the theme // Announce the theme
@stat HMEVENT.applyTheme, r: rez, theme: theme this.stat(HMEVENT.applyTheme, {r: rez, theme});
# Load the resume into a FRESHResume or JRSResume object // Load the resume into a FRESHResume or JRSResume object
_rezObj = new (RTYPES[ toFormat ])().parseJSON( rez, private: _opts.private ); _rezObj = new (RTYPES[ toFormat ])().parseJSON( rez, {private: _opts.private} );
# Expand output resumes... // Expand output resumes...
targets = _expand dst, theme const targets = _expand(dst, theme);
# Run the transformation! // Run the transformation!
_.each targets, (t) -> _.each(targets, function(t) {
return { } if @hasError() and opts.assert if (this.hasError() && opts.assert) { return { }; }
t.final = _single.call @, t, theme, targets t.final = _single.call(this, t, theme, targets);
if t.final?.fluenterror if (t.final != null ? t.final.fluenterror : undefined) {
t.final.quit = opts.assert t.final.quit = opts.assert;
@err t.final.fluenterror, t.final this.err(t.final.fluenterror, t.final);
return }
, @ }
, this);
results = const results = {
sheet: _rezObj sheet: _rezObj,
targets: targets targets,
processed: targets processed: targets
};
if @hasError() and !opts.assert if (this.hasError() && !opts.assert) {
@reject results this.reject(results);
else if !@hasError() } else if (!this.hasError()) {
@resolve results this.resolve(results);
}
results return results;
};
###* /**
Prepare for a BUILD run. Prepare for a BUILD run.
### */
_prep = ( src, dst, opts ) -> var _prep = function( src, dst, opts ) {
# Cherry-pick options //_opts = extend( true, _opts, opts ); // Cherry-pick options //_opts = extend( true, _opts, opts );
_opts.theme = (opts.theme && opts.theme.toLowerCase().trim()) || 'modern'; _opts.theme = (opts.theme && opts.theme.toLowerCase().trim()) || 'modern';
_opts.prettify = opts.prettify is true _opts.prettify = opts.prettify === true;
_opts.private = opts.private is true _opts.private = opts.private === true;
_opts.noescape = opts.noescape is true _opts.noescape = opts.noescape === true;
_opts.css = opts.css _opts.css = opts.css;
_opts.pdf = opts.pdf _opts.pdf = opts.pdf;
_opts.wrap = opts.wrap || 60 _opts.wrap = opts.wrap || 60;
_opts.stitles = opts.sectionTitles _opts.stitles = opts.sectionTitles;
_opts.tips = opts.tips _opts.tips = opts.tips;
_opts.errHandler = opts.errHandler _opts.errHandler = opts.errHandler;
_opts.noTips = opts.noTips _opts.noTips = opts.noTips;
_opts.debug = opts.debug _opts.debug = opts.debug;
_opts.sort = opts.sort _opts.sort = opts.sort;
_opts.wkhtmltopdf = opts.wkhtmltopdf _opts.wkhtmltopdf = opts.wkhtmltopdf;
that = @ const that = this;
# Set up callbacks for internal generators // Set up callbacks for internal generators
_opts.onTransform = (info) -> _opts.onTransform = function(info) {
that.stat HMEVENT.afterTransform, info; return that.stat(HMEVENT.afterTransform, info);
_opts.beforeWrite = (info) -> };
that.stat HMEVENT.beforeWrite, info; return _opts.beforeWrite = function(info) {
_opts.afterWrite = (info) -> that.stat(HMEVENT.beforeWrite, info);
that.stat HMEVENT.afterWrite, info; return };
_opts.afterWrite = function(info) {
that.stat(HMEVENT.afterWrite, info);
};
# If two or more files are passed to the GENERATE command and the TO // If two or more files are passed to the GENERATE command and the TO
# keyword is omitted, the last file specifies the output file. // keyword is omitted, the last file specifies the output file.
( src.length > 1 && ( !dst || !dst.length ) ) && dst.push( src.pop() ) ( (src.length > 1) && ( !dst || !dst.length ) ) && dst.push( src.pop() );
return };
###* /**
Generate a single target resume such as "out/rez.html" or "out/rez.doc". Generate a single target resume such as "out/rez.html" or "out/rez.doc".
TODO: Refactor. TODO: Refactor.
@param targInfo Information for the target resume. @param targInfo Information for the target resume.
@param theme A FRESHTheme or JRSTheme object. @param theme A FRESHTheme or JRSTheme object.
### */
_single = ( targInfo, theme, finished ) -> var _single = function( targInfo, theme, finished ) {
ret = null let ret = null;
ex = null let ex = null;
f = targInfo.file const f = targInfo.file;
try try {
if !targInfo.fmt if (!targInfo.fmt) {
return { } return { };
fType = targInfo.fmt.outFormat }
fName = PATH.basename f, '.' + fType const fType = targInfo.fmt.outFormat;
theFormat = null const fName = PATH.basename(f, `.${fType}`);
let theFormat = null;
@stat HMEVENT.beforeGenerate, this.stat(HMEVENT.beforeGenerate, {
fmt: targInfo.fmt.outFormat fmt: targInfo.fmt.outFormat,
file: PATH.relative process.cwd(), f file: PATH.relative(process.cwd(), f)
}
);
_opts.targets = finished _opts.targets = finished;
# If targInfo.fmt.files exists, this format is backed by a document. // If targInfo.fmt.files exists, this format is backed by a document.
# Fluent/FRESH themes are handled here. // Fluent/FRESH themes are handled here.
if targInfo.fmt.files && targInfo.fmt.files.length if (targInfo.fmt.files && targInfo.fmt.files.length) {
theFormat = _fmts.filter( (fmt) -> theFormat = _fmts.filter( fmt => fmt.name === targInfo.fmt.outFormat)[0];
return fmt.name == targInfo.fmt.outFormat MKDIRP.sync(PATH.dirname( f ));
)[0]; ret = theFormat.gen.generate(_rezObj, f, _opts);
MKDIRP.sync PATH.dirname( f )
ret = theFormat.gen.generate _rezObj, f, _opts
# Otherwise this is an ad-hoc format (JSON, YML, or PNG) that every theme // Otherwise this is an ad-hoc format (JSON, YML, or PNG) that every theme
# gets "for free". // gets "for free".
else } else {
theFormat = _fmts.filter( (fmt) -> theFormat = _fmts.filter( fmt => fmt.name === targInfo.fmt.outFormat)[0];
return fmt.name == targInfo.fmt.outFormat const outFolder = PATH.dirname(f);
)[0]; MKDIRP.sync(outFolder); // Ensure dest folder exists;
outFolder = PATH.dirname f ret = theFormat.gen.generate(_rezObj, f, _opts);
MKDIRP.sync outFolder # Ensure dest folder exists; }
ret = theFormat.gen.generate _rezObj, f, _opts
catch e } catch (e) {
ex = e ex = e;
}
this.stat HMEVENT.afterGenerate, this.stat(HMEVENT.afterGenerate, {
fmt: targInfo.fmt.outFormat fmt: targInfo.fmt.outFormat,
file: PATH.relative process.cwd(), f file: PATH.relative(process.cwd(), f),
error: ex error: ex
}
);
if ex if (ex) {
if ex.fluenterror if (ex.fluenterror) {
ret = ex ret = ex;
else } else {
ret = fluenterror: HMSTATUS.generateError, inner: ex ret = {fluenterror: HMSTATUS.generateError, inner: ex};
ret }
}
return ret;
};
###* Ensure that user-specified outputs/targets are valid. ### /** Ensure that user-specified outputs/targets are valid. */
_verifyOutputs = ( targets, theme ) -> var _verifyOutputs = function( targets, theme ) {
@stat HMEVENT.verifyOutputs, targets: targets, theme: theme this.stat(HMEVENT.verifyOutputs, {targets, theme});
_.reject targets.map( ( t ) -> return _.reject(targets.map( function( t ) {
pathInfo = parsePath t const pathInfo = parsePath(t);
format: pathInfo.extname.substr(1) ), return {format: pathInfo.extname.substr(1)}; }),
(t) -> t.format == 'all' || theme.hasFormat( t.format ) t => (t.format === 'all') || theme.hasFormat( t.format ));
};
###* /**
Reinforce the chosen theme with "freebie" formats provided by HackMyResume. Reinforce the chosen theme with "freebie" formats provided by HackMyResume.
A "freebie" format is an output format such as JSON, YML, or PNG that can be A "freebie" format is an output format such as JSON, YML, or PNG that can be
generated directly from the resume model or from one of the theme's declared generated directly from the resume model or from one of the theme's declared
@ -279,123 +313,131 @@ 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 that declares an HTML format; the theme doesn't have to provide an explicit
PNG template. PNG template.
@param theTheme A FRESHTheme or JRSTheme object. @param theTheme A FRESHTheme or JRSTheme object.
### */
_addFreebieFormats = ( theTheme ) -> var _addFreebieFormats = function( theTheme ) {
# Add freebie formats (JSON, YAML, PNG) every theme gets... // Add freebie formats (JSON, YAML, PNG) every theme gets...
# Add HTML-driven PNG only if the theme has an HTML format. // Add HTML-driven PNG only if the theme has an HTML format.
theTheme.formats.json = theTheme.formats.json || { theTheme.formats.json = theTheme.formats.json || {
freebie: true, title: 'json', outFormat: 'json', pre: 'json', freebie: true, title: 'json', outFormat: 'json', pre: 'json',
ext: 'json', path: null, data: null ext: 'json', path: null, data: null
} };
theTheme.formats.yml = theTheme.formats.yml || { theTheme.formats.yml = theTheme.formats.yml || {
freebie: true, title: 'yaml', outFormat: 'yml', pre: 'yml', freebie: true, title: 'yaml', outFormat: 'yml', pre: 'yml',
ext: 'yml', path: null, data: null ext: 'yml', path: null, data: null
} };
if theTheme.formats.html && !theTheme.formats.png if (theTheme.formats.html && !theTheme.formats.png) {
theTheme.formats.png = { theTheme.formats.png = {
freebie: true, title: 'png', outFormat: 'png', freebie: true, title: 'png', outFormat: 'png',
ext: 'yml', path: null, data: null ext: 'yml', path: null, data: null
} };
return }
};
###* /**
Expand output files. For example, "foo.all" should be expanded to Expand output files. For example, "foo.all" should be expanded to
["foo.html", "foo.doc", "foo.pdf", "etc"]. ["foo.html", "foo.doc", "foo.pdf", "etc"].
@param dst An array of output files as specified by the user. @param dst An array of output files as specified by the user.
@param theTheme A FRESHTheme or JRSTheme object. @param theTheme A FRESHTheme or JRSTheme object.
### */
_expand = ( dst, theTheme ) -> var _expand = function( dst, theTheme ) {
# Set up the destination collection. It's either the array of files passed // Set up the destination collection. It's either the array of files passed
# by the user or 'out/resume.all' if no targets were specified. // by the user or 'out/resume.all' if no targets were specified.
destColl = (dst && dst.length && dst) || [PATH.normalize('out/resume.all')]; const destColl = (dst && dst.length && dst) || [PATH.normalize('out/resume.all')];
# Assemble an array of expanded target files... (can't use map() here) // Assemble an array of expanded target files... (can't use map() here)
targets = []; const targets = [];
destColl.forEach (t) -> destColl.forEach(function(t) {
to = PATH.resolve(t) const to = PATH.resolve(t);
pa = parsePath(to) const pa = parsePath(to);
fmat = pa.extname || '.all'; const fmat = pa.extname || '.all';
targets.push.apply( targets, return targets.push.apply( targets,
if fmat == '.all' fmat === '.all'
then Object.keys( theTheme.formats ).map( ( k ) -> ? Object.keys( theTheme.formats ).map( function( k ) {
z = theTheme.formats[k] const z = theTheme.formats[k];
return { file: to.replace( /all$/g, z.outFormat ), fmt: z } return { file: to.replace( /all$/g, z.outFormat ), fmt: z };
) })
else [{ file: to, fmt: theTheme.getFormat( fmat.slice(1) ) }] : [{ file: to, fmt: theTheme.getFormat( fmat.slice(1) ) }]
) );
targets });
return targets;
};
###* /**
Verify the specified theme name/path. Verify the specified theme name/path.
### */
_verifyTheme = ( themeNameOrPath ) -> var _verifyTheme = function( themeNameOrPath ) {
# First, see if this is one of the predefined FRESH themes. There are only a // 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 // 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 // the official source of truth: the fresh-themes repository, which mounts the
# themes conveniently by name to the module object, and which is embedded // themes conveniently by name to the module object, and which is embedded
# locally inside the HackMyResume installation. // locally inside the HackMyResume installation.
themesObj = require 'fresh-themes' let tFolder;
if _.has themesObj.themes, themeNameOrPath const themesObj = require('fresh-themes');
if (_.has(themesObj.themes, themeNameOrPath)) {
tFolder = PATH.join( tFolder = PATH.join(
parsePath( require.resolve('fresh-themes') ).dirname, parsePath( require.resolve('fresh-themes') ).dirname,
'/themes/', '/themes/',
themeNameOrPath themeNameOrPath
) );
else } else {
# Otherwsie it's a path to an arbitrary FRESH or JRS theme sitting somewhere // 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). // 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 // In either case, make sure the theme folder exists
exists = require('path-exists').sync const exists = require('path-exists').sync;
if exists tFolder if (exists(tFolder)) {
tFolder return tFolder;
else } else {
fluenterror: HMSTATUS.themeNotFound, data: _opts.theme return {fluenterror: HMSTATUS.themeNotFound, data: _opts.theme};
}
};
###* /**
Load the specified theme, which could be either a FRESH theme or a JSON Resume Load the specified theme, which could be either a FRESH theme or a JSON Resume
theme (or both). theme (or both).
### */
_loadTheme = ( tFolder ) -> var _loadTheme = function( tFolder ) {
themeJsonPath = PATH.join tFolder, 'theme.json' # [^1] const themeJsonPath = PATH.join(tFolder, 'theme.json'); // [^1]
exists = require('path-exists').sync const exists = require('path-exists').sync;
# Create a FRESH or JRS theme object // Create a FRESH or JRS theme object
theTheme = const theTheme =
if exists themeJsonPath exists(themeJsonPath)
then new FRESHTheme().open tFolder ? new FRESHTheme().open(tFolder)
else new JRSTheme().open tFolder : new JRSTheme().open(tFolder);
# Cache the theme object // Cache the theme object
_opts.themeObj = theTheme; _opts.themeObj = theTheme;
theTheme return theTheme;
};
# FOOTNOTES // FOOTNOTES
# ------------------------------------------------------------------------------ // ------------------------------------------------------------------------------
# [^1] We don't know ahead of time whether this is a FRESH or JRS theme. // [^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 // However, all FRESH themes have a theme.json file, so we'll use that as a
# canary for now, as an interim solution. // canary for now, as an interim solution.
# //
# Unfortunately, with the exception of FRESH's theme.json, both FRESH and // 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 // 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 // marks, which makes a simple task like ad hoc theme detection harder than
# it should be to do cleanly. // it should be to do cleanly.
# //
# Another complicating factor is that it's possible for a theme to be BOTH. // 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 // That is, a single set of theme files can serve as a FRESH theme -and- a
# JRS theme. // JRS theme.
# //
# TODO: The most robust way to deal with all these issues is with a strong // 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 // 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. // type, then for all intents and purposes, it IS a theme of that type.

View File

@ -1,141 +1,172 @@
###* /*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
/**
Implementation of the 'convert' verb for HackMyResume. Implementation of the 'convert' verb for HackMyResume.
@module verbs/convert @module verbs/convert
@license MIT. See LICENSE.md for details. @license MIT. See LICENSE.md for details.
### */
ResumeFactory = require('../core/resume-factory') let ConvertVerb;
chalk = require('chalk') const ResumeFactory = require('../core/resume-factory');
Verb = require('../verbs/verb') const chalk = require('chalk');
HMSTATUS = require('../core/status-codes') const Verb = require('../verbs/verb');
_ = require('underscore') const HMSTATUS = require('../core/status-codes');
HMEVENT = require('../core/event-codes'); const _ = require('underscore');
const HMEVENT = require('../core/event-codes');
module.exports = class ConvertVerb extends Verb module.exports = (ConvertVerb = class ConvertVerb extends Verb {
constructor: -> super 'convert', _convert constructor() { super('convert', _convert); }
});
###* Private workhorse method. Convert 0..N resumes between FRESH and JRS /** Private workhorse method. Convert 0..N resumes between FRESH and JRS
formats. ### formats. */
_convert = ( srcs, dst, opts ) -> var _convert = function( srcs, dst, opts ) {
# If no source resumes are specified, error out // If no source resumes are specified, error out
if !srcs || !srcs.length let fmtUp;
@err HMSTATUS.resumeNotFound, { quit: true } if (!srcs || !srcs.length) {
return null this.err(HMSTATUS.resumeNotFound, { quit: true });
return null;
}
# If no destination resumes are specified, error out except for the special // If no destination resumes are specified, error out except for the special
# case of two resumes: // case of two resumes:
# hackmyresume CONVERT r1.json r2.json // hackmyresume CONVERT r1.json r2.json
if !dst || !dst.length if (!dst || !dst.length) {
if srcs.length == 1 if (srcs.length === 1) {
@err HMSTATUS.inputOutputParity, { quit: true } this.err(HMSTATUS.inputOutputParity, { quit: true });
else if srcs.length == 2 } else if (srcs.length === 2) {
dst = dst || []; dst.push( srcs.pop() ) dst = dst || []; dst.push( srcs.pop() );
else } else {
@err HMSTATUS.inputOutputParity, { quit: true } this.err(HMSTATUS.inputOutputParity, { quit: true });
}
}
# Different number of source and dest resumes? Error out. // 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)) {
@err HMSTATUS.inputOutputParity, { quit: true } this.err(HMSTATUS.inputOutputParity, { quit: true });
}
# Validate the destination format (if specified) // Validate the destination format (if specified)
targetVer = null const targetVer = null;
if opts.format if (opts.format) {
fmtUp = opts.format.trim().toUpperCase() fmtUp = opts.format.trim().toUpperCase();
if not _.contains ['FRESH','FRESCA','JRS','JRS@1','JRS@edge'], fmtUp if (!_.contains(['FRESH','FRESCA','JRS','JRS@1','JRS@edge'], fmtUp)) {
@err HMSTATUS.invalidSchemaVersion, data: opts.format.trim(), quit: true this.err(HMSTATUS.invalidSchemaVersion, {data: opts.format.trim(), quit: true});
# freshVerRegex = require '../utils/fresh-version-regex' }
# matches = fmtUp.match freshVerRegex() }
# # null // freshVerRegex = require '../utils/fresh-version-regex'
# # [ 'JRS@1.0', 'JRS', '1.0', index: 0, input: 'FRESH' ] // matches = fmtUp.match freshVerRegex()
# # [ 'FRESH', 'FRESH', undefined, index: 0, input: 'FRESH' ] // # null
# if not matches // # [ 'JRS@1.0', 'JRS', '1.0', index: 0, input: 'FRESH' ]
# @err HMSTATUS.invalidSchemaVersion, data: opts.format.trim(), quit: true // # [ 'FRESH', 'FRESH', undefined, index: 0, input: 'FRESH' ]
# targetSchema = matches[1] // if not matches
# targetVer = matches[2] || '1' // @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 any errors have occurred this early, we're done.
if @hasError() if (this.hasError()) {
@reject @errorCode this.reject(this.errorCode);
return null return null;
}
# Map each source resume to the converted destination resume // Map each source resume to the converted destination resume
results = _.map srcs, ( src, idx ) -> const results = _.map(srcs, function( src, idx ) {
# Convert each resume in turn // Convert each resume in turn
r = _convertOne.call @, src, dst, idx, fmtUp const r = _convertOne.call(this, src, dst, idx, fmtUp);
# Handle conversion errors // Handle conversion errors
if r.fluenterror if (r.fluenterror) {
r.quit = opts.assert r.quit = opts.assert;
@err r.fluenterror, r this.err(r.fluenterror, r);
r }
, @ return r;
}
, this);
if @hasError() and !opts.assert if (this.hasError() && !opts.assert) {
@reject results this.reject(results);
else if !@hasError() } else if (!this.hasError()) {
@resolve results this.resolve(results);
results }
return results;
};
###* Private workhorse method. Convert a single resume. ### /** Private workhorse method. Convert a single resume. */
_convertOne = (src, dst, idx, targetSchema) -> var _convertOne = function(src, dst, idx, targetSchema) {
# Load the resume // Load the resume
rinfo = ResumeFactory.loadOne src, const rinfo = ResumeFactory.loadOne(src, {
format: null format: null,
objectify: true objectify: true,
inner: inner: {
privatize: false privatize: false
}
}
);
# If a load error occurs, report it and move on to the next file (if any) // If a load error occurs, report it and move on to the next file (if any)
if rinfo.fluenterror if (rinfo.fluenterror) {
@stat HMEVENT.beforeConvert, this.stat(HMEVENT.beforeConvert, {
srcFile: src #rinfo.file srcFile: src, //rinfo.file
srcFmt: '???' srcFmt: '???',
dstFile: dst[idx] dstFile: dst[idx],
dstFmt: '???' dstFmt: '???',
error: true error: true
#@err rinfo.fluenterror, rinfo }
return rinfo );
//@err rinfo.fluenterror, rinfo
return rinfo;
}
# Determine the resume's SOURCE format // Determine the resume's SOURCE format
# TODO: replace with detector component // TODO: replace with detector component
rez = rinfo.rez const { rez } = rinfo;
srcFmt = '' let srcFmt = '';
if rez.meta && rez.meta.format #&& rez.meta.format.substr(0, 5).toUpperCase() == 'FRESH' 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';
else } else {
rinfo.fluenterror = HMSTATUS.unknownSchema rinfo.fluenterror = HMSTATUS.unknownSchema;
return rinfo return rinfo;
}
# Determine the TARGET format for the conversion // Determine the TARGET format for the conversion
targetFormat = targetSchema or (if srcFmt == 'JRS' then 'FRESH' else 'JRS') const targetFormat = targetSchema || (srcFmt === 'JRS' ? 'FRESH' : 'JRS');
# Fire the beforeConvert event // Fire the beforeConvert event
this.stat HMEVENT.beforeConvert, this.stat(HMEVENT.beforeConvert, {
srcFile: rinfo.file srcFile: rinfo.file,
srcFmt: srcFmt srcFmt,
dstFile: dst[idx] dstFile: dst[idx],
dstFmt: targetFormat dstFmt: targetFormat
}
);
# Save it to the destination format // Save it to the destination format
try try {
rez.saveAs dst[idx], targetFormat rez.saveAs(dst[idx], targetFormat);
catch err } catch (err) {
if err.badVer if (err.badVer) {
return fluenterror: HMSTATUS.invalidSchemaVersion, quit: true, data: err.badVer return {fluenterror: HMSTATUS.invalidSchemaVersion, quit: true, data: err.badVer};
rez }
}
return rez;
};

View File

@ -1,68 +1,85 @@
###* /*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
/**
Implementation of the 'create' verb for HackMyResume. Implementation of the 'create' verb for HackMyResume.
@module verbs/create @module verbs/create
@license MIT. See LICENSE.md for details. @license MIT. See LICENSE.md for details.
### */
MKDIRP = require 'mkdirp' let CreateVerb;
PATH = require 'path' const MKDIRP = require('mkdirp');
chalk = require 'chalk' const PATH = require('path');
Verb = require '../verbs/verb' const chalk = require('chalk');
_ = require 'underscore' const Verb = require('../verbs/verb');
HMSTATUS = require '../core/status-codes' const _ = require('underscore');
HMEVENT = require '../core/event-codes' const HMSTATUS = require('../core/status-codes');
const HMEVENT = require('../core/event-codes');
module.exports = class CreateVerb extends Verb module.exports = (CreateVerb = class CreateVerb extends Verb {
constructor: -> super 'new', _create constructor() { super('new', _create); }
});
###* Create a new empty resume in either FRESH or JRS format. ### /** Create a new empty resume in either FRESH or JRS format. */
_create = ( src, dst, opts ) -> var _create = function( src, dst, opts ) {
if !src || !src.length if (!src || !src.length) {
@err HMSTATUS.createNameMissing, { quit: true } this.err(HMSTATUS.createNameMissing, { quit: true });
return null return null;
}
results = _.map src, ( t ) -> const results = _.map(src, function( t ) {
return { } if opts.assert and @hasError() if (opts.assert && this.hasError()) { return { }; }
r = _createOne.call @, t, opts const r = _createOne.call(this, t, opts);
if r.fluenterror if (r.fluenterror) {
r.quit = opts.assert r.quit = opts.assert;
@err r.fluenterror, r this.err(r.fluenterror, r);
r }
, @ return r;
}
, this);
if @hasError() and !opts.assert if (this.hasError() && !opts.assert) {
@reject @errorCode this.reject(this.errorCode);
else if !@hasError() } else if (!this.hasError()) {
@resolve results this.resolve(results);
results }
return results;
};
###* Create a single new resume ### /** Create a single new resume */
_createOne = ( t, opts ) -> var _createOne = function( t, opts ) {
try let ret, safeFmt;
ret = null try {
safeFmt = opts.format.toUpperCase() ret = null;
@.stat HMEVENT.beforeCreate, { fmt: safeFmt, file: t } safeFmt = opts.format.toUpperCase();
MKDIRP.sync PATH.dirname( t ) # Ensure dest folder exists; this.stat(HMEVENT.beforeCreate, { fmt: safeFmt, file: t });
RezClass = require '../core/' + safeFmt.toLowerCase() + '-resume' MKDIRP.sync(PATH.dirname( t )); // Ensure dest folder exists;
newRez = RezClass.default() const RezClass = require(`../core/${safeFmt.toLowerCase()}-resume`);
newRez.save t const newRez = RezClass.default();
ret = newRez newRez.save(t);
return ret = newRez;
catch err return;
ret = } catch (err) {
fluenterror: HMSTATUS.createError ret = {
fluenterror: HMSTATUS.createError,
inner: err inner: err
return };
finally return;
@.stat HMEVENT.afterCreate, fmt: safeFmt, file: t, isError: ret.fluenterror }
return ret finally {
this.stat(HMEVENT.afterCreate, {fmt: safeFmt, file: t, isError: ret.fluenterror});
return ret;
}
};

View File

@ -1,82 +1,102 @@
###* /*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
/**
Implementation of the 'peek' verb for HackMyResume. Implementation of the 'peek' verb for HackMyResume.
@module verbs/peek @module verbs/peek
@license MIT. See LICENSE.md for details. @license MIT. See LICENSE.md for details.
### */
Verb = require('../verbs/verb') let PeekVerb;
_ = require('underscore') const Verb = require('../verbs/verb');
__ = require('lodash') const _ = require('underscore');
safeLoadJSON = require('../utils/safe-json-loader') const __ = require('lodash');
HMSTATUS = require('../core/status-codes') const safeLoadJSON = require('../utils/safe-json-loader');
HMEVENT = require('../core/event-codes') const HMSTATUS = require('../core/status-codes');
const HMEVENT = require('../core/event-codes');
module.exports = class PeekVerb extends Verb module.exports = (PeekVerb = class PeekVerb extends Verb {
constructor: -> super 'peek', _peek constructor() { super('peek', _peek); }
});
###* Peek at a resume, resume section, or resume field. ### /** Peek at a resume, resume section, or resume field. */
_peek = ( src, dst, opts ) -> var _peek = function( src, dst, opts ) {
if !src || !src.length if (!src || !src.length) {
@err HMSTATUS.resumeNotFound, { quit: true } this.err(HMSTATUS.resumeNotFound, { quit: true });
return null return null;
}
objPath = (dst && dst[0]) || '' const objPath = (dst && dst[0]) || '';
results = _.map src, ( t ) -> const results = _.map(src, function( t ) {
return { } if opts.assert and @hasError() if (opts.assert && this.hasError()) { return { }; }
tgt = _peekOne.call @, t, objPath const tgt = _peekOne.call(this, t, objPath);
if tgt.error if (tgt.error) {
@setError tgt.error.fluenterror, tgt.error this.setError(tgt.error.fluenterror, tgt.error);
#tgt.error.quit = opts.assert }
#@err tgt.error.fluenterror, tgt.error //tgt.error.quit = opts.assert
tgt //@err tgt.error.fluenterror, tgt.error
, @ return tgt;
}
, this);
if @hasError() and !opts.assert if (this.hasError() && !opts.assert) {
@reject @errorCode this.reject(this.errorCode);
else if !@hasError() } else if (!this.hasError()) {
@resolve results this.resolve(results);
results }
return results;
};
###* Peek at a single resume, resume section, or resume field. ### /** Peek at a single resume, resume section, or resume field. */
_peekOne = ( t, objPath ) -> var _peekOne = function( t, objPath ) {
@stat HMEVENT.beforePeek, { file: t, target: objPath } this.stat(HMEVENT.beforePeek, { file: t, target: objPath });
# Load the input file JSON 1st // Load the input file JSON 1st
obj = safeLoadJSON t const obj = safeLoadJSON(t);
# Fetch the requested object path (or the entire file) // Fetch the requested object path (or the entire file)
tgt = null let tgt = null;
if !obj.ex if (!obj.ex) {
tgt = if objPath then __.get obj.json, objPath else obj.json tgt = objPath ? __.get(obj.json, objPath) : obj.json;
}
## safeLoadJSON can only return a READ error or a PARSE error //# safeLoadJSON can only return a READ error or a PARSE error
pkgError = null let pkgError = null;
if obj.ex if (obj.ex) {
errCode = if obj.ex.op == 'parse' then HMSTATUS.parseError else HMSTATUS.readError const errCode = obj.ex.op === 'parse' ? HMSTATUS.parseError : HMSTATUS.readError;
if errCode == HMSTATUS.readError if (errCode === HMSTATUS.readError) {
obj.ex.quiet = true obj.ex.quiet = true;
pkgError = fluenterror: errCode, inner: obj.ex }
pkgError = {fluenterror: errCode, inner: obj.ex};
}
# Fire the 'afterPeek' event with collected info // Fire the 'afterPeek' event with collected info
@stat HMEVENT.afterPeek, this.stat(HMEVENT.afterPeek, {
file: t file: t,
requested: objPath requested: objPath,
target: if obj.ex then undefined else tgt target: obj.ex ? undefined : tgt,
error: pkgError error: pkgError
}
);
val: if obj.ex then undefined else tgt return {
error: pkgError val: obj.ex ? undefined : tgt,
error: pkgError
};
};

View File

@ -1,58 +1,70 @@
###* /*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
/**
Implementation of the 'validate' verb for HackMyResume. Implementation of the 'validate' verb for HackMyResume.
@module verbs/validate @module verbs/validate
@license MIT. See LICENSE.md for details. @license MIT. See LICENSE.md for details.
### */
FS = require 'fs' let ValidateVerb;
ResumeFactory = require '../core/resume-factory' const FS = require('fs');
SyntaxErrorEx = require '../utils/syntax-error-ex' const ResumeFactory = require('../core/resume-factory');
chalk = require 'chalk' const SyntaxErrorEx = require('../utils/syntax-error-ex');
Verb = require '../verbs/verb' const chalk = require('chalk');
HMSTATUS = require '../core/status-codes' const Verb = require('../verbs/verb');
HMEVENT = require '../core/event-codes' const HMSTATUS = require('../core/status-codes');
_ = require 'underscore' const HMEVENT = require('../core/event-codes');
safeLoadJSON = require '../utils/safe-json-loader' const _ = require('underscore');
const safeLoadJSON = require('../utils/safe-json-loader');
###* An invokable resume validation command. ### /** An invokable resume validation command. */
module.exports = class ValidateVerb extends Verb module.exports = (ValidateVerb = class ValidateVerb extends Verb {
constructor: -> super 'validate', _validate constructor() { super('validate', _validate); }
});
# Validate 1 to N resumes in FRESH or JSON Resume format. // Validate 1 to N resumes in FRESH or JSON Resume format.
_validate = (sources, unused, opts) -> var _validate = function(sources, unused, opts) {
if !sources || !sources.length if (!sources || !sources.length) {
@err HMSTATUS.resumeNotFoundAlt, quit: true this.err(HMSTATUS.resumeNotFoundAlt, {quit: true});
return null return null;
}
validator = require 'is-my-json-valid' const validator = require('is-my-json-valid');
schemas = const schemas = {
fresh: require 'fresh-resume-schema' fresh: require('fresh-resume-schema'),
jars: require '../core/resume.json' jars: require('../core/resume.json')
};
results = _.map sources, (t) -> const results = _.map(sources, function(t) {
r = _validateOne.call @, t, validator, schemas, opts const r = _validateOne.call(this, t, validator, schemas, opts);
@err r.error.fluenterror, r.error if r.error if (r.error) { this.err(r.error.fluenterror, r.error); }
r return r;
, @ }
, this);
if @hasError() and !opts.assert if (this.hasError() && !opts.assert) {
@reject @errorCode this.reject(this.errorCode);
else if !@hasError() } else if (!this.hasError()) {
@resolve results this.resolve(results);
results }
return results;
};
###* /**
Validate a single resume. Validate a single resume.
@returns { @returns {
file: <fileName>, file: <fileName>,
@ -62,41 +74,47 @@ Validate a single resume.
schema: <schemaType>, schema: <schemaType>,
error: <errorObject> error: <errorObject>
} }
### */
_validateOne = (t, validator, schemas, opts) -> var _validateOne = function(t, validator, schemas, opts) {
ret = file: t, isValid: false, status: 'unknown', schema: '-----' const ret = {file: t, isValid: false, status: 'unknown', schema: '-----'};
try try {
# Read and parse the resume JSON. Won't throw. // Read and parse the resume JSON. Won't throw.
obj = safeLoadJSON t const obj = safeLoadJSON(t);
# If success, validate the resume against the schema // If success, validate the resume against the schema
if !obj.ex if (!obj.ex) {
if obj.json.basics then ret.schema = 'jars' else ret.schema = 'fresh' if (obj.json.basics) { ret.schema = 'jars'; } else { ret.schema = 'fresh'; }
validate = validator schemas[ ret.schema ], # Note [1] const validate = validator(schemas[ ret.schema ], // Note [1]
formats: { date: /^\d{4}(?:-(?:0[0-9]{1}|1[0-2]{1})(?:-[0-9]{2})?)?$/ } {formats: { date: /^\d{4}(?:-(?:0[0-9]{1}|1[0-2]{1})(?:-[0-9]{2})?)?$/ }});
ret.isValid = validate obj.json ret.isValid = validate(obj.json);
ret.status = if ret.isValid then 'valid' else 'invalid' ret.status = ret.isValid ? 'valid' : 'invalid';
ret.violations = validate.errors if !ret.isValid if (!ret.isValid) { ret.violations = validate.errors; }
# If failure, package JSON read/parse errors // If failure, package JSON read/parse errors
else } else {
if obj.ex.op == 'parse' let errCode;
errCode = HMSTATUS.parseError if (obj.ex.op === 'parse') {
ret.status = 'broken' errCode = HMSTATUS.parseError;
else ret.status = 'broken';
errCode = HMSTATUS.readError } else {
ret.status = 'missing' errCode = HMSTATUS.readError;
ret.error = ret.status = 'missing';
}
ret.error = {
fluenterror: errCode, fluenterror: errCode,
inner: obj.ex.inner, inner: obj.ex.inner,
quiet: errCode == HMSTATUS.readError quiet: errCode === HMSTATUS.readError
};
}
catch err } catch (err) {
# Package any unexpected exceptions // Package any unexpected exceptions
ret.error = fluenterror: HMSTATUS.validateError, inner: err ret.error = {fluenterror: HMSTATUS.validateError, inner: err};
}
@stat HMEVENT.afterValidate, ret this.stat(HMEVENT.afterValidate, ret);
ret return ret;
};

View File

@ -1,99 +1,114 @@
###* /*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
/**
Definition of the Verb class. Definition of the Verb class.
@module verbs/verb @module verbs/verb
@license MIT. See LICENSE.md for details. @license MIT. See LICENSE.md for details.
### */
EVENTS = require 'events' let Verb;
HMEVENT = require '../core/event-codes' const EVENTS = require('events');
Promise = require 'pinkie-promise' const HMEVENT = require('../core/event-codes');
const 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 = class Verb module.exports = (Verb = class Verb {
###* Constructor. Automatically called at creation. ### /** Constructor. Automatically called at creation. */
constructor: ( @moniker, @workhorse ) -> constructor( moniker, workhorse ) {
@emitter = new EVENTS.EventEmitter() this.moniker = moniker;
return this.workhorse = workhorse;
this.emitter = new EVENTS.EventEmitter();
}
###* Invoke the command. ### /** Invoke the command. */
invoke: -> invoke() {
# Sent the 'begin' notification for this verb // Sent the 'begin' notification for this verb
@stat HMEVENT.begin, cmd: @moniker this.stat(HMEVENT.begin, {cmd: this.moniker});
# Prepare command arguments // Prepare command arguments
argsArray = Array::slice.call arguments const argsArray = Array.prototype.slice.call(arguments);
# Create a promise for this verb instance // Create a promise for this verb instance
that = @ const that = this;
@promise = new Promise (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);
return });
}
###* Forward subscriptions to the event emitter. ### /** Forward subscriptions to the event emitter. */
on: -> @emitter.on.apply @emitter, arguments on() { 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) -> fire(evtName, payload) {
payload = payload || { } payload = payload || { };
payload.cmd = @moniker payload.cmd = this.moniker;
@emitter.emit 'hmr:' + evtName, payload this.emitter.emit(`hmr:${evtName}`, payload);
true return true;
}
###* Handle an error condition. ### /** Handle an error condition. */
err: ( errorCode, payload, hot ) -> err( errorCode, payload, hot ) {
payload = payload || { } payload = payload || { };
payload.sub = payload.fluenterror = errorCode payload.sub = (payload.fluenterror = errorCode);
payload.throw = hot payload.throw = hot;
@setError errorCode, payload this.setError(errorCode, payload);
if payload.quit if (payload.quit) {
@reject errorCode this.reject(errorCode);
@fire 'error', payload }
if hot this.fire('error', payload);
throw payload if (hot) {
true throw payload;
}
return true;
}
###* Fire the 'hmr:status' error event. ### /** Fire the 'hmr:status' error event. */
stat: ( subEvent, payload ) -> stat( subEvent, payload ) {
payload = payload || { } payload = payload || { };
payload.sub = subEvent payload.sub = subEvent;
@fire 'status', payload this.fire('status', payload);
true return true;
}
###* Has an error occurred during this verb invocation? ### /** Has an error occurred during this verb invocation? */
hasError: -> @errorCode || @errorObj hasError() { return this.errorCode || this.errorObj; }
###* Associate error info with the invocation. ### /** Associate error info with the invocation. */
setError: ( code, obj ) -> setError( code, obj ) {
@errorCode = code this.errorCode = code;
@errorObj = obj this.errorObj = obj;
return }
});