1
0
mirror of https://github.com/JuanCanham/HackMyResume.git synced 2024-11-24 17:30:10 +00:00

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.
@module cli/error
@license MIT. See LICENSE.md for details.
###
*/
HMSTATUS = require '../core/status-codes'
FS = require 'fs'
PATH = require 'path'
WRAP = require 'word-wrap'
M2C = require '../utils/md2chalk'
chalk = require 'chalk'
extend = require 'extend'
printf = require 'printf'
SyntaxErrorEx = require '../utils/syntax-error-ex'
require 'string.prototype.startswith'
const HMSTATUS = require('../core/status-codes');
const FS = require('fs');
const PATH = require('path');
const WRAP = require('word-wrap');
const M2C = require('../utils/md2chalk');
const chalk = require('chalk');
const extend = require('extend');
const printf = require('printf');
const SyntaxErrorEx = require('../utils/syntax-error-ex');
require('string.prototype.startswith');
###* Error handler for HackMyResume. All errors are handled here.
@class ErrorHandler ###
module.exports =
/** Error handler for HackMyResume. All errors are handled here.
@class ErrorHandler */
module.exports = {
init: ( debug, assert, silent ) ->
@debug = debug
@assert = assert
@silent = silent
@msgs = require('./msg').errors
@
init( debug, assert, silent ) {
this.debug = debug;
this.assert = assert;
this.silent = silent;
this.msgs = require('./msg').errors;
return this;
},
err: ( ex, shouldExit ) ->
err( ex, shouldExit ) {
# Short-circuit logging output if --silent is on
o = if @silent then () -> else _defaultLog
// Short-circuit logging output if --silent is on
let stack;
const o = this.silent ? function() {} : _defaultLog;
# Special case; can probably be removed.
throw ex if ex.pass
// Special case; can probably be removed.
if (ex.pass) { throw ex; }
# Load error messages
@msgs = @msgs || require('./msg').errors
// Load error messages
this.msgs = this.msgs || require('./msg').errors;
# Handle packaged HMR exceptions
if ex.fluenterror
// Handle packaged HMR exceptions
if (ex.fluenterror) {
# Output the error message
objError = assembleError.call @, ex
o( @[ 'format_' + objError.etype ]( objError.msg ))
// Output the error message
const objError = assembleError.call(this, ex);
o( this[ `format_${objError.etype}` ]( objError.msg ));
# Output the stack (sometimes)
if objError.withStack
// Output the stack (sometimes)
if (objError.withStack) {
stack = ex.stack || (ex.inner && ex.inner.stack);
stack && o( chalk.gray( stack ) );
}
# Quit if necessary
if shouldExit or ex.exit
if @debug
o chalk.cyan('Exiting with error code ' + ex.fluenterror.toString())
if @assert
ex.pass = true
throw ex
process.exit ex.fluenterror
// Quit if necessary
if (shouldExit || ex.exit) {
if (this.debug) {
o(chalk.cyan(`Exiting with error code ${ex.fluenterror.toString()}`));
}
if (this.assert) {
ex.pass = true;
throw ex;
}
return process.exit(ex.fluenterror);
}
# Handle raw exceptions
else
o ex
stackTrace = ex.stack || (ex.inner && ex.inner.stack)
if stackTrace && this.debug
o M2C(ex.stack || ex.inner.stack, 'gray')
// Handle raw exceptions
} else {
o(ex);
const stackTrace = ex.stack || (ex.inner && ex.inner.stack);
if (stackTrace && this.debug) {
return o(M2C(ex.stack || ex.inner.stack, 'gray'));
}
}
},
format_error: ( msg ) ->
msg = msg || ''
chalk.red.bold( if msg.toUpperCase().startsWith('ERROR:') then msg else 'Error: ' + msg )
format_error( msg ) {
msg = msg || '';
return chalk.red.bold( msg.toUpperCase().startsWith('ERROR:') ? msg : `Error: ${msg}` );
},
format_warning: ( brief, msg ) ->
chalk.yellow(brief) + chalk.yellow(msg || '')
format_warning( brief, 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 = ''
withStack = false
quit = false
etype = 'warning'
withStack = true if @debug
let se;
let msg = '';
let withStack = false;
let quit = false;
let etype = 'warning';
if (this.debug) { withStack = true; }
switch ex.fluenterror
switch (ex.fluenterror) {
when HMSTATUS.themeNotFound
msg = printf( M2C( this.msgs.themeNotFound.msg, 'yellow' ), ex.data)
case HMSTATUS.themeNotFound:
msg = printf( M2C( this.msgs.themeNotFound.msg, 'yellow' ), ex.data);
break;
when HMSTATUS.copyCSS
msg = M2C( this.msgs.copyCSS.msg, 'red' )
quit = false
case HMSTATUS.copyCSS:
msg = M2C( this.msgs.copyCSS.msg, 'red' );
quit = false;
break;
when HMSTATUS.resumeNotFound
#msg = M2C( this.msgs.resumeNotFound.msg, 'yellow' );
case HMSTATUS.resumeNotFound:
//msg = M2C( this.msgs.resumeNotFound.msg, 'yellow' );
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
# msg = M2C( this.msgs.missingCommand.msg + " (", 'yellow');
# msg += Object.keys( FCMD.verbs ).map( (v, idx, ar) ->
# return ( if idx == ar.length - 1 then chalk.yellow('or ') else '') +
# chalk.yellow.bold(v.toUpperCase());
# ).join( chalk.yellow(', ')) + chalk.yellow(").\n\n");
case HMSTATUS.missingCommand:
// msg = M2C( this.msgs.missingCommand.msg + " (", 'yellow');
// msg += Object.keys( FCMD.verbs ).map( (v, idx, ar) ->
// return ( if idx == ar.length - 1 then chalk.yellow('or ') else '') +
// chalk.yellow.bold(v.toUpperCase());
// ).join( chalk.yellow(', ')) + chalk.yellow(").\n\n");
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
msg = printf( M2C( this.msgs.invalidCommand.msg, 'yellow'), ex.attempted )
case HMSTATUS.invalidCommand:
msg = printf( M2C( this.msgs.invalidCommand.msg, 'yellow'), ex.attempted );
break;
when HMSTATUS.resumeNotFoundAlt
msg = M2C( this.msgs.resumeNotFoundAlt.msg, 'yellow' )
case HMSTATUS.resumeNotFoundAlt:
msg = M2C( this.msgs.resumeNotFoundAlt.msg, 'yellow' );
break;
when HMSTATUS.inputOutputParity
msg = M2C( this.msgs.inputOutputParity.msg )
case HMSTATUS.inputOutputParity:
msg = M2C( this.msgs.inputOutputParity.msg );
break;
when HMSTATUS.createNameMissing
msg = M2C( this.msgs.createNameMissing.msg )
case HMSTATUS.createNameMissing:
msg = M2C( this.msgs.createNameMissing.msg );
break;
when HMSTATUS.pdfGeneration
msg = M2C( this.msgs.pdfGeneration.msg, 'bold' )
msg += chalk.red('\n' + ex.inner) if ex.inner
quit = false
etype = 'error'
case HMSTATUS.pdfGeneration:
msg = M2C( this.msgs.pdfGeneration.msg, 'bold' );
if (ex.inner) { msg += chalk.red(`\n${ex.inner}`); }
quit = false;
etype = 'error';
break;
when HMSTATUS.invalid
msg = M2C( this.msgs.invalid.msg, 'red' )
etype = 'error'
case HMSTATUS.invalid:
msg = M2C( this.msgs.invalid.msg, 'red' );
etype = 'error';
break;
when HMSTATUS.generateError
msg = (ex.inner && ex.inner.toString()) || ex
quit = false
etype = 'error'
case HMSTATUS.generateError:
msg = (ex.inner && ex.inner.toString()) || ex;
quit = false;
etype = 'error';
break;
when HMSTATUS.fileSaveError
msg = printf( M2C( this.msgs.fileSaveError.msg ), (ex.inner || ex).toString() )
etype = 'error'
quit = false
case HMSTATUS.fileSaveError:
msg = printf( M2C( this.msgs.fileSaveError.msg ), (ex.inner || ex).toString() );
etype = 'error';
quit = false;
break;
when HMSTATUS.invalidFormat
ex.data.forEach( (d) ->
msg += printf( M2C( this.msgs.invalidFormat.msg, 'bold' ),
ex.theme.name.toUpperCase(), d.format.toUpperCase())
, @);
case HMSTATUS.invalidFormat:
ex.data.forEach( function(d) {
return msg += printf( M2C( this.msgs.invalidFormat.msg, 'bold' ),
ex.theme.name.toUpperCase(), d.format.toUpperCase());
}
, this);
break;
when HMSTATUS.missingParam
msg = printf(M2C( this.msgs.missingParam.msg ), ex.expected, ex.helper)
case HMSTATUS.missingParam:
msg = printf(M2C( this.msgs.missingParam.msg ), ex.expected, ex.helper);
break;
when HMSTATUS.invalidHelperUse
msg = printf( M2C( this.msgs.invalidHelperUse.msg ), ex.helper )
if ex.error
msg += '\n--> ' + assembleError.call( this, extend( true, {}, ex, {fluenterror: ex.error} )).msg;
#msg += printf( '\n--> ' + M2C( this.msgs.invalidParamCount.msg ), ex.expected );
quit = false
etype = 'warning'
case HMSTATUS.invalidHelperUse:
msg = printf( M2C( this.msgs.invalidHelperUse.msg ), ex.helper );
if (ex.error) {
msg += `\n--> ${assembleError.call( this, extend( true, {}, ex, {fluenterror: ex.error} )).msg}`;
}
//msg += printf( '\n--> ' + M2C( this.msgs.invalidParamCount.msg ), ex.expected );
quit = false;
etype = 'warning';
break;
when HMSTATUS.notOnPath
msg = printf( M2C(this.msgs.notOnPath.msg, 'bold'), ex.engine)
quit = false
etype = 'error'
case HMSTATUS.notOnPath:
msg = printf( M2C(this.msgs.notOnPath.msg, 'bold'), ex.engine);
quit = false;
etype = 'error';
break;
when HMSTATUS.readError
if !ex.quiet
# eslint-disable-next-line no-console
console.error(printf( M2C(this.msgs.readError.msg, 'red'), ex.file))
msg = ex.inner.toString()
etype = 'error'
case HMSTATUS.readError:
if (!ex.quiet) {
// eslint-disable-next-line no-console
console.error(printf( M2C(this.msgs.readError.msg, 'red'), ex.file));
}
msg = ex.inner.toString();
etype = 'error';
break;
when HMSTATUS.mixedMerge
msg = M2C this.msgs.mixedMerge.msg
quit = false
case HMSTATUS.mixedMerge:
msg = M2C(this.msgs.mixedMerge.msg);
quit = false;
break;
when HMSTATUS.invokeTemplate
msg = M2C this.msgs.invokeTemplate.msg, 'red'
msg += M2C( '\n' + WRAP(ex.inner.toString(), { width: 60, indent: ' ' }), 'gray' );
etype = 'custom'
case HMSTATUS.invokeTemplate:
msg = M2C(this.msgs.invokeTemplate.msg, 'red');
msg += M2C( `\n${WRAP(ex.inner.toString(), { width: 60, indent: ' ' })}`, 'gray' );
etype = 'custom';
break;
when HMSTATUS.compileTemplate
etype = 'error'
case HMSTATUS.compileTemplate:
etype = 'error';
break;
when HMSTATUS.themeLoad
case HMSTATUS.themeLoad:
msg = M2C( printf( this.msgs.themeLoad.msg, ex.attempted.toUpperCase() ), 'red');
if ex.inner && ex.inner.fluenterror
msg += M2C('\nError: ', 'red') + assembleError.call( this, ex.inner ).msg
quit = true
etype = 'custom'
if (ex.inner && ex.inner.fluenterror) {
msg += M2C('\nError: ', 'red') + assembleError.call( this, ex.inner ).msg;
}
quit = true;
etype = 'custom';
break;
when HMSTATUS.parseError
if SyntaxErrorEx.is ex.inner
# eslint-disable-next-line no-console
console.error printf( M2C(this.msgs.readError.msg, 'red'), ex.file )
se = new SyntaxErrorEx ex, ex.raw
if se.line? and se.col?
msg = printf M2C( this.msgs.parseError.msg[0], 'red' ), se.line, se.col
else if se.line?
msg = printf M2C( this.msgs.parseError.msg[1], 'red' ), se.line
else
msg = M2C @msgs.parseError.msg[2], 'red'
else if ex.inner && ex.inner.line? && ex.inner.col?
msg = printf( M2C( this.msgs.parseError.msg[0], 'red' ), ex.inner.line, ex.inner.col)
else
msg = ex
etype = 'error'
case HMSTATUS.parseError:
if (SyntaxErrorEx.is(ex.inner)) {
// eslint-disable-next-line no-console
console.error(printf( M2C(this.msgs.readError.msg, 'red'), ex.file ));
se = new SyntaxErrorEx(ex, ex.raw);
if ((se.line != null) && (se.col != null)) {
msg = printf(M2C( this.msgs.parseError.msg[0], 'red' ), se.line, se.col);
} else if (se.line != null) {
msg = printf(M2C( this.msgs.parseError.msg[1], 'red' ), se.line);
} else {
msg = M2C(this.msgs.parseError.msg[2], 'red');
}
} else if (ex.inner && (ex.inner.line != null) && (ex.inner.col != null)) {
msg = printf( M2C( this.msgs.parseError.msg[0], 'red' ), ex.inner.line, ex.inner.col);
} else {
msg = ex;
}
etype = 'error';
break;
when HMSTATUS.createError
# inner.code could be EPERM, EACCES, etc
msg = printf M2C( this.msgs.createError.msg ), ex.inner.path
etype = 'error'
case HMSTATUS.createError:
// inner.code could be EPERM, EACCES, etc
msg = printf(M2C( this.msgs.createError.msg ), ex.inner.path);
etype = 'error';
break;
when HMSTATUS.validateError
msg = printf M2C( @msgs.validateError.msg ), ex.inner.toString()
etype = 'error'
case HMSTATUS.validateError:
msg = printf(M2C( this.msgs.validateError.msg ), ex.inner.toString());
etype = 'error';
break;
when HMSTATUS.invalidOptionsFile
msg = M2C @msgs.invalidOptionsFile.msg[0]
if SyntaxErrorEx.is ex.inner
# eslint-disable-next-line no-console
console.error printf( M2C(this.msgs.readError.msg, 'red'), ex.file )
se = new SyntaxErrorEx ex, ex.raw
if se.line? and se.col?
msg += printf M2C( this.msgs.parseError.msg[0], 'red' ), se.line, se.col
else if se.line?
msg += printf M2C( this.msgs.parseError.msg[1], 'red' ), se.line
else
msg += M2C @msgs.parseError.msg[2], 'red'
else if ex.inner && ex.inner.line? && ex.inner.col?
msg += printf( M2C( this.msgs.parseError.msg[0], 'red' ), ex.inner.line, ex.inner.col)
else
msg += ex
msg += @msgs.invalidOptionsFile.msg[1]
etype = 'error'
case HMSTATUS.invalidOptionsFile:
msg = M2C(this.msgs.invalidOptionsFile.msg[0]);
if (SyntaxErrorEx.is(ex.inner)) {
// eslint-disable-next-line no-console
console.error(printf( M2C(this.msgs.readError.msg, 'red'), ex.file ));
se = new SyntaxErrorEx(ex, ex.raw);
if ((se.line != null) && (se.col != null)) {
msg += printf(M2C( this.msgs.parseError.msg[0], 'red' ), se.line, se.col);
} else if (se.line != null) {
msg += printf(M2C( this.msgs.parseError.msg[1], 'red' ), se.line);
} else {
msg += M2C(this.msgs.parseError.msg[2], 'red');
}
} else if (ex.inner && (ex.inner.line != null) && (ex.inner.col != null)) {
msg += printf( M2C( this.msgs.parseError.msg[0], 'red' ), ex.inner.line, ex.inner.col);
} else {
msg += ex;
}
msg += this.msgs.invalidOptionsFile.msg[1];
etype = 'error';
break;
when HMSTATUS.optionsFileNotFound
msg = M2C( @msgs.optionsFileNotFound.msg )
etype = 'error'
case HMSTATUS.optionsFileNotFound:
msg = M2C( this.msgs.optionsFileNotFound.msg );
etype = 'error';
break;
when HMSTATUS.unknownSchema
msg = M2C( @msgs.unknownSchema.msg[0] )
#msg += "\n" + M2C( @msgs.unknownSchema.msg[1], 'yellow' )
etype = 'error'
case HMSTATUS.unknownSchema:
msg = M2C( this.msgs.unknownSchema.msg[0] );
//msg += "\n" + M2C( @msgs.unknownSchema.msg[1], 'yellow' )
etype = 'error';
break;
when HMSTATUS.themeHelperLoad
msg = printf M2C( @msgs.themeHelperLoad.msg ), ex.glob
etype = 'error'
case HMSTATUS.themeHelperLoad:
msg = printf(M2C( this.msgs.themeHelperLoad.msg ), ex.glob);
etype = 'error';
break;
when HMSTATUS.invalidSchemaVersion
msg = printf M2C( @msgs.invalidSchemaVersion.msg ), ex.data
etype = 'error'
case HMSTATUS.invalidSchemaVersion:
msg = printf(M2C( this.msgs.invalidSchemaVersion.msg ), ex.data);
etype = 'error';
break;
}
msg: msg # The error message to display
withStack: withStack # Whether to include the stack
quit: quit
etype: etype
return {
msg, // The error message to display
withStack, // Whether to include the stack
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.
@module cli/main
@license MIT. See LICENSE.md for details.
###
*/
HMR = require '../index'
PKG = require '../../package.json'
FS = require 'fs'
EXTEND = require 'extend'
chalk = require 'chalk'
PATH = require 'path'
HMSTATUS = require '../core/status-codes'
safeLoadJSON = require '../utils/safe-json-loader'
#StringUtils = require '../utils/string.js'
_ = require 'underscore'
OUTPUT = require './out'
PAD = require 'string-padding'
Command = require('commander').Command
M2C = require '../utils/md2chalk'
printf = require 'printf'
_opts = { }
_title = chalk.white.bold('\n*** HackMyResume v' +PKG.version+ ' ***')
_out = new OUTPUT( _opts )
_err = require('./error')
_exitCallback = null
const HMR = require('../index');
const PKG = require('../../package.json');
const FS = require('fs');
const EXTEND = require('extend');
const chalk = require('chalk');
const PATH = require('path');
const HMSTATUS = require('../core/status-codes');
const safeLoadJSON = require('../utils/safe-json-loader');
//StringUtils = require '../utils/string.js'
const _ = require('underscore');
const OUTPUT = require('./out');
const PAD = require('string-padding');
const { Command } = require('commander');
const M2C = require('../utils/md2chalk');
const printf = require('printf');
const _opts = { };
const _title = chalk.white.bold(`\n*** HackMyResume v${PKG.version} ***`);
const _out = new OUTPUT( _opts );
const _err = require('./error');
let _exitCallback = null;
###
/*
A callable implementation of the HackMyResume CLI. Encapsulates the command
line interface as a single method accepting a parameter array.
@alias module:cli/main.main
@param rawArgs {Array} An array of command-line parameters. Will either be
process.argv (in production) or custom parameters (in test).
###
module.exports = ( rawArgs, exitCallback ) ->
*/
module.exports = function( rawArgs, exitCallback ) {
initInfo = initialize( rawArgs, exitCallback )
if initInfo is null
return
const initInfo = initialize( rawArgs, exitCallback );
if (initInfo === null) {
return;
}
args = initInfo.args
const { args } = initInfo;
# Create the top-level (application) command...
program = new Command('hackmyresume')
// Create the top-level (application) command...
const program = new Command('hackmyresume')
.version(PKG.version)
.description(chalk.yellow.bold('*** HackMyResume ***'))
.option('-s --silent', 'Run in silent mode')
@ -55,65 +61,61 @@ module.exports = ( rawArgs, exitCallback ) ->
.option('-a --assert', 'Treat warnings as errors', false)
.option('-v --version', 'Show the version')
.allowUnknownOption();
program.jsonArgs = initInfo.options
program.jsonArgs = initInfo.options;
# Create the NEW command
// Create the NEW command
program
.command 'new'
.arguments '<sources...>'
.option '-f --format <fmt>', 'FRESH or JRS format', 'FRESH'
.alias 'create'
.description 'Create resume(s) in FRESH or JSON RESUME format.'
.action (( sources ) ->
execute.call( this, sources, [], this.opts(), logMsg)
return
)
.command('new')
.arguments('<sources...>')
.option('-f --format <fmt>', 'FRESH or JRS format', 'FRESH')
.alias('create')
.description('Create resume(s) in FRESH or JSON RESUME format.')
.action((function( sources ) {
execute.call( this, sources, [], this.opts(), logMsg);
})
);
# Create the VALIDATE command
// Create the VALIDATE command
program
.command('validate')
.arguments('<sources...>')
.description('Validate a resume in FRESH or JSON RESUME format.')
.action((sources) ->
execute.call( this, sources, [], this.opts(), logMsg)
return
)
.action(function(sources) {
execute.call( this, sources, [], this.opts(), logMsg);
});
# Create the CONVERT command
// Create the CONVERT command
program
.command('convert')
.description('Convert a resume to/from FRESH or JSON RESUME format.')
.option('-f --format <fmt>', 'FRESH or JRS format and optional version', undefined)
.action(->
x = splitSrcDest.call( this );
execute.call( this, x.src, x.dst, this.opts(), logMsg)
return
)
.action(function() {
const x = splitSrcDest.call( this );
execute.call( this, x.src, x.dst, this.opts(), logMsg);
});
# Create the ANALYZE command
// Create the ANALYZE command
program
.command('analyze')
.arguments('<sources...>')
.option('--private', 'Include resume fields marked as private', false)
.description('Analyze one or more resumes.')
.action(( sources ) ->
execute.call( this, sources, [], this.opts(), logMsg)
return
)
.action(function( sources ) {
execute.call( this, sources, [], this.opts(), logMsg);
});
# Create the PEEK command
// Create the PEEK command
program
.command('peek')
.arguments('<sources...>')
.description('Peek at a resume field or section')
#.action(( sources, sectionOrField ) ->
.action(( sources ) ->
dst = if (sources && sources.length > 1) then [sources.pop()] else []
execute.call( this, sources, dst, this.opts(), logMsg)
return
)
//.action(( sources, sectionOrField ) ->
.action(function( sources ) {
const dst = (sources && (sources.length > 1)) ? [sources.pop()] : [];
execute.call( this, sources, dst, this.opts(), logMsg);
});
# Create the BUILD command
// Create the BUILD command
program
.command('build')
.alias('generate')
@ -126,134 +128,150 @@ module.exports = ( rawArgs, exitCallback ) ->
.option('--private', 'Include resume fields marked as private', false)
.option('--no-escape', 'Turn off encoding in Handlebars themes.', false)
.description('Generate resume to multiple formats')
#.action(( sources, targets, options ) ->
.action(->
x = splitSrcDest.call( this );
execute.call( this, x.src, x.dst, this.opts(), logMsg)
return
)
//.action(( sources, targets, options ) ->
.action(function() {
const x = splitSrcDest.call( this );
execute.call( this, x.src, x.dst, this.opts(), logMsg);
});
# Create the HELP command
// Create the HELP command
program
.command('help')
.arguments('[command]')
.description('Get help on a HackMyResume command')
.action ( cmd ) ->
cmd = cmd || 'use'
manPage = FS.readFileSync(
PATH.join(__dirname, 'help/' + cmd + '.txt'),
'utf8')
_out.log M2C(manPage, 'white', 'yellow.bold')
return
.action(function( cmd ) {
cmd = cmd || 'use';
const manPage = FS.readFileSync(
PATH.join(__dirname, `help/${cmd}.txt`),
'utf8');
_out.log(M2C(manPage, 'white', 'yellow.bold'));
});
program.parse( args )
program.parse( args );
if !program.args.length
throw fluenterror: 4
if (!program.args.length) {
throw {fluenterror: 4};
}
};
### Massage command-line args and setup Commander.js. ###
initialize = ( ar, exitCallback ) ->
/* Massage command-line args and setup Commander.js. */
var initialize = function( ar, exitCallback ) {
_exitCallback = exitCallback || process.exit
o = initOptions ar
if o.ex
_err.init false, true, false
if( o.ex.op == 'parse' )
_err.err
fluenterror: if o.ex.op == 'parse' then HMSTATUS.invalidOptionsFile else HMSTATUS.optionsFileNotFound,
_exitCallback = exitCallback || process.exit;
const o = initOptions(ar);
if (o.ex) {
_err.init(false, true, false);
if( o.ex.op === 'parse' ) {
_err.err({
fluenterror: o.ex.op === 'parse' ? HMSTATUS.invalidOptionsFile : HMSTATUS.optionsFileNotFound,
inner: o.ex.inner,
quit: true
else
_err.err fluenterror: HMSTATUS.optionsFileNotFound, inner: o.ex.inner, quit: true
return null
o.silent || logMsg( _title )
});
} else {
_err.err({fluenterror: HMSTATUS.optionsFileNotFound, inner: o.ex.inner, quit: true});
}
return null;
}
o.silent || logMsg( _title );
# Emit debug prelude if --debug was specified
if o.debug
_out.log(chalk.cyan('The -d or --debug switch was specified. DEBUG mode engaged.'))
_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(' 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(' FRESCA:',25, null, PAD.RIGHT)) + chalk.cyan.bold( PKG.dependencies.fresca ))
#_out.log(chalk.cyan(PAD(' fresh-themes:',25, null, PAD.RIGHT)) + chalk.cyan.bold( PKG.dependencies['fresh-themes'] ))
#_out.log(chalk.cyan(PAD(' fresh-jrs-converter:',25, null, PAD.RIGHT)) + chalk.cyan.bold( PKG.dependencies['fresh-jrs-converter'] ))
_out.log('')
// Emit debug prelude if --debug was specified
if (o.debug) {
_out.log(chalk.cyan('The -d or --debug switch was specified. DEBUG mode engaged.'));
_out.log('');
_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(' 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(' 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('');
}
_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)...
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
// Handle invalid verbs here (a bit easier here than in commander.js)...
if (o.verb && !HMR.verbs[ o.verb ] && !HMR.alias[ o.verb ] && (o.verb !== 'help')) {
_err.err({fluenterror: HMSTATUS.invalidCommand, quit: true, attempted: o.orgVerb}, true);
}
# Override the .missingArgument behavior
Command.prototype.missingArgument = () ->
if this.name() != 'help'
_err.err
verb: @name()
// Override the .missingArgument behavior
Command.prototype.missingArgument = function() {
if (this.name() !== 'help') {
_err.err({
verb: this.name(),
fluenterror: HMSTATUS.resumeNotFound
, true
return
}
, true);
}
};
# Override the .helpInformation behavior
Command.prototype.helpInformation = ->
manPage = FS.readFileSync(
PATH.join(__dirname, 'help/use.txt'), 'utf8' )
return M2C(manPage, 'white', 'yellow')
// Override the .helpInformation behavior
Command.prototype.helpInformation = function() {
const manPage = FS.readFileSync(
PATH.join(__dirname, 'help/use.txt'), 'utf8' );
return M2C(manPage, 'white', 'yellow');
};
return {
args: o.args,
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;
}
}
}
}
}
### Init options prior to setting up command infrastructure. ###
initOptions = ( ar ) ->
oVerb
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'
// Grab the --debug flag, --silent, --assert and --no-color flags
const isDebug = _.some(args, v => (v === '-d') || (v === '--debug'));
const isSilent = _.some(args, v => (v === '-s') || (v === '--silent'));
const isAssert = _.some(args, v => (v === '-a') || (v === '--assert'));
const isMono = _.some(args, v => v === '--no-color');
const isNoEscape = _.some(args, v => v === '--no-escape');
return {
color: !isMono,
@ -262,129 +280,142 @@ initOptions = ( ar ) ->
assert: isAssert,
noescape: isNoEscape,
orgVerb: oVerb,
verb: verb,
verb,
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.
TODO: Options loading is a little hacky, for two reasons:
- Commander.js idiosyncracies
- 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
if( cmdO )
o = EXTEND(true, o, cmdO)
// Load the specified options file (if any) and apply options
if( cmdO ) {
o = EXTEND(true, o, cmdO);
}
# Merge in command-line options
o = EXTEND( true, o, this.opts() )
// Merge in command-line options
o = EXTEND( true, o, this.opts() );
# Kludge parent-level options until piping issue is resolved
if this.parent.silent != undefined && this.parent.silent != null
o.silent = this.parent.silent
if this.parent.debug != undefined && this.parent.debug != null
o.debug = this.parent.debug
if this.parent.assert != undefined && this.parent.assert != null
o.assert = this.parent.assert
// Kludge parent-level options until piping issue is resolved
if ((this.parent.silent !== undefined) && (this.parent.silent !== null)) {
o.silent = this.parent.silent;
}
if ((this.parent.debug !== undefined) && (this.parent.debug !== null)) {
o.debug = this.parent.debug;
}
if ((this.parent.assert !== undefined) && (this.parent.assert !== null)) {
o.assert = this.parent.assert;
}
if o.debug
logMsg(chalk.cyan('OPTIONS:') + '\n')
_.each(o, (val, key) ->
if (o.debug) {
logMsg(chalk.cyan('OPTIONS:') + '\n');
_.each(o, (val, key) =>
logMsg(chalk.cyan(' %s') + chalk.cyan.bold(' %s'),
PAD(key,22,null,PAD.RIGHT), val)
);
logMsg('');
}
# Cache
EXTEND( true, _opts, o )
return
// Cache
EXTEND( true, _opts, o );
};
### Split multiple command-line filenames by the 'TO' keyword ###
splitSrcDest = () ->
/* Split multiple command-line filenames by the 'TO' keyword */
var splitSrcDest = function() {
params = this.parent.args.filter((j) -> return String.is(j) )
if params.length == 0
#tmpName = @name()
throw { fluenterror: HMSTATUS.resumeNotFound, verb: @name(), quit: true }
const params = this.parent.args.filter(j => String.is(j));
if (params.length === 0) {
//tmpName = @name()
throw { fluenterror: HMSTATUS.resumeNotFound, verb: this.name(), quit: true };
}
# Find the TO keyword, if any
splitAt = _.findIndex( params, (p) -> return p.toLowerCase() == 'to'; )
// Find the TO keyword, if any
const splitAt = _.findIndex( params, p => p.toLowerCase() === 'to');
# TO can't be the last keyword
if splitAt == params.length - 1 && splitAt != -1
// TO can't be the last keyword
if ((splitAt === (params.length - 1)) && (splitAt !== -1)) {
logMsg(chalk.yellow('Please ') +
chalk.yellow.bold('specify an output file') +
chalk.yellow(' for this operation or ') +
chalk.yellow.bold('omit the TO keyword') +
chalk.yellow('.') )
return
return {
src: params.slice(0, if splitAt == -1 then undefined else splitAt ),
dst: if splitAt == -1 then [] else params.slice( splitAt + 1 )
chalk.yellow('.') );
return;
}
return {
src: params.slice(0, splitAt === -1 ? undefined : splitAt ),
dst: splitAt === -1 ? [] : params.slice( splitAt + 1 )
};
};
### Simple logging placeholder. ###
logMsg = () ->
# eslint-disable-next-line no-console
_opts.silent || console.log.apply( console.log, arguments )
/* Simple logging placeholder. */
var logMsg = function() {
// 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.
@module cli/msg
@license MIT. See LICENSE.md for details.
###
*/
PATH = require 'path'
YAML = require 'yamljs'
module.exports = YAML.load PATH.join __dirname, 'msg.yml'
const PATH = require('path');
const YAML = require('yamljs');
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.
@license MIT. See LICENSE.md for details.
@module cli/out
###
*/
chalk = require('chalk')
HME = require('../core/event-codes')
_ = require('underscore')
M2C = require('../utils/md2chalk.js')
PATH = require('path')
FS = require('fs')
EXTEND = require('extend')
HANDLEBARS = require('handlebars')
YAML = require('yamljs')
printf = require('printf')
pad = require('string-padding')
dbgStyle = 'cyan';
const chalk = require('chalk');
const HME = require('../core/event-codes');
const _ = require('underscore');
const M2C = require('../utils/md2chalk.js');
const PATH = require('path');
const FS = require('fs');
const EXTEND = require('extend');
const HANDLEBARS = require('handlebars');
const YAML = require('yamljs');
let printf = require('printf');
const pad = require('string-padding');
const dbgStyle = 'cyan';
###* A stateful output module. All HMR console output handled here. ###
class OutputHandler
/** A stateful output module. All HMR console output handled here. */
class OutputHandler {
constructor: ( opts ) ->
@init opts
return
constructor( opts ) {
this.init(opts);
}
init: (opts) ->
@opts = EXTEND( true, @opts || { }, opts )
@msgs = YAML.load(PATH.join( __dirname, 'msg.yml' )).events
return
init(opts) {
this.opts = EXTEND( true, this.opts || { }, opts );
this.msgs = YAML.load(PATH.join( __dirname, 'msg.yml' )).events;
}
log: ->
printf = require('printf')
finished = printf.apply( printf, arguments )
@opts.silent || console.log( finished ) # eslint-disable-line no-console
log() {
printf = require('printf');
const finished = printf.apply( printf, arguments );
return this.opts.silent || console.log( finished ); // eslint-disable-line no-console
}
do: ( evt ) ->
do( evt ) {
that = @
L = () -> that.log.apply( that, arguments )
const that = this;
const L = function() { return that.log.apply( that, arguments ); };
switch evt.sub
switch (evt.sub) {
when HME.begin
this.opts.debug &&
L( M2C( this.msgs.begin.msg, dbgStyle), evt.cmd.toUpperCase() )
case HME.begin:
return this.opts.debug &&
L( M2C( this.msgs.begin.msg, dbgStyle), evt.cmd.toUpperCase() );
#when HME.beforeCreate
#L( M2C( this.msgs.beforeCreate.msg, 'green' ), evt.fmt, evt.file )
#break;
//when HME.beforeCreate
//L( M2C( this.msgs.beforeCreate.msg, 'green' ), evt.fmt, evt.file )
//break;
when HME.afterCreate
L( M2C( @msgs.beforeCreate.msg, if evt.isError then 'red' else 'green' ), evt.fmt, evt.file )
case HME.afterCreate:
L( M2C( this.msgs.beforeCreate.msg, evt.isError ? 'red' : 'green' ), evt.fmt, evt.file );
break;
when HME.beforeTheme
this.opts.debug &&
L( M2C( this.msgs.beforeTheme.msg, dbgStyle), evt.theme.toUpperCase() )
case HME.beforeTheme:
return this.opts.debug &&
L( M2C( this.msgs.beforeTheme.msg, dbgStyle), evt.theme.toUpperCase() );
when HME.afterParse
L( M2C( this.msgs.afterRead.msg, 'gray', 'white.dim'), evt.fmt.toUpperCase(), evt.file )
case HME.afterParse:
return L( M2C( this.msgs.afterRead.msg, 'gray', 'white.dim'), evt.fmt.toUpperCase(), evt.file );
when HME.beforeMerge
msg = ''
evt.f.reverse().forEach ( a, idx ) ->
msg += printf( (if idx == 0 then @msgs.beforeMerge.msg[0] else @msgs.beforeMerge.msg[1]), a.file )
, @
L( M2C(msg, (if evt.mixed then 'yellow' else 'gray'), 'white.dim') )
case HME.beforeMerge:
var msg = '';
evt.f.reverse().forEach(function( a, idx ) {
return msg += printf( (idx === 0 ? this.msgs.beforeMerge.msg[0] : this.msgs.beforeMerge.msg[1]), a.file );
}
, this);
return L( M2C(msg, (evt.mixed ? 'yellow' : 'gray'), 'white.dim') );
when HME.applyTheme
@theme = evt.theme;
numFormats = Object.keys( evt.theme.formats ).length;
L( M2C(this.msgs.applyTheme.msg,
if evt.status == 'error' then 'red' else 'gray',
if evt.status == 'error' then 'bold' else 'white.dim'),
case HME.applyTheme:
this.theme = evt.theme;
var numFormats = Object.keys( evt.theme.formats ).length;
return L( M2C(this.msgs.applyTheme.msg,
evt.status === 'error' ? 'red' : 'gray',
evt.status === 'error' ? 'bold' : 'white.dim'),
evt.theme.name.toUpperCase(),
numFormats, if numFormats == 1 then '' else 's' )
numFormats, numFormats === 1 ? '' : 's' );
when HME.end
if evt.cmd == 'build'
themeName = this.theme.name.toUpperCase()
if this.opts.tips && (this.theme.message || this.theme.render)
if this.theme.message
L( M2C( this.msgs.afterBuild.msg[0], 'cyan' ), themeName )
L( M2C( this.theme.message, 'white' ))
else if this.theme.render
L( M2C( this.msgs.afterBuild.msg[0], 'cyan'), themeName)
L( M2C( this.msgs.afterBuild.msg[1], 'white'))
case HME.end:
if (evt.cmd === 'build') {
const themeName = this.theme.name.toUpperCase();
if (this.opts.tips && (this.theme.message || this.theme.render)) {
if (this.theme.message) {
L( M2C( this.msgs.afterBuild.msg[0], 'cyan' ), themeName );
return L( M2C( this.theme.message, 'white' ));
} else if (this.theme.render) {
L( M2C( this.msgs.afterBuild.msg[0], 'cyan'), themeName);
return L( M2C( this.msgs.afterBuild.msg[1], 'white'));
}
}
}
break;
when HME.afterGenerate
suffix = ''
if evt.fmt == 'pdf'
if this.opts.pdf
if this.opts.pdf != 'none'
suffix = printf( M2C( this.msgs.afterGenerate.msg[0], if evt.error then 'red' else 'green' ), this.opts.pdf )
else
L( M2C( this.msgs.afterGenerate.msg[1], 'gray' ), evt.fmt.toUpperCase(), evt.file )
return
case HME.afterGenerate:
var suffix = '';
if (evt.fmt === 'pdf') {
if (this.opts.pdf) {
if (this.opts.pdf !== 'none') {
suffix = printf( M2C( this.msgs.afterGenerate.msg[0], evt.error ? 'red' : 'green' ), this.opts.pdf );
} else {
L( M2C( this.msgs.afterGenerate.msg[1], 'gray' ), evt.fmt.toUpperCase(), evt.file );
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 ),
PATH.relative( process.cwd(), evt.file ) );
when HME.beforeAnalyze
L( M2C( this.msgs.beforeAnalyze.msg, 'green' ), evt.fmt, evt.file)
case HME.beforeAnalyze:
return L( M2C( this.msgs.beforeAnalyze.msg, 'green' ), evt.fmt, evt.file);
when HME.afterAnalyze
info = evt.info
rawTpl = FS.readFileSync( PATH.join( __dirname, 'analyze.hbs' ), 'utf8')
HANDLEBARS.registerHelper( require('../helpers/console-helpers') )
template = HANDLEBARS.compile(rawTpl, { strict: false, assumeObjects: false })
tot = 0
info.keywords.forEach (g) -> tot += g.count
info.keywords.totalKeywords = tot
output = template( info )
@log( chalk.cyan(output) )
case HME.afterAnalyze:
var { info } = evt;
var rawTpl = FS.readFileSync( PATH.join( __dirname, 'analyze.hbs' ), 'utf8');
HANDLEBARS.registerHelper( require('../helpers/console-helpers') );
var template = HANDLEBARS.compile(rawTpl, { strict: false, assumeObjects: false });
var tot = 0;
info.keywords.forEach(g => tot += g.count);
info.keywords.totalKeywords = tot;
var output = template( info );
return this.log( chalk.cyan(output) );
when HME.beforeConvert
L( M2C( this.msgs.beforeConvert.msg, if evt.error then 'red' else 'green' ),
case HME.beforeConvert:
return L( M2C( this.msgs.beforeConvert.msg, evt.error ? 'red' : 'green' ),
evt.srcFile, evt.srcFmt, evt.dstFile, evt.dstFmt
);
when HME.afterInlineConvert
L( M2C( this.msgs.afterInlineConvert.msg, 'gray', 'white.dim' ),
case HME.afterInlineConvert:
return L( M2C( this.msgs.afterInlineConvert.msg, 'gray', 'white.dim' ),
evt.file, evt.fmt );
when HME.afterValidate
style = 'red'
adj = ''
msgs = @msgs.afterValidate.msg;
switch evt.status
when 'valid' then style = 'green'; adj = msgs[1]
when 'invalid' then style = 'yellow'; adj = msgs[2]
when 'broken' then style = 'red'; adj = msgs[3]
when 'missing' then style = 'red'; adj = msgs[4]
when 'unknown' then style = 'red'; adj = msgs[5]
evt.schema = evt.schema.replace('jars','JSON Resume').toUpperCase()
L(M2C( msgs[0], 'white' ) + chalk[style].bold(adj), evt.file, evt.schema)
case HME.afterValidate:
var style = 'red';
var adj = '';
var msgs = this.msgs.afterValidate.msg;
switch (evt.status) {
case 'valid': style = 'green'; adj = msgs[1]; break;
case 'invalid': style = 'yellow'; adj = msgs[2]; break;
case 'broken': style = 'red'; adj = msgs[3]; break;
case 'missing': style = 'red'; adj = msgs[4]; break;
case 'unknown': style = 'red'; adj = msgs[5]; break;
}
evt.schema = evt.schema.replace('jars','JSON Resume').toUpperCase();
L(M2C( msgs[0], 'white' ) + chalk[style].bold(adj), evt.file, evt.schema);
if evt.violations
_.each evt.violations, (err) ->
if (evt.violations) {
_.each(evt.violations, function(err) {
L( chalk.yellow.bold('--> ') +
chalk.yellow(err.field.replace('data.','resume.').toUpperCase() +
' ' + err.message))
return
, @
return
' ' + err.message));
}
, this);
}
return;
when HME.afterPeek
sty = if evt.error then 'red' else ( if evt.target != undefined then 'green' else 'yellow' )
case HME.afterPeek:
var sty = evt.error ? 'red' : ( evt.target !== undefined ? 'green' : 'yellow' );
# "Peeking at 'someKey' in 'someFile'."
if evt.requested
L(M2C(this.msgs.beforePeek.msg[0], sty), evt.requested, evt.file)
else
L(M2C(this.msgs.beforePeek.msg[1], sty), evt.file)
// "Peeking at 'someKey' in 'someFile'."
if (evt.requested) {
L(M2C(this.msgs.beforePeek.msg[0], sty), evt.requested, evt.file);
} else {
L(M2C(this.msgs.beforePeek.msg[1], sty), evt.file);
}
# If the key was present, print it
if evt.target != undefined and !evt.error
# eslint-disable-next-line no-console
console.dir( evt.target, { depth: null, colors: true } )
// If the key was present, print it
if ((evt.target !== undefined) && !evt.error) {
// eslint-disable-next-line no-console
return console.dir( evt.target, { depth: null, colors: true } );
# If the key was not present, but no error occurred, print it
else if !evt.error
L M2C( this.msgs.afterPeek.msg, 'yellow'), evt.requested, evt.file
// If the key was not present, but no error occurred, print it
} else if (!evt.error) {
return L(M2C( this.msgs.afterPeek.msg, 'yellow'), evt.requested, evt.file);
else if evt.error
L chalk.red( evt.error.inner.inner )
} else if (evt.error) {
return L(chalk.red( evt.error.inner.inner ));
}
break;
}
}
}
module.exports = OutputHandler
module.exports = OutputHandler;

View File

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

View File

@ -1,13 +1,15 @@
###
/*
Event code definitions.
@module core/default-options
@license MIT. See LICENSE.md for details.
###
*/
module.exports =
theme: 'modern'
prettify: # See https://github.com/beautify-web/js-beautify#options
indent_size: 2
unformatted: ['em','strong']
max_char: 80, # See lib/html.js in above-linked repo
# wrap_line_length: 120, Don't use this
module.exports = {
theme: 'modern',
prettify: { // ← See https://github.com/beautify-web/js-beautify#options
indent_size: 2,
unformatted: ['em','strong'],
max_char: 80
} // ← 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.
@module core/event-codes
@license MIT. See LICENSE.md for details.
###
*/
module.exports =
error: -1
success: 0
begin: 1
end: 2
beforeRead: 3
afterRead: 4
beforeCreate: 5
afterCreate: 6
beforeTheme: 7
afterTheme: 8
beforeMerge: 9
afterMerge: 10
beforeGenerate: 11
afterGenerate: 12
beforeAnalyze: 13
afterAnalyze: 14
beforeConvert: 15
afterConvert: 16
verifyOutputs: 17
beforeParse: 18
afterParse: 19
beforePeek: 20
afterPeek: 21
beforeInlineConvert: 22
afterInlineConvert: 23
beforeValidate: 24
afterValidate: 25
beforeWrite: 26
afterWrite: 27
module.exports = {
error: -1,
success: 0,
begin: 1,
end: 2,
beforeRead: 3,
afterRead: 4,
beforeCreate: 5,
afterCreate: 6,
beforeTheme: 7,
afterTheme: 8,
beforeMerge: 9,
afterMerge: 10,
beforeGenerate: 11,
afterGenerate: 12,
beforeAnalyze: 13,
afterAnalyze: 14,
beforeConvert: 15,
afterConvert: 16,
verifyOutputs: 17,
beforeParse: 18,
afterParse: 19,
beforePeek: 20,
afterPeek: 21,
beforeInlineConvert: 22,
afterInlineConvert: 23,
beforeValidate: 24,
afterValidate: 25,
beforeWrite: 26,
afterWrite: 27,
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.
@license MIT. See LICENSE.md for details.
@module core/fluent-date
###
*/
moment = require 'moment'
require('../utils/string')
const moment = require('moment');
require('../utils/string');
###*
/**
Create a FluentDate from a string or Moment date object. There are a few date
formats to be aware of here.
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
format or b) use an ISO format. For clarity, we handle these cases explicitly.
@class FluentDate
###
*/
class FluentDate
class FluentDate {
constructor: (dt) ->
@rep = this.fmt dt
constructor(dt) {
this.rep = this.fmt(dt);
}
@isCurrent: (dt) ->
!dt || (String.is(dt) and /^(present|now|current)$/.test(dt))
static isCurrent(dt) {
return !dt || (String.is(dt) && /^(present|now|current)$/.test(dt));
}
}
months = {}
abbr = {}
moment.months().forEach((m,idx) -> months[m.toLowerCase()] = idx+1 )
moment.monthsShort().forEach((m,idx) -> abbr[m.toLowerCase()]=idx+1 )
abbr.sept = 9
module.exports = FluentDate
const months = {};
const abbr = {};
moment.months().forEach((m,idx) => months[m.toLowerCase()] = idx+1);
moment.monthsShort().forEach((m,idx) => abbr[m.toLowerCase()]=idx+1);
abbr.sept = 9;
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
dt = dt.toLowerCase().trim()
if /^(present|now|current)$/.test(dt) # "Present", "Now"
return moment()
else if /^\D+\s+\d{4}$/.test(dt) # "Mar 2015"
parts = dt.split(' ');
month = (months[parts[0]] || abbr[parts[0]]);
temp = parts[1] + '-' + (month < 10 ? '0' + month : month.toString());
return moment temp, 'YYYY-MM'
else if /^\d{4}-\d{1,2}$/.test(dt) # "2015-03", "1998-4"
return moment dt, 'YYYY-MM'
else if /^\s*\d{4}\s*$/.test(dt) # "2015"
return moment dt, 'YYYY'
else if /^\s*$/.test(dt) # "", " "
return moment()
else
mt = moment dt
if mt.isValid()
return mt
if throws
throw 'Invalid date format encountered.'
return null
else
if !dt
return moment()
else if dt.isValid and dt.isValid()
return dt
if throws
throw 'Unknown date object encountered.'
return null
if ((typeof dt === 'string') || dt instanceof String) {
dt = dt.toLowerCase().trim();
if (/^(present|now|current)$/.test(dt)) { // "Present", "Now"
return moment();
} else if (/^\D+\s+\d{4}$/.test(dt)) { // "Mar 2015"
let left;
const parts = dt.split(' ');
const month = (months[parts[0]] || abbr[parts[0]]);
const temp = parts[1] + '-' + ((left = month < 10) != null ? left : `0${{month : month.toString()}}`);
return moment(temp, 'YYYY-MM');
} else if (/^\d{4}-\d{1,2}$/.test(dt)) { // "2015-03", "1998-4"
return moment(dt, 'YYYY-MM');
} else if (/^\s*\d{4}\s*$/.test(dt)) { // "2015"
return moment(dt, 'YYYY');
} else if (/^\s*$/.test(dt)) { // "", " "
return moment();
} else {
const mt = moment(dt);
if (mt.isValid()) {
return mt;
}
if (throws) {
throw 'Invalid date format encountered.';
}
return null;
}
} else {
if (!dt) {
return moment();
} 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.
@license MIT. See LICENSE.md for details.
@module core/fresh-resume
###
*/
FS = require 'fs'
extend = require 'extend'
validator = require 'is-my-json-valid'
_ = require 'underscore'
__ = require 'lodash'
PATH = require 'path'
moment = require 'moment'
XML = require 'xml-escape'
MD = require 'marked'
CONVERTER = require 'fresh-jrs-converter'
JRSResume = require './jrs-resume'
FluentDate = require './fluent-date'
const FS = require('fs');
const extend = require('extend');
let validator = require('is-my-json-valid');
const _ = require('underscore');
const __ = require('lodash');
const PATH = require('path');
const moment = require('moment');
const XML = require('xml-escape');
const MD = require('marked');
const CONVERTER = require('fresh-jrs-converter');
const JRSResume = require('./jrs-resume');
const FluentDate = require('./fluent-date');
###*
/**
A FRESH resume or CV. FRESH resumes are backed by JSON, and each FreshResume
object is an instantiation of that JSON decorated with utility methods.
@constructor
###
class FreshResume# extends AbstractResume
*/
class FreshResume {// extends AbstractResume
###* Initialize the the FreshResume from JSON string data. ###
parse: ( stringData, opts ) ->
@imp = @imp ? raw: stringData
this.parseJSON JSON.parse( stringData ), opts
/** Initialize the the FreshResume from JSON string data. */
parse( stringData, opts ) {
this.imp = this.imp != null ? this.imp : {raw: stringData};
return this.parseJSON(JSON.parse( stringData ), opts);
}
###*
/**
Initialize the FreshResume from JSON.
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 &
@ -49,390 +56,429 @@ class FreshResume# extends AbstractResume
sort: Sort resume items by date.
compute: Prepare computed resume totals.
}
###
parseJSON: ( rep, opts ) ->
*/
parseJSON( rep, opts ) {
if opts and opts.privatize
# Ignore any element with the 'ignore: true' or 'private: true' designator.
scrubber = require '../utils/resume-scrubber'
{ scrubbed, ignoreList, privateList } = scrubber.scrubResume rep, opts
let scrubbed;
if (opts && opts.privatize) {
// Ignore any element with the 'ignore: true' or 'private: true' designator.
let ignoreList, privateList;
const scrubber = require('../utils/resume-scrubber');
({ scrubbed, ignoreList, privateList } = scrubber.scrubResume(rep, opts));
}
# Now apply the resume representation onto this object
extend true, @, if opts and opts.privatize then scrubbed else rep
// Now apply the resume representation onto this object
extend(true, this, opts && opts.privatize ? scrubbed : rep);
# 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
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) || @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();
(opts.compute == undefined || opts.compute) && (@computed = {
// 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
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.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();
((opts.compute === undefined) || opts.compute) && (this.computed = {
numYears: this.duration(),
keywords: this.keywords()
});
}
@
return this;
}
###* Save the sheet to disk (for environments that have disk access). ###
save: ( filename ) ->
@imp.file = filename || @imp.file
FS.writeFileSync @imp.file, @stringify(), 'utf8'
@
/** 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(), 'utf8');
return this;
}
###*
/**
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
safeFormat = (format && format.trim()) || 'FRESH'
// If format isn't specified, default to FRESH
const safeFormat = (format && format.trim()) || 'FRESH';
# Validate against the FRESH version regex
# freshVersionReg = require '../utils/fresh-version-regex'
# if (not freshVersionReg().test( safeFormat ))
# throw badVer: safeFormat
// Validate against the FRESH version regex
// freshVersionReg = require '../utils/fresh-version-regex'
// if (not freshVersionReg().test( safeFormat ))
// throw badVer: safeFormat
parts = safeFormat.split '@'
const parts = safeFormat.split('@');
if parts[0] == 'FRESH'
@imp.file = filename || @imp.file
FS.writeFileSync @imp.file, @stringify(), 'utf8'
if (parts[0] === 'FRESH') {
this.imp.file = filename || this.imp.file;
FS.writeFileSync(this.imp.file, this.stringify(), 'utf8');
else if parts[0] == 'JRS'
useEdgeSchema = if parts.length > 1 then parts[1] == '1' else false
newRep = CONVERTER.toJRS @, edge: useEdgeSchema
FS.writeFileSync filename, JRSResume.stringify( newRep ), 'utf8'
else
throw badVer: safeFormat
@
} else if (parts[0] === 'JRS') {
const useEdgeSchema = parts.length > 1 ? parts[1] === '1' : false;
const newRep = CONVERTER.toJRS(this, {edge: useEdgeSchema});
FS.writeFileSync(filename, JRSResume.stringify( newRep ), 'utf8');
} else {
throw {badVer: safeFormat};
}
return this;
}
###*
/**
Duplicate this FreshResume instance.
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.
We do it this way to create a true clone of the object without re-running any
of the associated processing.
###
dupe: () ->
jso = extend true, { }, @
rnew = new FreshResume()
rnew.parseJSON jso, { }
rnew
*/
dupe() {
const jso = extend(true, { }, this);
const rnew = new FreshResume();
rnew.parseJSON(jso, { });
return rnew;
}
###*
/**
Convert this object to a JSON string, sanitizing meta-properties along the
way.
###
stringify: () -> FreshResume.stringify @
*/
stringify() { return FreshResume.stringify(this); }
###*
/**
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).
TODO: Move this out of FRESHResume.
###
transformStrings: ( filt, transformer ) ->
ret = this.dupe()
trx = require '../utils/string-transformer'
trx ret, filt, transformer
*/
transformStrings( filt, transformer ) {
const ret = this.dupe();
const trx = require('../utils/string-transformer');
return trx(ret, filt, transformer);
}
###*
/**
Create a copy of this resume in which all fields have been interpreted as
Markdown.
###
markdownify: () ->
*/
markdownify() {
MDIN = ( txt ) ->
return MD(txt || '' ).replace(/^\s*<p>|<\/p>\s*$/gi, '')
const MDIN = txt => MD(txt || '' ).replace(/^\s*<p>|<\/p>\s*$/gi, '');
trx = ( key, val ) ->
if key == 'summary'
return MD val
MDIN(val)
const trx = function( key, val ) {
if (key === 'summary') {
return MD(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
Markdown.
###
xmlify: () ->
trx = (key, val) -> XML val
return @transformStrings [], trx
*/
xmlify() {
const trx = (key, val) => XML(val);
return this.transformStrings([], trx);
}
###* Return the resume format. ###
format: () -> 'FRESH'
/** Return the resume format. */
format() { return 'FRESH'; }
###*
/**
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.
###
*/
# TODO: Several problems here:
# 1) Confusing name. Easily confused with the keyword-inspector module, which
# parses resume body text looking for these same keywords. This should probably
# be renamed.
#
# 2) Doesn't bother trying to integrate skills.list with skills.sets if they
# happen to declare different skills, and if skills.sets declares ONE skill and
# skills.list declared 50, only 1 skill will be registered.
#
# 3) In the future, skill.sets should only be able to use skills declared in
# skills.list. That is, skills.list is the official record of a candidate's
# declared skills. skills.sets is just a way of grouping those into skillsets
# for easier consumption.
// TODO: Several problems here:
// 1) Confusing name. Easily confused with the keyword-inspector module, which
// parses resume body text looking for these same keywords. This should probably
// be renamed.
//
// 2) Doesn't bother trying to integrate skills.list with skills.sets if they
// happen to declare different skills, and if skills.sets declares ONE skill and
// skills.list declared 50, only 1 skill will be registered.
//
// 3) In the future, skill.sets should only be able to use skills declared in
// skills.list. That is, skills.list is the official record of a candidate's
// declared skills. skills.sets is just a way of grouping those into skillsets
// for easier consumption.
keywords: () ->
flatSkills = []
if @skills
if @skills.sets
flatSkills = @skills.sets.map((sk) -> sk.skills ).reduce( (a,b) -> a.concat(b) )
else if @skills.list
flatSkills = flatSkills.concat( this.skills.list.map (sk) -> return sk.name )
flatSkills = _.uniq flatSkills
flatSkills
keywords() {
let flatSkills = [];
if (this.skills) {
if (this.skills.sets) {
flatSkills = this.skills.sets.map(sk => sk.skills).reduce( (a,b) => a.concat(b));
} else if (this.skills.list) {
flatSkills = flatSkills.concat( this.skills.list.map(sk => sk.name) );
}
flatSkills = _.uniq(flatSkills);
}
return flatSkills;
}
###*
/**
Reset the sheet to an empty state. TODO: refactor/review
###
clear: ( clearMeta ) ->
clearMeta = ((clearMeta == undefined) && true) || clearMeta
delete this.imp if clearMeta
delete this.computed # Don't use Object.keys() here
delete this.employment
delete this.service
delete this.education
delete this.recognition
delete this.reading
delete this.writing
delete this.interests
delete this.skills
delete this.social
*/
clear( clearMeta ) {
clearMeta = ((clearMeta === undefined) && true) || clearMeta;
if (clearMeta) { delete this.imp; }
delete this.computed; // Don't use Object.keys() here
delete this.employment;
delete this.service;
delete this.education;
delete this.recognition;
delete this.reading;
delete this.writing;
delete this.interests;
delete this.skills;
return delete this.social;
}
###*
/**
Get a safe count of the number of things in a section.
###
count: ( obj ) ->
return 0 if !obj
return obj.history.length if obj.history
return obj.sets.length if obj.sets
obj.length || 0;
*/
count( obj ) {
if (!obj) { return 0; }
if (obj.history) { return obj.history.length; }
if (obj.sets) { return obj.sets.length; }
return obj.length || 0;
}
###* Add work experience to the sheet. ###
add: ( moniker ) ->
defSheet = FreshResume.default()
newObject =
if defSheet[moniker].history
then $.extend( true, {}, defSheet[ moniker ].history[0] )
else
if moniker == 'skills'
then $.extend( true, {}, defSheet.skills.sets[0] )
else $.extend( true, {}, defSheet[ moniker ][0] )
/** Add work experience to the sheet. */
add( moniker ) {
const defSheet = FreshResume.default();
const newObject =
defSheet[moniker].history
? $.extend( true, {}, defSheet[ moniker ].history[0] )
:
moniker === 'skills'
? $.extend( true, {}, defSheet.skills.sets[0] )
: $.extend( true, {}, defSheet[ moniker ][0] );
@[ moniker ] = @[ moniker ] || []
if @[ moniker ].history
@[ moniker ].history.push newObject
else if moniker == 'skills'
@skills.sets.push newObject
else
@[ moniker ].push newObject
newObject
this[ moniker ] = this[ moniker ] || [];
if (this[ moniker ].history) {
this[ moniker ].history.push(newObject);
} else if (moniker === 'skills') {
this.skills.sets.push(newObject);
} else {
this[ moniker ].push(newObject);
}
return newObject;
}
###*
/**
Determine if the sheet includes a specific social profile (eg, GitHub).
###
hasProfile: ( socialNetwork ) ->
socialNetwork = socialNetwork.trim().toLowerCase()
@social && _.some @social, (p) ->
p.network.trim().toLowerCase() == socialNetwork
*/
hasProfile( socialNetwork ) {
socialNetwork = socialNetwork.trim().toLowerCase();
return this.social && _.some(this.social, p => p.network.trim().toLowerCase() === socialNetwork);
}
###* Return the specified network profile. ###
getProfile: ( socialNetwork ) ->
socialNetwork = socialNetwork.trim().toLowerCase()
@social && _.find @social, (sn) ->
sn.network.trim().toLowerCase() == socialNetwork
/** Return the specified network profile. */
getProfile( socialNetwork ) {
socialNetwork = socialNetwork.trim().toLowerCase();
return this.social && _.find(this.social, sn => sn.network.trim().toLowerCase() === socialNetwork);
}
###*
/**
Return an array of profiles for the specified network, for when the user
has multiple eg. GitHub accounts.
###
getProfiles: ( socialNetwork ) ->
socialNetwork = socialNetwork.trim().toLowerCase()
@social && _.filter @social, (sn) ->
sn.network.trim().toLowerCase() == socialNetwork
*/
getProfiles( socialNetwork ) {
socialNetwork = socialNetwork.trim().toLowerCase();
return this.social && _.filter(this.social, sn => sn.network.trim().toLowerCase() === socialNetwork);
}
###* Determine if the sheet includes a specific skill. ###
hasSkill: ( skill ) ->
skill = skill.trim().toLowerCase()
@skills && _.some @skills, (sk) ->
sk.keywords && _.some sk.keywords, (kw) ->
kw.trim().toLowerCase() == skill
/** Determine if the sheet includes a specific skill. */
hasSkill( skill ) {
skill = skill.trim().toLowerCase();
return this.skills && _.some(this.skills, sk =>
sk.keywords && _.some(sk.keywords, kw => kw.trim().toLowerCase() === skill)
);
}
###* Validate the sheet against the FRESH Resume schema. ###
isValid: ( info ) ->
schemaObj = require 'fresh-resume-schema'
validator = require 'is-my-json-valid'
validate = validator( schemaObj, { # See Note [1].
/** Validate the sheet against the FRESH Resume schema. */
isValid( info ) {
const schemaObj = require('fresh-resume-schema');
validator = require('is-my-json-valid');
const validate = validator( schemaObj, { // See Note [1].
formats: { date: /^\d{4}(?:-(?:0[0-9]{1}|1[0-2]{1})(?:-[0-9]{2})?)?$/ }
})
ret = validate @
if !ret
});
const ret = validate(this);
if (!ret) {
this.imp = this.imp || { };
this.imp.validationErrors = validate.errors;
ret
}
return ret;
}
duration: (unit) ->
inspector = require '../inspectors/duration-inspector'
inspector.run @, 'employment.history', 'start', 'end', unit
duration(unit) {
const inspector = require('../inspectors/duration-inspector');
return inspector.run(this, 'employment.history', 'start', 'end', unit);
}
###*
/**
Sort dated things on the sheet by start date descending. Assumes that dates
on the sheet have been processed with _parseDates().
###
sort: () ->
*/
sort() {
byDateDesc = (a,b) ->
if a.safe.start.isBefore(b.safe.start)
then 1
else ( if a.safe.start.isAfter(b.safe.start) then -1 else 0 )
const byDateDesc = function(a,b) {
if (a.safe.start.isBefore(b.safe.start)) {
return 1;
} else { if (a.safe.start.isAfter(b.safe.start)) { return -1; } else { return 0; } }
};
sortSection = ( key ) ->
ar = __.get this, key
if ar && ar.length
datedThings = obj.filter (o) -> o.start
datedThings.sort( byDateDesc );
const sortSection = function( key ) {
const ar = __.get(this, key);
if (ar && ar.length) {
const datedThings = obj.filter(o => o.start);
return datedThings.sort( byDateDesc );
}
};
sortSection 'employment.history'
sortSection 'education.history'
sortSection 'service.history'
sortSection 'projects'
sortSection('employment.history');
sortSection('education.history');
sortSection('service.history');
sortSection('projects');
@writing && @writing.sort (a, b) ->
if a.safe.date.isBefore b.safe.date
then 1
else ( a.safe.date.isAfter(b.safe.date) && -1 ) || 0
return this.writing && this.writing.sort(function(a, b) {
if (a.safe.date.isBefore(b.safe.date)) {
return 1;
} else { return ( a.safe.date.isAfter(b.safe.date) && -1 ) || 0; }
});
}
}
###*
/**
Get the default (starter) sheet.
###
FreshResume.default = () ->
new FreshResume().parseJSON require('fresh-resume-starter').fresh
*/
FreshResume.default = () => new FreshResume().parseJSON(require('fresh-resume-starter').fresh);
###*
/**
Convert the supplied FreshResume to a JSON string, sanitizing meta-properties
along the way.
###
FreshResume.stringify = ( obj ) ->
replacer = ( key,value ) -> # Exclude these keys from stringification
exKeys = ['imp', 'warnings', 'computed', 'filt', 'ctrl', 'index',
'safe', 'result', 'isModified', 'htmlPreview', 'display_progress_bar']
return if _.some( exKeys, (val) -> key.trim() == val )
then undefined else value
JSON.stringify obj, replacer, 2
*/
FreshResume.stringify = function( obj ) {
const replacer = function( key,value ) { // Exclude these keys from stringification
const exKeys = ['imp', 'warnings', 'computed', 'filt', 'ctrl', 'index',
'safe', 'result', 'isModified', 'htmlPreview', 'display_progress_bar'];
if (_.some( exKeys, val => key.trim() === val)) {
return undefined; } else { return value; }
};
return JSON.stringify(obj, replacer, 2);
};
###*
/**
Convert human-friendly dates into formal Moment.js dates for all collections.
We don't want to lose the raw textual date as entered by the user, so we store
the Moment-ified date as a separate property with a prefix of .safe. For ex:
job.startDate is the date as entered by the user. job.safeStartDate is the
parsed Moment.js date that we actually use in processing.
###
_parseDates = () ->
*/
var _parseDates = function() {
_fmt = require('./fluent-date').fmt
that = @
const _fmt = require('./fluent-date').fmt;
const that = this;
# TODO: refactor recursion
replaceDatesInObject = ( obj ) ->
// TODO: refactor recursion
var replaceDatesInObject = function( obj ) {
return if !obj
if Object.prototype.toString.call( obj ) == '[object Array]'
obj.forEach (elem) -> replaceDatesInObject( elem )
return
else if typeof obj == 'object'
if obj._isAMomentObject || obj.safe
return
Object.keys( obj ).forEach (key) -> replaceDatesInObject obj[key]
['start','end','date'].forEach (val) ->
if (obj[val] != undefined) && (!obj.safe || !obj.safe[val])
obj.safe = obj.safe || { }
obj.safe[ val ] = _fmt obj[val]
if obj[val] && (val == 'start') && !obj.end
obj.safe.end = _fmt 'current'
return
return
Object.keys( this ).forEach (member) ->
replaceDatesInObject(that[member])
return
return
if (!obj) { return; }
if (Object.prototype.toString.call( obj ) === '[object Array]') {
obj.forEach(elem => replaceDatesInObject( elem ));
return;
} else if (typeof obj === 'object') {
if (obj._isAMomentObject || obj.safe) {
return;
}
Object.keys( obj ).forEach(key => replaceDatesInObject(obj[key]));
['start','end','date'].forEach(function(val) {
if ((obj[val] !== undefined) && (!obj.safe || !obj.safe[val])) {
obj.safe = obj.safe || { };
obj.safe[ val ] = _fmt(obj[val]);
if (obj[val] && (val === 'start') && !obj.end) {
obj.safe.end = _fmt('current');
return;
}
}
});
return;
}
};
Object.keys( this ).forEach(function(member) {
replaceDatesInObject(that[member]);
});
};
###* Export the Sheet function/ctor. ###
module.exports = FreshResume
/** Export the Sheet function/ctor. */
module.exports = FreshResume;
# Note 1: Adjust default date validation to allow YYYY and YYYY-MM formats
# in addition to YYYY-MM-DD. The original regex:
#
# /^\d{4}-(?:0[0-9]{1}|1[0-2]{1})-[0-9]{2}$/
#
// Note 1: Adjust default date validation to allow YYYY and YYYY-MM formats
// in addition to YYYY-MM-DD. The original regex:
//
// /^\d{4}-(?:0[0-9]{1}|1[0-2]{1})-[0-9]{2}$/
//

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.
@module core/fresh-theme
@license MIT. See LICENSE.md for details.
###
*/
FS = require 'fs'
validator = require 'is-my-json-valid'
_ = require 'underscore'
PATH = require 'path'
parsePath = require 'parse-filepath'
pathExists = require('path-exists').sync
EXTEND = require 'extend'
HMSTATUS = require './status-codes'
moment = require 'moment'
loadSafeJson = require '../utils/safe-json-loader'
READFILES = require 'recursive-readdir-sync'
const FS = require('fs');
const validator = require('is-my-json-valid');
const _ = require('underscore');
const PATH = require('path');
const parsePath = require('parse-filepath');
const pathExists = require('path-exists').sync;
const EXTEND = require('extend');
const HMSTATUS = require('./status-codes');
const moment = require('moment');
const loadSafeJson = require('../utils/safe-json-loader');
const READFILES = require('recursive-readdir-sync');
### A representation of a FRESH theme asset.
@class FRESHTheme ###
class FRESHTheme
/* A representation of a FRESH theme asset.
@class FRESHTheme */
class FRESHTheme {
constructor: () ->
@baseFolder = 'src'
return
constructor() {
this.baseFolder = 'src';
}
### Open and parse the specified theme. ###
open: ( themeFolder ) ->
/* Open and parse the specified theme. */
open( themeFolder ) {
@folder = themeFolder
this.folder = themeFolder;
# Open the [theme-name].json file; should have the same name as folder
pathInfo = parsePath themeFolder
// Open the [theme-name].json file; should have the same name as folder
const pathInfo = parsePath(themeFolder);
# Set up a formats hash for the theme
formatsHash = { }
// Set up a formats hash for the theme
let formatsHash = { };
# Load the theme
themeFile = PATH.join themeFolder, 'theme.json'
themeInfo = loadSafeJson themeFile
if themeInfo.ex
throw
// Load the theme
const themeFile = PATH.join(themeFolder, 'theme.json');
const themeInfo = loadSafeJson(themeFile);
if (themeInfo.ex) {
throw{
fluenterror:
if themeInfo.ex.op == 'parse'
then HMSTATUS.parseError
else HMSTATUS.readError
themeInfo.ex.op === 'parse'
? HMSTATUS.parseError
: HMSTATUS.readError,
inner: themeInfo.ex.inner
};
}
that = this
const that = this;
# Move properties from the theme JSON file to the theme object
EXTEND true, @, themeInfo.json
// Move properties from the theme JSON file to the theme object
EXTEND(true, this, themeInfo.json);
# Check for an "inherits" entry in the theme JSON.
if @inherits
cached = { }
_.each @inherits, (th, key) ->
# First, see if this is one of the predefined FRESH themes. There are
# only a handful of these, but they may change over time, so we need to
# query the official source of truth: the fresh-themes repository, which
# mounts the themes conveniently by name to the module object, and which
# is embedded locally inside the HackMyResume installation.
# TODO: merge this code with
themesObj = require 'fresh-themes'
if _.has themesObj.themes, th
// Check for an "inherits" entry in the theme JSON.
if (this.inherits) {
const cached = { };
_.each(this.inherits, function(th, key) {
// First, see if this is one of the predefined FRESH themes. There are
// only a handful of these, but they may change over time, so we need to
// query the official source of truth: the fresh-themes repository, which
// mounts the themes conveniently by name to the module object, and which
// is embedded locally inside the HackMyResume installation.
// TODO: merge this code with
let themePath;
const themesObj = require('fresh-themes');
if (_.has(themesObj.themes, th)) {
themePath = PATH.join(
parsePath( require.resolve('fresh-themes') ).dirname,
'/themes/',
th
)
else
d = parsePath( th ).dirname
themePath = PATH.join d, th
);
} else {
const d = parsePath( th ).dirname;
themePath = PATH.join(d, th);
}
cached[ th ] = cached[th] || new FRESHTheme().open( themePath )
formatsHash[ key ] = cached[ th ].getFormat( key )
cached[ th ] = cached[th] || new FRESHTheme().open( themePath );
return formatsHash[ key ] = cached[ th ].getFormat( key );
});
}
# Load theme files
formatsHash = _load.call @, formatsHash
// Load theme files
formatsHash = _load.call(this, formatsHash);
# Cache
@formats = formatsHash
// Cache
this.formats = formatsHash;
# Set the official theme name
@name = parsePath( @folder ).name
@
// Set the official theme name
this.name = parsePath( this.folder ).name;
return this;
}
### Determine if the theme supports the specified output format. ###
hasFormat: ( fmt ) -> _.has @formats, fmt
/* Determine if the theme supports the specified output format. */
hasFormat( fmt ) { return _.has(this.formats, fmt); }
### Determine if the theme supports the specified output format. ###
getFormat: ( fmt ) -> @formats[ fmt ]
/* Determine if the theme supports the specified output format. */
getFormat( fmt ) { return this.formats[ fmt ]; }
}
### Load and parse theme source files. ###
_load = (formatsHash) ->
/* Load and parse theme source files. */
var _load = function(formatsHash) {
that = @
major = false
tplFolder = PATH.join @folder, @baseFolder
const that = this;
const major = false;
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,
# containing info for each file. While we're doing that, also build up
# the formatsHash object.
fmts = READFILES(tplFolder).map (absPath) ->
_loadOne.call @, absPath, formatsHash, tplFolder
, @
// Iterate over all files in the theme folder, producing an array, fmts,
// containing info for each file. While we're doing that, also build up
// the formatsHash object.
const fmts = READFILES(tplFolder).map(function(absPath) {
return _loadOne.call(this, absPath, formatsHash, tplFolder);
}
, this);
# Now, get all the CSS files...
@cssFiles = fmts.filter (fmt) -> fmt and (fmt.ext == 'css')
// Now, get all the CSS files...
this.cssFiles = fmts.filter(fmt => fmt && (fmt.ext === 'css'));
# For each CSS file, get its corresponding HTML file. It's possible that
# a theme can have a CSS file but *no* HTML file, as when a theme author
# creates a pure CSS override of an existing theme.
@cssFiles.forEach (cssf) ->
idx = _.findIndex fmts, ( fmt ) ->
fmt && fmt.pre == cssf.pre && fmt.ext == 'html'
cssf.major = false
if idx > -1
fmts[ idx ].css = cssf.data
fmts[ idx ].cssPath = cssf.path
else
if that.inherits
# Found a CSS file without an HTML file in a theme that inherits
# from another theme. This is the override CSS file.
that.overrides = { file: cssf.path, data: cssf.data }
// For each CSS file, get its corresponding HTML file. It's possible that
// a theme can have a CSS file but *no* HTML file, as when a theme author
// creates a pure CSS override of an existing theme.
this.cssFiles.forEach(function(cssf) {
const idx = _.findIndex(fmts, fmt => fmt && (fmt.pre === cssf.pre) && (fmt.ext === 'html'));
cssf.major = false;
if (idx > -1) {
fmts[ idx ].css = cssf.data;
return fmts[ idx ].cssPath = cssf.path;
} else {
if (that.inherits) {
// Found a CSS file without an HTML file in a theme that inherits
// from another theme. This is the override CSS file.
return that.overrides = { file: cssf.path, data: cssf.data };
}
}});
# Now, save all the javascript file paths to a theme property.
jsFiles = fmts.filter (fmt) -> fmt and (fmt.ext == 'js')
@.jsFiles = jsFiles.map (jsf) -> jsf['path']
// Now, save all the javascript file paths to a theme property.
const jsFiles = fmts.filter(fmt => fmt && (fmt.ext === 'js'));
this.jsFiles = jsFiles.map(jsf => jsf['path']);
formatsHash
return formatsHash;
};
### Load a single theme file. ###
_loadOne = ( absPath, formatsHash, tplFolder ) ->
/* Load a single theme file. */
var _loadOne = function( absPath, formatsHash, tplFolder ) {
pathInfo = parsePath absPath
return if pathInfo.basename.toLowerCase() == 'theme.json'
const pathInfo = parsePath(absPath);
if (pathInfo.basename.toLowerCase() === 'theme.json') { return; }
absPathSafe = absPath.trim().toLowerCase()
outFmt = ''
act = 'copy'
isPrimary = false
const absPathSafe = absPath.trim().toLowerCase();
let outFmt = '';
let act = 'copy';
let isPrimary = false;
# If this is an "explicit" theme, all files of importance are specified in
# the "transform" section of the theme.json file.
if @explicit
// If this is an "explicit" theme, all files of importance are specified in
// the "transform" section of the theme.json file.
if (this.explicit) {
outFmt = _.find Object.keys( @formats ), ( fmtKey ) ->
fmtVal = @formats[ fmtKey ]
_.some fmtVal.transform, (fpath) ->
absPathB = PATH.join( @folder, fpath ).trim().toLowerCase()
absPathB == absPathSafe
, @
, @
act = 'transform' if outFmt
outFmt = _.find(Object.keys( this.formats ), function( fmtKey ) {
const fmtVal = this.formats[ fmtKey ];
return _.some(fmtVal.transform, function(fpath) {
const absPathB = PATH.join( this.folder, fpath ).trim().toLowerCase();
return absPathB === absPathSafe;
}
, this);
}
, this);
if (outFmt) { act = 'transform'; }
}
if !outFmt
# If this file lives in a specific format folder within the theme,
# such as "/latex" or "/html", then that format is the implicit output
# format for all files within the folder
portion = pathInfo.dirname.replace tplFolder,''
if portion && portion.trim()
return if portion[1] == '_'
reg = /^(?:\/|\\)(html|latex|doc|pdf|png|partials)(?:\/|\\)?/ig
res = reg.exec( portion )
if res
if res[1] != 'partials'
outFmt = res[1]
act = 'transform' if !@explicit
else
@partials = @partials || []
@partials.push( { name: pathInfo.name, path: absPath } )
return null
if (!outFmt) {
// If this file lives in a specific format folder within the theme,
// such as "/latex" or "/html", then that format is the implicit output
// format for all files within the folder
const portion = pathInfo.dirname.replace(tplFolder,'');
if (portion && portion.trim()) {
if (portion[1] === '_') { return; }
const reg = /^(?:\/|\\)(html|latex|doc|pdf|png|partials)(?:\/|\\)?/ig;
const res = reg.exec( portion );
if (res) {
if (res[1] !== 'partials') {
outFmt = res[1];
if (!this.explicit) { act = 'transform'; }
} else {
this.partials = this.partials || [];
this.partials.push( { name: pathInfo.name, path: absPath } );
return null;
}
}
}
}
# Otherwise, the output format is inferred from the filename, as in
# compact-[outputformat].[extension], for ex, compact-pdf.html
if !outFmt
idx = pathInfo.name.lastIndexOf '-'
outFmt = if idx == -1 then pathInfo.name else pathInfo.name.substr idx+1
act = 'transform' if !@explicit
defFormats = require './default-formats'
isPrimary = _.some defFormats, (form) ->
form.name == outFmt and pathInfo.extname != '.css'
// Otherwise, the output format is inferred from the filename, as in
// compact-[outputformat].[extension], for ex, compact-pdf.html
if (!outFmt) {
const idx = pathInfo.name.lastIndexOf('-');
outFmt = idx === -1 ? pathInfo.name : pathInfo.name.substr(idx+1);
if (!this.explicit) { act = 'transform'; }
const defFormats = require('./default-formats');
isPrimary = _.some(defFormats, form => (form.name === outFmt) && (pathInfo.extname !== '.css'));
}
# Make sure we have a valid formatsHash
// Make sure we have a valid formatsHash
formatsHash[ outFmt ] = formatsHash[outFmt] || {
outFormat: outFmt,
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
if @formats?[ outFmt ]?.symLinks
formatsHash[ outFmt ].symLinks = @formats[ outFmt ].symLinks
# Create the file representation object
obj =
action: act
primary: isPrimary
path: absPath
orgPath: PATH.relative tplFolder, absPath
ext: pathInfo.extname.slice 1
title: friendlyName outFmt
pre: outFmt
# outFormat: outFmt || pathInfo.name,
data: FS.readFileSync absPath, 'utf8'
// Create the file representation object
const obj = {
action: act,
primary: isPrimary,
path: absPath,
orgPath: PATH.relative(tplFolder, absPath),
ext: pathInfo.extname.slice(1),
title: friendlyName(outFmt),
pre: outFmt,
// outFormat: outFmt || pathInfo.name,
data: FS.readFileSync(absPath, 'utf8'),
css: null
};
# Add this file to the list of files for this format type.
formatsHash[ outFmt ].files.push( obj )
obj
// Add this file to the list of files for this format type.
formatsHash[ outFmt ].files.push( obj );
return obj;
};
### Return a more friendly name for certain formats. ###
friendlyName = ( val ) ->
val = (val && val.trim().toLowerCase()) || ''
friendly = { yml: 'yaml', md: 'markdown', txt: 'text' }
friendly[val] || val
/* Return a more friendly name for certain formats. */
var friendlyName = function( val ) {
val = (val && val.trim().toLowerCase()) || '';
const friendly = { yml: 'yaml', md: 'markdown', txt: 'text' };
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.
@license MIT. See LICENSE.md for details.
@module core/jrs-resume
###
*/
FS = require('fs')
extend = require('extend')
validator = require('is-my-json-valid')
_ = require('underscore')
PATH = require('path')
MD = require('marked')
CONVERTER = require('fresh-jrs-converter')
moment = require('moment')
const FS = require('fs');
const extend = require('extend');
let validator = require('is-my-json-valid');
const _ = require('underscore');
const PATH = require('path');
const MD = require('marked');
const CONVERTER = require('fresh-jrs-converter');
const moment = require('moment');
###*
/**
A JRS resume or CV. JRS resumes are backed by JSON, and each JRSResume object
is an instantiation of that JSON decorated with utility methods.
@class JRSResume
###
class JRSResume# 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. ###
parse: ( stringData, opts ) ->
@imp = @imp ? raw: stringData
this.parseJSON JSON.parse( stringData ), opts
/** Determine if the sheet includes a specific social profile (eg, GitHub). */
hasProfile( socialNetwork ) {
socialNetwork = socialNetwork.trim().toLowerCase();
return this.basics.profiles && _.some(this.basics.profiles, p => p.network.trim().toLowerCase() === socialNetwork);
}
###*
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 ) ->
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
/** Determine if the sheet includes a specific skill. */
hasSkill( skill ) {
skill = skill.trim().toLowerCase();
return this.skills && _.some(this.skills, sk =>
sk.keywords && _.some(sk.keywords, kw => kw.trim().toLowerCase() === skill)
);
}
# 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. ###
format: () -> 'JRS'
this.work && this.work.sort(byDateDesc);
this.education && this.education.sort(byDateDesc);
this.volunteer && this.volunteer.sort(byDateDesc);
this.awards && this.awards.sort(function(a, b) {
if (a.safeDate.isBefore(b.safeDate)) {
return 1;
} else { return (a.safeDate.isAfter(b.safeDate) && -1 ) || 0; }
});
return this.publications && this.publications.sort(function(a, b) {
if ( a.safeReleaseDate.isBefore(b.safeReleaseDate) ) {
return 1;
} else { return ( a.safeReleaseDate.isAfter(b.safeReleaseDate) && -1 ) || 0; }
});
}
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() {
###*
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 ret = this.dupe();
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. ###
clear = ( clearMeta ) ->
clearMeta = ((clearMeta == undefined) && true) || clearMeta;
delete this.imp if clearMeta
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
delete this.basics.profiles
const transformer = require('../utils/string-transformer');
return transformer(ret,
[ 'skills','url','website','startDate','endDate', 'releaseDate', 'date',
'phone','email','address','postalCode','city','country','region',
'safeStartDate','safeEndDate' ],
(key, val) => HD(val));
}
};
JRSResume.initClass();
return JRSResume;
})();
###* Add work experience to the sheet. ###
add: ( moniker ) ->
defSheet = JRSResume.default()
newObject = $.extend( true, {}, defSheet[ moniker ][0] )
this[ moniker ] = this[ moniker ] || []
this[ moniker ].push( newObject )
newObject
/** Get the default (empty) sheet. */
JRSResume.default = () => new JRSResume().parseJSON(require('fresh-resume-starter').jrs);
###* 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
way. Don't override .toString().
###
JRSResume.stringify = ( obj ) ->
replacer = ( key,value ) -> # Exclude these keys from stringification
temp = _.some ['imp', 'warnings', 'computed', 'filt', 'ctrl', 'index',
*/
JRSResume.stringify = function( obj ) {
const replacer = function( key,value ) { // Exclude these keys from stringification
const temp = _.some(['imp', 'warnings', 'computed', 'filt', 'ctrl', 'index',
'safeStartDate', 'safeEndDate', 'safeDate', 'safeReleaseDate', 'result',
'isModified', 'htmlPreview', 'display_progress_bar'],
( val ) -> return key.trim() == val
return if temp then undefined else value
JSON.stringify obj, replacer, 2
val => key.trim() === val);
if (temp) { return undefined; } else { return value; }
};
return JSON.stringify(obj, replacer, 2);
};
###*
/**
Convert human-friendly dates into formal Moment.js dates for all collections.
We don't want to lose the raw textual date as entered by the user, so we store
the Moment-ified date as a separate property with a prefix of .safe. For ex:
job.startDate is the date as entered by the user. job.safeStartDate is the
parsed Moment.js date that we actually use in processing.
###
_parseDates = () ->
*/
var _parseDates = function() {
_fmt = require('./fluent-date').fmt
const _fmt = require('./fluent-date').fmt;
@work && @work.forEach (job) ->
job.safeStartDate = _fmt( job.startDate )
job.safeEndDate = _fmt( job.endDate )
@education && @education.forEach (edu) ->
edu.safeStartDate = _fmt( edu.startDate )
edu.safeEndDate = _fmt( edu.endDate )
@volunteer && @volunteer.forEach (vol) ->
vol.safeStartDate = _fmt( vol.startDate )
vol.safeEndDate = _fmt( vol.endDate )
@awards && @awards.forEach (awd) ->
awd.safeDate = _fmt( awd.date )
@publications && @publications.forEach (pub) ->
pub.safeReleaseDate = _fmt( pub.releaseDate )
this.work && this.work.forEach(function(job) {
job.safeStartDate = _fmt( job.startDate );
return job.safeEndDate = _fmt( job.endDate );
});
this.education && this.education.forEach(function(edu) {
edu.safeStartDate = _fmt( edu.startDate );
return edu.safeEndDate = _fmt( edu.endDate );
});
this.volunteer && this.volunteer.forEach(function(vol) {
vol.safeStartDate = _fmt( vol.startDate );
return vol.safeEndDate = _fmt( vol.endDate );
});
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.
###
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.
@module core/jrs-theme
@license MIT. See LICENSE.MD for details.
###
*/
_ = require 'underscore'
PATH = require 'path'
parsePath = require 'parse-filepath'
pathExists = require('path-exists').sync
errors = require './status-codes'
const _ = require('underscore');
const PATH = require('path');
const parsePath = require('parse-filepath');
const pathExists = require('path-exists').sync;
const errors = require('./status-codes');
###*
/**
The JRSTheme class is a representation of a JSON Resume theme asset.
@class JRSTheme
###
class JRSTheme
*/
class JRSTheme {
###*
/**
Open and parse the specified JRS theme.
@method open
###
open: ( thFolder ) ->
*/
open( thFolder ) {
@folder = thFolder
pathInfo = parsePath thFolder
this.folder = thFolder;
const pathInfo = parsePath(thFolder);
# Open and parse the theme's package.json file
pkgJsonPath = PATH.join thFolder, 'package.json'
if pathExists pkgJsonPath
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
this.name = thPkg.name
this.render = (thApi && thApi.render) || undefined
this.engine = 'jrs'
// Open and parse the theme's package.json file
const pkgJsonPath = PATH.join(thFolder, 'package.json');
if (pathExists(pkgJsonPath)) {
const thApi = require(thFolder); // Requiring the folder yields whatever the package.json's "main" is set to
const thPkg = require(pkgJsonPath); // Get the package.json as JSON
this.name = thPkg.name;
this.render = (thApi && thApi.render) || undefined;
this.engine = 'jrs';
# Create theme formats (HTML and PDF). Just add the bare minimum mix of
# properties necessary to allow JSON Resume themes to share a rendering
# path with FRESH themes.
this.formats =
html:
outFormat: 'html'
// Create theme formats (HTML and PDF). Just add the bare minimum mix of
// properties necessary to allow JSON Resume themes to share a rendering
// path with FRESH themes.
this.formats = {
html: {
outFormat: 'html',
files: [{
action: 'transform',
render: this.render,
@ -53,8 +58,9 @@ class JRSTheme
ext: 'html',
css: null
}]
pdf:
outFormat: 'pdf'
},
pdf: {
outFormat: 'pdf',
files: [{
action: 'transform',
render: this.render,
@ -62,25 +68,30 @@ class JRSTheme
ext: 'pdf',
css: null
}]
else
throw fluenterror: errors.missingPackageJSON
@
}
};
} else {
throw {fluenterror: errors.missingPackageJSON};
}
return this;
}
###*
/**
Determine if the theme supports the output format.
@method hasFormat
###
hasFormat: ( fmt ) -> _.has this.formats, fmt
*/
hasFormat( fmt ) { return _.has(this.formats, fmt); }
###*
/**
Return the requested output format.
@method getFormat
###
getFormat: ( fmt ) -> @formats[ fmt ]
*/
getFormat( fmt ) { return this.formats[ fmt ]; }
}
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.
@license MIT. See LICENSE.md for details.
@module core/resume-factory
###
*/
FS = require 'fs'
HMS = require './status-codes'
HME = require './event-codes'
ResumeConverter = require 'fresh-jrs-converter'
chalk = require 'chalk'
SyntaxErrorEx = require '../utils/syntax-error-ex'
_ = require 'underscore'
resumeDetect = require '../utils/resume-detector'
require 'string.prototype.startswith'
const FS = require('fs');
const HMS = require('./status-codes');
const HME = require('./event-codes');
const ResumeConverter = require('fresh-jrs-converter');
const chalk = require('chalk');
const SyntaxErrorEx = require('../utils/syntax-error-ex');
const _ = require('underscore');
const resumeDetect = require('../utils/resume-detector');
require('string.prototype.startswith');
###*
/**
A simple factory class for FRESH and JSON Resumes.
@class ResumeFactory
###
*/
ResumeFactory = module.exports =
const ResumeFactory = (module.exports = {
###*
/**
Load one or more resumes from disk.
@param {Object} opts An options object with settings for the factory as well
@ -41,72 +46,85 @@ ResumeFactory = module.exports =
}
}
###
load: ( sources, opts, emitter ) ->
sources.map( (src) ->
@loadOne( src, opts, emitter )
, @)
*/
load( sources, opts, emitter ) {
return sources.map( function(src) {
return this.loadOne( src, opts, emitter );
}
, this);
},
###* Load a single resume from disk. ###
loadOne: ( src, opts, emitter ) ->
/** Load a single resume from disk. */
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.
toFormat && (toFormat = toFormat.toLowerCase().trim())
// Get the destination format. Can be 'fresh', 'jrs', or null/undefined.
toFormat && (toFormat = toFormat.toLowerCase().trim());
# Load and parse the resume JSON
info = _parse src, opts, emitter
return info if info.fluenterror
// Load and parse the resume JSON
const info = _parse(src, opts, emitter);
if (info.fluenterror) { return info; }
# Determine the resume format: FRESH or JRS
json = info.json
orgFormat = resumeDetect json
if orgFormat == 'unk'
info.fluenterror = HMS.unknownSchema
return info
// Determine the resume format: FRESH or JRS
let { json } = info;
const orgFormat = resumeDetect(json);
if (orgFormat === 'unk') {
info.fluenterror = HMS.unknownSchema;
return info;
}
# Convert between formats if necessary
if toFormat and ( orgFormat != toFormat )
json = ResumeConverter[ 'to' + toFormat.toUpperCase() ] json
// Convert between formats if necessary
if (toFormat && ( orgFormat !== toFormat )) {
json = ResumeConverter[ `to${toFormat.toUpperCase()}` ](json);
}
# Objectify the resume, that is, convert it from JSON to a FRESHResume
# or JRSResume object.
rez = null
if opts.objectify
reqLib = '../core/' + (toFormat || orgFormat) + '-resume'
ResumeClass = require reqLib
rez = new ResumeClass().parseJSON( json, opts.inner )
rez.i().file = src
// Objectify the resume, that is, convert it from JSON to a FRESHResume
// or JRSResume object.
let rez = null;
if (opts.objectify) {
const reqLib = `../core/${toFormat || orgFormat}-resume`;
const ResumeClass = require(reqLib);
rez = new ResumeClass().parseJSON( json, opts.inner );
rez.i().file = src;
}
file: src
json: info.json
rez: rez
return {
file: src,
json: info.json,
rez
};
}
});
_parse = ( fileName, opts, eve ) ->
var _parse = function( fileName, opts, eve ) {
rawData = null
try
let rawData = null;
try {
# Read the file
// Read the file
eve && eve.stat( HME.beforeRead, { file: fileName });
rawData = FS.readFileSync( fileName, 'utf8' );
eve && eve.stat( HME.afterRead, { file: fileName, data: rawData });
# Parse the file
eve && eve.stat HME.beforeParse, { data: rawData }
ret = { json: JSON.parse( rawData ) }
orgFormat =
if ret.json.meta && ret.json.meta.format && ret.json.meta.format.startsWith('FRESH@')
then 'fresh' else 'jrs'
// Parse the file
eve && eve.stat(HME.beforeParse, { data: rawData });
const ret = { json: JSON.parse( rawData ) };
const orgFormat =
ret.json.meta && ret.json.meta.format && ret.json.meta.format.startsWith('FRESH@')
? 'fresh' : 'jrs';
eve && eve.stat HME.afterParse, { file: fileName, data: ret.json, fmt: orgFormat }
return ret
catch err
# Can be ENOENT, EACCES, SyntaxError, etc.
fluenterror: if rawData then HMS.parseError else HMS.readError
inner: err
raw: rawData
file: fileName
eve && eve.stat(HME.afterParse, { file: fileName, data: ret.json, fmt: orgFormat });
return ret;
} catch (err) {
// Can be ENOENT, EACCES, SyntaxError, etc.
return {
fluenterror: rawData ? HMS.parseError : HMS.readError,
inner: err,
raw: rawData,
file: fileName
};
}
};

View File

@ -1,40 +1,41 @@
###*
/**
Status codes for HackMyResume.
@module core/status-codes
@license MIT. See LICENSE.MD for details.
###
*/
module.exports =
success: 0
themeNotFound: 1
copyCss: 2
resumeNotFound: 3
missingCommand: 4
invalidCommand: 5
resumeNotFoundAlt: 6
inputOutputParity: 7
createNameMissing: 8
pdfGeneration: 9
missingPackageJSON: 10
invalid: 11
invalidFormat: 12
notOnPath: 13
readError: 14
parseError: 15
fileSaveError: 16
generateError: 17
invalidHelperUse: 18
mixedMerge: 19
invokeTemplate: 20
compileTemplate: 21
themeLoad: 22
invalidParamCount: 23
missingParam: 24
createError: 25
validateError: 26
invalidOptionsFile: 27
optionsFileNotFound: 28
unknownSchema: 29
themeHelperLoad: 30
module.exports = {
success: 0,
themeNotFound: 1,
copyCss: 2,
resumeNotFound: 3,
missingCommand: 4,
invalidCommand: 5,
resumeNotFoundAlt: 6,
inputOutputParity: 7,
createNameMissing: 8,
pdfGeneration: 9,
missingPackageJSON: 10,
invalid: 11,
invalidFormat: 12,
notOnPath: 13,
readError: 14,
parseError: 15,
fileSaveError: 16,
generateError: 17,
invalidHelperUse: 18,
mixedMerge: 19,
invokeTemplate: 20,
compileTemplate: 21,
themeLoad: 22,
invalidParamCount: 23,
missingParam: 24,
createError: 25,
validateError: 26,
invalidOptionsFile: 27,
optionsFileNotFound: 28,
unknownSchema: 29,
themeHelperLoad: 30,
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.
@module generators/base-generator
@license MIT. See LICENSE.md for details.
###
*/
###*
/**
The BaseGenerator class is the root of the generator hierarchy. Functionality
common to ALL generators lives here.
###
*/
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. ###
constructor: ( @format ) ->
###* Status codes. ###
codes: require '../core/status-codes'
###* Generator options. ###
opts: { }
/** Base-class initialize. */
constructor( format ) {
this.format = format;
}
};
BaseGenerator.initClass();
return BaseGenerator;
})());

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.
@module generators/html-generator
@license MIT. See LICENSE.md for details.
###
*/
TemplateGenerator = require './template-generator'
FS = require 'fs-extra'
HTML = require 'html'
PATH = require 'path'
require 'string.prototype.endswith'
let HtmlGenerator;
const TemplateGenerator = require('./template-generator');
const FS = require('fs-extra');
const HTML = require('html');
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
the HTML resume prior to saving.
###
onBeforeSave: ( info ) ->
if info.outputFile.endsWith '.css'
return info.mk
if @opts.prettify
then HTML.prettyPrint info.mk, this.opts.prettify
else info.mk
*/
onBeforeSave( info ) {
if (info.outputFile.endsWith('.css')) {
return info.mk;
}
if (this.opts.prettify) {
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.
@module generators/html-pdf-generator.js
@license MIT. See LICENSE.md for details.
###
*/
TemplateGenerator = require './template-generator'
FS = require 'fs-extra'
PATH = require 'path'
SLASH = require 'slash'
_ = require 'underscore'
HMSTATUS = require '../core/status-codes'
SPAWN = require '../utils/safe-spawn'
let HtmlPdfCLIGenerator;
const TemplateGenerator = require('./template-generator');
const FS = require('fs-extra');
const PATH = require('path');
const SLASH = require('slash');
const _ = require('underscore');
const HMSTATUS = require('../core/status-codes');
const SPAWN = require('../utils/safe-spawn');
###*
/**
An HTML-driven PDF resume generator for HackMyResume. Talks to Phantom,
wkhtmltopdf, and other PDF engines over a CLI (command-line interface).
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. ###
onBeforeSave: ( info ) ->
#console.dir _.omit( info, 'mk' ), depth: null, colors: true
return info.mk if info.ext != 'html' and info.ext != 'pdf'
safe_eng = info.opts.pdf || 'wkhtmltopdf'
safe_eng = 'phantomjs' if safe_eng == 'phantom'
if _.has engines, safe_eng
@errHandler = info.opts.errHandler
engines[ safe_eng ].call @, info.mk, info.outputFile, info.opts, @onError
return null # halt further processing
/** Generate the binary PDF. */
onBeforeSave( info ) {
//console.dir _.omit( info, 'mk' ), depth: null, colors: true
if ((info.ext !== 'html') && (info.ext !== 'pdf')) { return info.mk; }
let safe_eng = info.opts.pdf || 'wkhtmltopdf';
if (safe_eng === 'phantom') { safe_eng = 'phantomjs'; }
if (_.has(engines, safe_eng)) {
this.errHandler = info.opts.errHandler;
engines[ safe_eng ].call(this, info.mk, info.outputFile, info.opts, this.onError);
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
the references are invalid, the error was already logged. We could use
spawn-watch here but that causes issues on legacy Node.js. ###
onError: (ex, param) ->
param.errHandler?.err? HMSTATUS.pdfGeneration, ex
return
spawn-watch here but that causes issues on legacy Node.js. */
onError(ex, param) {
__guardMethod__(param.errHandler, 'err', o => o.err(HMSTATUS.pdfGeneration, ex));
}
});
# TODO: Move each engine to a separate module
engines =
// TODO: Move each engine to a separate module
var engines = {
###*
/**
Generate a PDF from HTML using wkhtmltopdf's CLI interface.
Spawns a child process with `wkhtmltopdf <source> <target>`. wkhtmltopdf
must be installed and path-accessible.
TODO: If HTML generation has run, reuse that output
TODO: Local web server to ease wkhtmltopdf rendering
###
wkhtmltopdf: (markup, fOut, opts, on_error) ->
# Save the markup to a temporary file
tempFile = fOut.replace /\.pdf$/i, '.pdf.html'
FS.writeFileSync tempFile, markup, 'utf8'
*/
wkhtmltopdf(markup, fOut, opts, on_error) {
// Save the markup to a temporary file
const tempFile = fOut.replace(/\.pdf$/i, '.pdf.html');
FS.writeFileSync(tempFile, markup, 'utf8');
# Prepare wkhtmltopdf arguments.
wkopts = _.extend 'margin-top': '10mm', 'margin-bottom': '10mm', opts.wkhtmltopdf
wkopts = _.flatten _.map wkopts, (v, k) -> ['--' + k, v]
wkargs = wkopts.concat [ tempFile, fOut ]
// Prepare wkhtmltopdf arguments.
let wkopts = _.extend({'margin-top': '10mm', 'margin-bottom': '10mm'}, opts.wkhtmltopdf);
wkopts = _.flatten(_.map(wkopts, (v, k) => [`--${k}`, v]));
const wkargs = wkopts.concat([ tempFile, fOut ]);
SPAWN 'wkhtmltopdf', wkargs , false, on_error, @
return
SPAWN('wkhtmltopdf', wkargs , false, on_error, this);
},
###*
/**
Generate a PDF from HTML using Phantom's CLI interface.
Spawns a child process with `phantomjs <script> <source> <target>`. Phantom
must be installed and path-accessible.
TODO: If HTML generation has run, reuse that output
TODO: Local web server to ease Phantom rendering
###
phantomjs: ( markup, fOut, opts, on_error ) ->
# Save the markup to a temporary file
tempFile = fOut.replace /\.pdf$/i, '.pdf.html'
FS.writeFileSync tempFile, markup, 'utf8'
scriptPath = PATH.relative process.cwd(), PATH.resolve( __dirname, '../utils/rasterize.js' )
scriptPath = SLASH scriptPath
sourcePath = SLASH PATH.relative( process.cwd(), tempFile)
destPath = SLASH PATH.relative( process.cwd(), fOut)
SPAWN 'phantomjs', [ scriptPath, sourcePath, destPath ], false, on_error, @
return
*/
phantomjs( markup, fOut, opts, on_error ) {
// Save the markup to a temporary file
const tempFile = fOut.replace(/\.pdf$/i, '.pdf.html');
FS.writeFileSync(tempFile, markup, 'utf8');
let scriptPath = PATH.relative(process.cwd(), PATH.resolve( __dirname, '../utils/rasterize.js' ));
scriptPath = SLASH(scriptPath);
const sourcePath = SLASH(PATH.relative( process.cwd(), tempFile));
const destPath = SLASH(PATH.relative( process.cwd(), fOut));
SPAWN('phantomjs', [ scriptPath, sourcePath, destPath ], false, on_error, this);
},
###*
/**
Generate a PDF from HTML using WeasyPrint's CLI interface.
Spawns a child process with `weasyprint <source> <target>`. Weasy Print
must be installed and path-accessible.
TODO: If HTML generation has run, reuse that output
###
weasyprint: ( markup, fOut, opts, on_error ) ->
# Save the markup to a temporary file
tempFile = fOut.replace /\.pdf$/i, '.pdf.html'
FS.writeFileSync tempFile, markup, 'utf8'
*/
weasyprint( markup, fOut, opts, on_error ) {
// Save the markup to a temporary file
const tempFile = fOut.replace(/\.pdf$/i, '.pdf.html');
FS.writeFileSync(tempFile, markup, 'utf8');
SPAWN 'weasyprint', [tempFile, fOut], false, on_error, @
return
SPAWN('weasyprint', [tempFile, fOut], false, on_error, this);
}
};
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.
@module generators/html-png-generator
@license MIT. See LICENSE.MD for details.
###
*/
TemplateGenerator = require './template-generator'
FS = require 'fs-extra'
HTML = require 'html'
SLASH = require 'slash'
SPAWN = require '../utils/safe-spawn'
PATH = require 'path'
let HtmlPngGenerator;
const TemplateGenerator = require('./template-generator');
const FS = require('fs-extra');
const HTML = require('html');
const SLASH = require('slash');
const SPAWN = require('../utils/safe-spawn');
const PATH = require('path');
###*
/**
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 ) ->
# TODO: Not currently called or callable.
invoke( rez, themeMarkup, cssInfo, opts ) {}
// TODO: Not currently called or callable.
generate: ( rez, f, opts ) ->
htmlResults = opts.targets.filter (t) -> t.fmt.outFormat == 'html'
htmlFile = htmlResults[0].final.files.filter (fl) ->
fl.info.ext == 'html'
phantom htmlFile[0].data, f
return
generate( rez, f, opts ) {
const htmlResults = opts.targets.filter(t => t.fmt.outFormat === 'html');
const htmlFile = htmlResults[0].final.files.filter(fl => fl.info.ext === 'html');
phantom(htmlFile[0].data, f);
}
});
###*
/**
Generate a PDF from HTML using Phantom's CLI interface.
Spawns a child process with `phantomjs <script> <source> <target>`. Phantom
must be installed and path-accessible.
TODO: If HTML generation has run, reuse that output
TODO: Local web server to ease Phantom rendering
###
*/
phantom = ( markup, fOut ) ->
var phantom = function( markup, fOut ) {
# Save the markup to a temporary file
tempFile = fOut.replace(/\.png$/i, '.png.html');
FS.writeFileSync tempFile, markup, 'utf8'
scriptPath = SLASH( PATH.relative( process.cwd(),
// Save the markup to a temporary file
const tempFile = fOut.replace(/\.png$/i, '.png.html');
FS.writeFileSync(tempFile, markup, 'utf8');
const scriptPath = SLASH( PATH.relative( process.cwd(),
PATH.resolve( __dirname, '../utils/rasterize.js' ) ) );
sourcePath = SLASH( PATH.relative( process.cwd(), tempFile) );
destPath = SLASH( PATH.relative( process.cwd(), fOut) );
info = SPAWN('phantomjs', [ scriptPath, sourcePath, destPath ]);
return
const sourcePath = SLASH( PATH.relative( process.cwd(), tempFile) );
const destPath = SLASH( PATH.relative( process.cwd(), fOut) );
const info = SPAWN('phantomjs', [ scriptPath, sourcePath, destPath ]);
};

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.
@module generators/json-generator
@license MIT. See LICENSE.md for details.
###
*/
BaseGenerator = require './base-generator'
FS = require 'fs'
_ = require 'underscore'
FJCV = require 'fresh-jrs-converter'
let JsonGenerator;
const BaseGenerator = require('./base-generator');
const FS = require('fs');
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 ) ->
altRez = FJCV[ 'to' + if rez.format() == 'FRESH' then 'JRS' else 'FRESH' ] rez
altRez = FJCV.toSTRING( altRez )
#altRez.stringify()
invoke( rez ) {
let altRez = FJCV[ `to${rez.format() === 'FRESH' ? 'JRS' : 'FRESH'}` ](rez);
return altRez = FJCV.toSTRING( altRez );
}
//altRez.stringify()
generate: ( rez, f ) ->
FS.writeFileSync f, @invoke(rez), 'utf8'
return
generate( rez, f ) {
FS.writeFileSync(f, this.invoke(rez), 'utf8');
}
});

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.
@module generators/json-yaml-generator
@license MIT. See LICENSE.md for details.
###
*/
BaseGenerator = require('./base-generator')
FS = require('fs')
YAML = require('yamljs')
let JsonYamlGenerator;
const BaseGenerator = require('./base-generator');
const FS = require('fs');
const YAML = require('yamljs');
###*
/**
JsonYamlGenerator takes a JSON resume object and translates it directly to
JSON without a template, producing an equivalent YAML-formatted resume. See
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 ) ->
YAML.stringify JSON.parse( rez.stringify() ), Infinity, 2
invoke( rez, themeMarkup, cssInfo, opts ) {
return YAML.stringify(JSON.parse( rez.stringify() ), Infinity, 2);
}
generate: ( rez, f, opts ) ->
data = YAML.stringify JSON.parse( rez.stringify() ), Infinity, 2
FS.writeFileSync f, data, 'utf8'
data
generate( rez, f, opts ) {
const data = YAML.stringify(JSON.parse( rez.stringify() ), Infinity, 2);
FS.writeFileSync(f, data, 'utf8');
return data;
}
});

View File

@ -1,14 +1,16 @@
###*
/**
Definition of the LaTeXGenerator class.
@module generators/latex-generator
@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.
###
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.
@module generators/markdown-generator
@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.
###
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
@module generators/template-generator
@license MIT. See LICENSE.md for details.
###
*/
FS = require 'fs-extra'
_ = require 'underscore'
MD = require 'marked'
XML = require 'xml-escape'
PATH = require 'path'
parsePath = require 'parse-filepath'
MKDIRP = require 'mkdirp'
BaseGenerator = require './base-generator'
EXTEND = require 'extend'
FRESHTheme = require '../core/fresh-theme'
JRSTheme = require '../core/jrs-theme'
let TemplateGenerator;
const FS = require('fs-extra');
const _ = require('underscore');
const MD = require('marked');
const XML = require('xml-escape');
const PATH = require('path');
const parsePath = require('parse-filepath');
const MKDIRP = require('mkdirp');
const BaseGenerator = require('./base-generator');
const EXTEND = require('extend');
const FRESHTheme = require('../core/fresh-theme');
const JRSTheme = require('../core/jrs-theme');
###*
/**
TemplateGenerator performs resume generation via local Handlebar or Underscore
style template expansion and is appropriate for text-based formats like HTML,
plain text, and XML versions of Microsoft Word, Excel, and OpenOffice.
@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
HTMLGenerator or MarkdownGenerator. ###
HTMLGenerator or MarkdownGenerator. */
constructor: ( outputFormat, templateFormat, cssFile ) ->
super outputFormat
@tplFormat = templateFormat || outputFormat
return
constructor( outputFormat, templateFormat, cssFile ) {
super(outputFormat);
this.tplFormat = templateFormat || outputFormat;
}
###* Generate a resume using string-based inputs and outputs without touching
/** Generate a resume using string-based inputs and outputs without touching
the filesystem.
@method invoke
@param rez A FreshResume object.
@param opts Generator options.
@returns {Array} An array of objects representing the generated output
files. ###
files. */
invoke: ( rez, opts ) ->
invoke( rez, opts ) {
opts =
if opts
then (@opts = EXTEND( true, { }, _defaultOpts, opts ))
else @opts
opts
? (this.opts = EXTEND( true, { }, _defaultOpts, opts ))
: this.opts;
# Sort such that CSS files are processed before others
curFmt = opts.themeObj.getFormat( this.format )
curFmt.files = _.sortBy curFmt.files, (fi) -> fi.ext != 'css'
// Sort such that CSS files are processed before others
const curFmt = opts.themeObj.getFormat( this.format );
curFmt.files = _.sortBy(curFmt.files, fi => fi.ext !== 'css');
# Run the transformation!
results = curFmt.files.map ( tplInfo, idx ) ->
if tplInfo.action == 'transform'
trx = @transform rez, tplInfo.data, @format, opts, opts.themeObj, curFmt
if tplInfo.ext == 'css'
curFmt.files[idx].data = trx
else tplInfo.ext == 'html'
#tplInfo.css contains the CSS data loaded by theme
#tplInfo.cssPath contains the absolute path to the source CSS File
else
# Images and non-transformable binary files
opts.onTransform? tplInfo
return info: tplInfo, data: trx
, @
// Run the transformation!
const results = curFmt.files.map(function( tplInfo, idx ) {
let trx;
if (tplInfo.action === 'transform') {
trx = this.transform(rez, tplInfo.data, this.format, opts, opts.themeObj, curFmt);
if (tplInfo.ext === 'css') {
curFmt.files[idx].data = trx;
} else { tplInfo.ext === 'html'; }
}
//tplInfo.css contains the CSS data loaded by theme
//tplInfo.cssPath contains the absolute path to the source CSS File
else {}
// Images and non-transformable binary files
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.
@method generate
@param rez A FreshResume object.
@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
@opts = EXTEND true, { }, _defaultOpts, opts
// Prepare
this.opts = EXTEND(true, { }, _defaultOpts, opts);
# Call the string-based generation method
genInfo = @invoke rez, null
outFolder = parsePath( f ).dirname
curFmt = opts.themeObj.getFormat @format
// Call the string-based generation method
const genInfo = this.invoke(rez, null);
const outFolder = parsePath( f ).dirname;
const curFmt = opts.themeObj.getFormat(this.format);
# Process individual files within this format. For example, the HTML
# output format for a theme may have multiple HTML files, CSS files,
# etc. Process them here.
genInfo.files.forEach ( file ) ->
// Process individual files within this format. For example, the HTML
// output format for a theme may have multiple HTML files, CSS files,
// etc. Process them here.
genInfo.files.forEach(function( file ) {
# console.dir _.omit(file.info,'cssData','data','css' )
// console.dir _.omit(file.info,'cssData','data','css' )
# Pre-processing
file.info.orgPath = file.info.orgPath || ''
thisFilePath =
if file.info.primary
then f
else PATH.join outFolder, file.info.orgPath
// Pre-processing
file.info.orgPath = file.info.orgPath || '';
const thisFilePath =
file.info.primary
? f
: PATH.join(outFolder, file.info.orgPath);
if file.info.action != 'copy' and @onBeforeSave
file.data = this.onBeforeSave
theme: opts.themeObj
outputFile: thisFilePath
mk: file.data
opts: @opts,
if ((file.info.action !== 'copy') && this.onBeforeSave) {
file.data = this.onBeforeSave({
theme: opts.themeObj,
outputFile: thisFilePath,
mk: file.data,
opts: this.opts,
ext: file.info.ext
if !file.data
return
});
if (!file.data) {
return;
}
}
# Write the file
opts.beforeWrite? data: thisFilePath
MKDIRP.sync PATH.dirname( thisFilePath )
// Write the file
if (typeof opts.beforeWrite === 'function') {
opts.beforeWrite({data: thisFilePath});
}
MKDIRP.sync(PATH.dirname( thisFilePath ));
if file.info.action != 'copy'
FS.writeFileSync thisFilePath, file.data, encoding: 'utf8', flags: 'w'
else
FS.copySync file.info.path, thisFilePath
opts.afterWrite? data: thisFilePath
if (file.info.action !== 'copy') {
FS.writeFileSync(thisFilePath, file.data, {encoding: 'utf8', flags: 'w'});
} else {
FS.copySync(file.info.path, thisFilePath);
}
if (typeof opts.afterWrite === 'function') {
opts.afterWrite({data: thisFilePath});
}
# Post-processing
if @onAfterSave
@onAfterSave outputFile: fileName, mk: file.data, opts: this.opts
// Post-processing
if (this.onAfterSave) {
return this.onAfterSave({outputFile: fileName, mk: file.data, opts: this.opts});
}
}
, @
, this);
# Some themes require a symlink structure. If so, create it.
createSymLinks curFmt, outFolder
// Some themes require a symlink structure. If so, create it.
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.
@param json A FRESH or JRS resume object.
@param jst The stringified template data
@param format The format name, such as "html" or "latex"
@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 ) ->
if @opts.freezeBreaks
jst = freeze jst
eng = require '../renderers/' + theme.engine + '-generator'
result = eng.generate json, jst, format, curFmt, opts, theme
if this.opts.freezeBreaks
result = unfreeze result
result
transform( json, jst, format, opts, theme, curFmt ) {
if (this.opts.freezeBreaks) {
jst = freeze(jst);
}
const eng = require(`../renderers/${theme.engine}-generator`);
let result = eng.generate(json, jst, format, curFmt, opts, theme);
if (this.opts.freezeBreaks) {
result = unfreeze(result);
}
return result;
}
});
createSymLinks = ( curFmt, outFolder ) ->
# Some themes require a symlink structure. If so, create it.
if curFmt.symLinks
Object.keys( curFmt.symLinks ).forEach (loc) ->
absLoc = PATH.join outFolder, loc
absTarg = PATH.join PATH.dirname(absLoc), curFmt.symLinks[loc]
# Set type to 'file', 'dir', or 'junction' (Windows only)
type = if parsePath( absLoc ).extname then 'file' else 'junction'
var createSymLinks = function( curFmt, outFolder ) {
// Some themes require a symlink structure. If so, create it.
if (curFmt.symLinks) {
Object.keys( curFmt.symLinks ).forEach(function(loc) {
const absLoc = PATH.join(outFolder, loc);
const absTarg = PATH.join(PATH.dirname(absLoc), curFmt.symLinks[loc]);
// Set type to 'file', 'dir', or 'junction' (Windows only)
const type = parsePath( absLoc ).extname ? 'file' : 'junction';
try
FS.symlinkSync absTarg, absLoc, type
catch err
succeeded = false
if err.code == 'EEXIST'
FS.unlinkSync absLoc
try
FS.symlinkSync absTarg, absLoc, type
succeeded = true
if !succeeded
throw ex
return
try {
return FS.symlinkSync(absTarg, absLoc, type);
} catch (err) {
let succeeded = false;
if (err.code === 'EEXIST') {
FS.unlinkSync(absLoc);
try {
FS.symlinkSync(absTarg, absLoc, type);
succeeded = true;
} catch (error) {}
}
if (!succeeded) {
throw ex;
}
}
});
return;
}
};
###* Freeze newlines for protection against errant JST parsers. ###
freeze = ( markup ) ->
markup.replace( _reg.regN, _defaultOpts.nSym )
markup.replace( _reg.regR, _defaultOpts.rSym )
/** Freeze newlines for protection against errant JST parsers. */
var freeze = function( markup ) {
markup.replace( _reg.regN, _defaultOpts.nSym );
return markup.replace( _reg.regR, _defaultOpts.rSym );
};
###* Unfreeze newlines when the coast is clear. ###
unfreeze = ( markup ) ->
markup.replace _reg.regSymR, '\r'
markup.replace _reg.regSymN, '\n'
/** Unfreeze newlines when the coast is clear. */
var unfreeze = function( markup ) {
markup.replace(_reg.regSymR, '\r');
return markup.replace(_reg.regSymN, '\n');
};
###* Default template generator options. ###
_defaultOpts =
engine: 'underscore'
keepBreaks: true
freezeBreaks: false
nSym: '&newl;' # newline entity
rSym: '&retn;' # return entity
template:
interpolate: /\{\{(.+?)\}\}/g
escape: /\{\{\=(.+?)\}\}/g
evaluate: /\{\%(.+?)\%\}/g
/** Default template generator options. */
var _defaultOpts = {
engine: 'underscore',
keepBreaks: true,
freezeBreaks: false,
nSym: '&newl;', // newline entity
rSym: '&retn;', // return entity
template: {
interpolate: /\{\{(.+?)\}\}/g,
escape: /\{\{\=(.+?)\}\}/g,
evaluate: /\{\%(.+?)\%\}/g,
comment: /\{\#(.+?)\#\}/g
filters:
out: ( txt ) -> txt
raw: ( txt ) -> txt
xml: ( txt ) -> XML(txt)
md: ( txt ) -> MD( txt || '' )
mdin: ( txt ) -> MD(txt || '' ).replace(/^\s*<p>|<\/p>\s*$/gi, '')
lower: ( txt ) -> txt.toLowerCase()
link: ( name, url ) ->
return if url then '<a href="' + url + '">' + name + '</a>' else name
prettify: # See https://github.com/beautify-web/js-beautify#options
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
},
filters: {
out( txt ) { return txt; },
raw( txt ) { return txt; },
xml( txt ) { return XML(txt); },
md( txt ) { return MD( txt || '' ); },
mdin( txt ) { return MD(txt || '' ).replace(/^\s*<p>|<\/p>\s*$/gi, ''); },
lower( txt ) { return txt.toLowerCase(); },
link( name, url ) {
if (url) { return `<a href="${url}">${name}</a>`; } else { return name; }
}
},
prettify: { // ← See https://github.com/beautify-web/js-beautify#options
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. ###
_reg =
regN: new RegExp( '\n', 'g' )
regR: new RegExp( '\r', 'g' )
regSymN: new RegExp( _defaultOpts.nSym, 'g' )
/** Regexes for linebreak preservation. */
var _reg = {
regN: new RegExp( '\n', 'g' ),
regR: new RegExp( '\r', 'g' ),
regSymN: new RegExp( _defaultOpts.nSym, 'g' ),
regSymR: new RegExp( _defaultOpts.rSym, 'g' )
};

View File

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

View File

@ -1,16 +1,18 @@
###*
/**
Definition of the YAMLGenerator class.
@module yaml-generator.js
@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.
###
*/
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.
@license MIT. See LICENSE.md for details.
@module helpers/generic-helpers
###
*/
HMSTATUS = require '../core/status-codes'
LO = require 'lodash'
_ = require 'underscore'
unused = require '../utils/string'
const HMSTATUS = require('../core/status-codes');
const LO = require('lodash');
const _ = require('underscore');
const unused = require('../utils/string');
###* Block helper function definitions. ###
BlockHelpers = module.exports =
/** Block helper function definitions. */
const BlockHelpers = (module.exports = {
###*
/**
Emit the enclosed content if the resume has a section with
the specified name. Otherwise, emit an empty string ''.
###
*/
section: ( title, options ) ->
title = title.trim().toLowerCase()
obj = LO.get this.r, title
ret = ''
if obj
if _.isArray obj
if obj.length
ret = options.fn @
else if _.isObject obj
if (obj.history && obj.history.length) || (obj.sets && obj.sets.length)
ret = options.fn @
ret
section( title, options ) {
title = title.trim().toLowerCase();
const obj = LO.get(this.r, title);
let ret = '';
if (obj) {
if (_.isArray(obj)) {
if (obj.length) {
ret = options.fn(this);
}
} else if (_.isObject(obj)) {
if ((obj.history && obj.history.length) || (obj.sets && obj.sets.length)) {
ret = options.fn(this);
}
}
}
return ret;
},
ifHasSkill: ( rez, skill, options ) ->
skUp = skill.toUpperCase()
ret = _.some rez.skills.list, (sk) ->
(skUp.toUpperCase() == sk.name.toUpperCase()) and sk.years
, @
options.fn @ if ret
ifHasSkill( rez, skill, options ) {
const skUp = skill.toUpperCase();
const ret = _.some(rez.skills.list, sk => (skUp.toUpperCase() === sk.name.toUpperCase()) && sk.years
, this);
if (ret) { return options.fn(this); }
},
###*
/**
Emit the enclosed content if the resume has the named
property or subproperty.
###
*/
has: ( title, options ) ->
title = title && title.trim().toLowerCase()
if LO.get this.r, title
return options.fn this
return
has( title, options ) {
title = title && title.trim().toLowerCase();
if (LO.get(this.r, title)) {
return options.fn(this);
}
},
###*
/**
Return true if either value is truthy.
@method either
###
either: ( 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.
@module console-helpers.js
@license MIT. See LICENSE.md for details.
###
*/
PAD = require 'string-padding'
LO = require 'lodash'
CHALK = require 'chalk'
_ = require 'underscore'
require '../utils/string'
const PAD = require('string-padding');
const LO = require('lodash');
const CHALK = require('chalk');
const _ = require('underscore');
require('../utils/string');
consoleFormatHelpers = module.exports =
const consoleFormatHelpers = (module.exports = {
v: ( val, defaultVal, padding, style ) ->
retVal = if ( val is null || val is undefined ) then defaultVal else val
spaces = 0
if String.is padding
spaces = parseInt padding, 10
spaces = 0 if isNaN spaces
else if _.isNumber padding
spaces = padding
v( val, defaultVal, padding, style ) {
let retVal = ( (val === null) || (val === undefined) ) ? defaultVal : val;
let spaces = 0;
if (String.is(padding)) {
spaces = parseInt(padding, 10);
if (isNaN(spaces)) { spaces = 0; }
} else if (_.isNumber(padding)) {
spaces = padding;
}
if spaces != 0
retVal = PAD retVal, Math.abs(spaces), null, if spaces > 0 then PAD.LEFT else PAD.RIGHT
if (spaces !== 0) {
retVal = PAD(retVal, Math.abs(spaces), null, spaces > 0 ? PAD.LEFT : PAD.RIGHT);
}
if style && String.is( style )
retVal = LO.get( CHALK, style )( retVal )
retVal
if (style && String.is( style )) {
retVal = LO.get( CHALK, style )( retVal );
}
return retVal;
},
gapLength: (val) ->
if val < 35
return CHALK.green.bold val
else if val < 95
return CHALK.yellow.bold val
else
return CHALK.red.bold val
gapLength(val) {
if (val < 35) {
return CHALK.green.bold(val);
} else if (val < 95) {
return CHALK.yellow.bold(val);
} else {
return CHALK.red.bold(val);
}
},
style: ( val, style ) ->
LO.get( CHALK, style )( val )
style( val, style ) {
return LO.get( CHALK, style )( val );
},
isPlural: ( val, options ) ->
if val > 1
return options.fn(this)
isPlural( val, options ) {
if (val > 1) {
return options.fn(this);
}
},
pad: ( val, spaces ) ->
PAD val, Math.abs(spaces), null, if spaces > 0 then PAD.LEFT else PAD.RIGHT
pad( val, spaces ) {
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.
@license MIT. See LICENSE.md for details.
@module handlebars-helpers.js
###
*/
HANDLEBARS = require 'handlebars'
_ = require 'underscore'
helpers = require './generic-helpers'
path = require 'path'
blockHelpers = require './block-helpers'
HMS = require '../core/status-codes'
const HANDLEBARS = require('handlebars');
const _ = require('underscore');
const helpers = require('./generic-helpers');
const path = require('path');
const blockHelpers = require('./block-helpers');
const HMS = require('../core/status-codes');
###*
/**
Register useful Handlebars helpers.
@method registerHelpers
###
*/
module.exports = ( theme, rez, opts ) ->
module.exports = function( theme, rez, opts ) {
helpers.theme = theme
helpers.opts = opts
helpers.type = 'handlebars'
helpers.theme = theme;
helpers.opts = opts;
helpers.type = 'handlebars';
# Prepare generic helpers for use with Handlebars. We do this by wrapping them
# in a Handlebars-aware wrapper which calls the helper internally.
wrappedHelpers = _.mapObject helpers, ( hVal, hKey ) ->
if _.isFunction hVal
return _.wrap hVal, (func) ->
args = Array.prototype.slice.call arguments
args.shift() # lose the 1st element (func) [^1]
#args.pop() # lose the last element (HB options hash)
args[ args.length - 1 ] = rez # replace w/ resume object
func.apply @, args # call the generic helper
hVal
, @
HANDLEBARS.registerHelper wrappedHelpers
// Prepare generic helpers for use with Handlebars. We do this by wrapping them
// in a Handlebars-aware wrapper which calls the helper internally.
const wrappedHelpers = _.mapObject(helpers, function( hVal, hKey ) {
if (_.isFunction(hVal)) {
return _.wrap(hVal, function(func) {
const args = Array.prototype.slice.call(arguments);
args.shift(); // lose the 1st element (func) [^1]
//args.pop() # lose the last element (HB options hash)
args[ args.length - 1 ] = rez; // replace w/ resume object
return func.apply(this, args);
}); // call the generic helper
}
return hVal;
}
, this);
HANDLEBARS.registerHelper(wrappedHelpers);
# Prepare Handlebars-specific helpers - "blockHelpers" is really a misnomer
# since any kind of Handlebars-specific helper can live here
HANDLEBARS.registerHelper blockHelpers
// Prepare Handlebars-specific helpers - "blockHelpers" is really a misnomer
// since any kind of Handlebars-specific helper can live here
HANDLEBARS.registerHelper(blockHelpers);
# Register any theme-provided custom helpers...
// Register any theme-provided custom helpers...
# Normalize "theme.helpers" (string or array) to an array
theme.helpers = [ theme.helpers ] if _.isString theme.helpers
// Normalize "theme.helpers" (string or array) to an array
if (_.isString(theme.helpers)) { theme.helpers = [ theme.helpers ]; }
if _.isArray theme.helpers
glob = require 'glob'
slash = require 'slash'
curGlob = null
try
_.each theme.helpers, (fGlob) -> # foreach theme.helpers entry
curGlob = fGlob # ..cache in case of exception
fGlob = path.join theme.folder, fGlob # ..make relative to theme
files = glob.sync slash fGlob # ..expand the glob
if files.length > 0 # ..guard against empty glob
_.each files, (f) -> # ..loop over concrete paths
HANDLEBARS.registerHelper require f # ..register the path
return
else
throw fluenterror: HMS.themeHelperLoad, inner: er, glob: fGlob
return
return
catch ex
throw
fluenterror: HMS.themeHelperLoad
inner: ex
if (_.isArray(theme.helpers)) {
const glob = require('glob');
const slash = require('slash');
let curGlob = null;
try {
_.each(theme.helpers, function(fGlob) { // foreach theme.helpers entry
curGlob = fGlob; // ..cache in case of exception
fGlob = path.join(theme.folder, fGlob); // ..make relative to theme
const files = glob.sync(slash(fGlob)); // ..expand the glob
if (files.length > 0) { // ..guard against empty glob
_.each(files, function(f) { // ..loop over concrete paths
HANDLEBARS.registerHelper(require(f)); // ..register the path
});
} else {
throw {fluenterror: HMS.themeHelperLoad, inner: er, glob: fGlob};
}
});
return;
} catch (ex) {
throw{
fluenterror: HMS.themeHelperLoad,
inner: ex,
glob: curGlob, exit: true
return
};
return;
}
}
};
# [^1]: This little bit of acrobatics ensures that our generic helpers are
# called as generic helpers, not as Handlebars-specific helpers. This allows
# them to be used in other templating engines, like Underscore. If you need a
# Handlebars-specific helper with normal Handlebars context and options, put it
# in block-helpers.coffee.
// [^1]: This little bit of acrobatics ensures that our generic helpers are
// called as generic helpers, not as Handlebars-specific helpers. This allows
// them to be used in other templating engines, like Underscore. If you need a
// Handlebars-specific helper with normal Handlebars context and options, put it
// in block-helpers.coffee.

View File

@ -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.
@license MIT. See LICENSE.md for details.
@module handlebars-helpers.js
###
*/
HANDLEBARS = require('handlebars')
_ = require('underscore')
helpers = require('./generic-helpers')
const HANDLEBARS = require('handlebars');
const _ = require('underscore');
const helpers = require('./generic-helpers');
###*
/**
Register useful Underscore helpers.
@method registerHelpers
###
module.exports = ( theme, opts, cssInfo, ctx, eng ) ->
helpers.theme = theme
helpers.opts = opts
helpers.cssInfo = cssInfo
helpers.engine = eng
ctx.h = helpers
_.each helpers, ( hVal, hKey ) ->
if _.isFunction hVal
_.bind hVal, ctx
, @
return
*/
module.exports = function( theme, opts, cssInfo, ctx, eng ) {
helpers.theme = theme;
helpers.opts = opts;
helpers.cssInfo = cssInfo;
helpers.engine = eng;
ctx.h = helpers;
_.each(helpers, function( hVal, hKey ) {
if (_.isFunction(hVal)) {
return _.bind(hVal, ctx);
}
}
, this);
};

View File

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

View File

@ -1,45 +1,54 @@
FluentDate = require '../core/fluent-date'
_ = require 'underscore'
lo = require 'lodash'
/*
* decaffeinate suggestions:
* 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.
@returns The total duration of the sheet's work history, that is, the number
of years between the start date of the earliest job on the resume and the
*latest end date of all jobs in the work history*. This last condition is for
sheets that have overlapping jobs.
###
run: (rez, collKey, startKey, endKey, unit) ->
unit = unit || 'years'
hist = lo.get rez, collKey
return 0 if !hist or !hist.length
*/
run(rez, collKey, startKey, endKey, unit) {
unit = unit || 'years';
const hist = lo.get(rez, collKey);
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,
# where each element in the array is a start date or an end date of a
# job -- it doesn't matter which.
new_e = hist.map ( job ) ->
obj = _.pick( job, [startKey, endKey] )
# Synthesize an end date if this is a "current" gig
obj[endKey] = 'current' if !_.has obj, endKey
if obj && (obj[startKey] || obj[endKey])
obj = _.pairs obj
obj[0][1] = FluentDate.fmt( obj[0][1] )
if obj.length > 1
obj[1][1] = FluentDate.fmt( obj[1][1] )
obj
// Convert the candidate's employment history to an array of dates,
// where each element in the array is a start date or an end date of a
// job -- it doesn't matter which.
let new_e = hist.map(function( job ) {
let obj = _.pick( job, [startKey, endKey] );
// Synthesize an end date if this is a "current" gig
if (!_.has(obj, endKey)) { obj[endKey] = 'current'; }
if (obj && (obj[startKey] || obj[endKey])) {
obj = _.pairs(obj);
obj[0][1] = FluentDate.fmt( obj[0][1] );
if (obj.length > 1) {
obj[1][1] = FluentDate.fmt( obj[1][1] );
}
}
return obj;
});
# Flatten the array, remove empties, and sort
new_e = _.filter _.flatten( new_e, true ), (v) ->
return v && v.length && v[0] && v[0].length
return 0 if !new_e or !new_e.length
new_e = _.sortBy new_e, ( elem ) -> return elem[1].unix()
// Flatten the array, remove empties, and sort
new_e = _.filter(_.flatten( new_e, true ), v => v && v.length && v[0] && v[0].length);
if (!new_e || !new_e.length) { return 0; }
new_e = _.sortBy(new_e, elem => elem[1].unix());
# END CODE DUPLICATION
// END CODE DUPLICATION
firstDate = _.first( new_e )[1];
lastDate = _.last( new_e )[1];
lastDate.diff firstDate, unit
const firstDate = _.first( new_e )[1];
const lastDate = _.last( new_e )[1];
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.
@license MIT. See LICENSE.md for details.
@module inspectors/gap-inspector
###
*/
_ = require 'underscore'
FluentDate = require '../core/fluent-date'
moment = require 'moment'
LO = require 'lodash'
const _ = require('underscore');
const FluentDate = require('../core/fluent-date');
const moment = require('moment');
const LO = require('lodash');
###*
/**
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.
@method run
@return {Array} An array of object representing gaps in the candidate's
@ -31,109 +36,124 @@ gapInspector = module.exports =
end: // A Moment.js date
duration: // Gap length
}
###
*/
run: (rez) ->
run(rez) {
# This is what we'll return
coverage =
gaps: []
overlaps: []
pct: '0%'
duration:
total: 0
work: 0
// This is what we'll return
const coverage = {
gaps: [],
overlaps: [],
pct: '0%',
duration: {
total: 0,
work: 0,
gaps: 0
}
};
# Missing employment section? Bye bye.
hist = LO.get rez, 'employment.history'
// Missing employment section? Bye bye.
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,
# where each element in the array is a start date or an end date of a
# job -- it doesn't matter which.
new_e = hist.map( ( job ) ->
obj = _.pick( job, ['start', 'end'] )
if obj && (obj.start || obj.end)
obj = _.pairs( obj )
obj[0][1] = FluentDate.fmt( obj[0][1] )
if obj.length > 1
obj[1][1] = FluentDate.fmt( obj[1][1] )
return obj
)
// Convert the candidate's employment history to an array of dates,
// where each element in the array is a start date or an end date of a
// job -- it doesn't matter which.
let new_e = hist.map( function( job ) {
let obj = _.pick( job, ['start', 'end'] );
if (obj && (obj.start || obj.end)) {
obj = _.pairs( obj );
obj[0][1] = FluentDate.fmt( obj[0][1] );
if (obj.length > 1) {
obj[1][1] = FluentDate.fmt( obj[1][1] );
}
}
return obj;
});
# Flatten the array, remove empties, and sort
new_e = _.filter _.flatten( new_e, true ), (v) ->
return v && v.length && v[0] && v[0].length
// Flatten the array, remove empties, and sort
new_e = _.filter(_.flatten( new_e, true ), v => v && v.length && v[0] && v[0].length);
return coverage if !new_e || !new_e.length
new_e = _.sortBy new_e, ( elem ) -> return elem[1].unix()
if (!new_e || !new_e.length) { return coverage; }
new_e = _.sortBy(new_e, elem => elem[1].unix());
# Iterate over elements in the array. Each time a start date is found,
# increment a reference count. Each time an end date is found, decrement
# the reference count. When the reference count reaches 0, we have a gap.
# When the reference count is > 0, the candidate is employed. When the
# reference count reaches 2, the candidate is overlapped.
// Iterate over elements in the array. Each time a start date is found,
// increment a reference count. Each time an end date is found, decrement
// the reference count. When the reference count reaches 0, we have a gap.
// When the reference count is > 0, the candidate is employed. When the
// reference count reaches 2, the candidate is overlapped.
num_gaps = 0
ref_count = 0
total_gap_days = 0
gap_start = null
const num_gaps = 0;
let ref_count = 0;
let total_gap_days = 0;
const gap_start = null;
new_e.forEach (point) ->
new_e.forEach(function(point) {
inc = if point[0] == 'start' then 1 else -1
ref_count += inc
const inc = point[0] === 'start' ? 1 : -1;
ref_count += inc;
# If the ref count just reached 0, start a new GAP
if ref_count == 0
coverage.gaps.push( { start: point[1], end: null })
// If the ref count just reached 0, start a new GAP
if (ref_count === 0) {
return coverage.gaps.push( { start: point[1], end: null });
# If the ref count reached 1 by rising, end the last GAP
else if ref_count == 1 && inc == 1
lastGap = _.last( coverage.gaps )
if lastGap
lastGap.end = point[1]
lastGap.duration = lastGap.end.diff( lastGap.start, 'days' )
total_gap_days += lastGap.duration
// If the ref count reached 1 by rising, end the last GAP
} else if ((ref_count === 1) && (inc === 1)) {
const lastGap = _.last( coverage.gaps );
if (lastGap) {
lastGap.end = point[1];
lastGap.duration = lastGap.end.diff( lastGap.start, 'days' );
return total_gap_days += lastGap.duration;
}
# If the ref count reaches 2 by rising, start a new OVERLAP
else if ref_count == 2 && inc == 1
coverage.overlaps.push( { start: point[1], end: null })
// If the ref count reaches 2 by rising, start a new OVERLAP
} else if ((ref_count === 2) && (inc === 1)) {
return coverage.overlaps.push( { start: point[1], end: null });
# If the ref count reaches 1 by falling, end the last OVERLAP
else if ref_count == 1 && inc == -1
lastOver = _.last( coverage.overlaps )
if lastOver
lastOver.end = point[1]
lastOver.duration = lastOver.end.diff( lastOver.start, 'days' )
if lastOver.duration == 0
coverage.overlaps.pop()
// If the ref count reaches 1 by falling, end the last OVERLAP
} else if ((ref_count === 1) && (inc === -1)) {
const lastOver = _.last( coverage.overlaps );
if (lastOver) {
lastOver.end = point[1];
lastOver.duration = lastOver.end.diff( lastOver.start, 'days' );
if (lastOver.duration === 0) {
return coverage.overlaps.pop();
}
}
}
});
# It's possible that the last gap/overlap didn't have an explicit .end
# date.If so, set the end date to the present date and compute the
# duration normally.
if coverage.overlaps.length
o = _.last( coverage.overlaps )
if o && !o.end
o.end = moment()
o.duration = o.end.diff( o.start, 'days' )
// It's possible that the last gap/overlap didn't have an explicit .end
// date.If so, set the end date to the present date and compute the
// duration normally.
if (coverage.overlaps.length) {
const o = _.last( coverage.overlaps );
if (o && !o.end) {
o.end = moment();
o.duration = o.end.diff( o.start, 'days' );
}
}
if coverage.gaps.length
g = _.last( coverage.gaps )
if g && !g.end
g.end = moment()
g.duration = g.end.diff( g.start, 'days' )
if (coverage.gaps.length) {
const g = _.last( coverage.gaps );
if (g && !g.end) {
g.end = moment();
g.duration = g.end.diff( g.start, 'days' );
}
}
# Package data for return to the client
tdur = rez.duration('days')
dur =
total: tdur
work: tdur - total_gap_days
// Package data for return to the client
const tdur = rez.duration('days');
const dur = {
total: tdur,
work: tdur - 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.duration = dur
coverage
coverage.pct = (dur.total > 0) && (dur.work > 0) ? ((((dur.total - dur.gaps) / dur.total) * 100)).toFixed(1) + '%' : '???';
coverage.duration = dur;
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.
@license MIT. See LICENSE.md for details.
@module inspectors/keyword-inspector
###
*/
_ = require('underscore')
FluentDate = require('../core/fluent-date')
const _ = require('underscore');
const FluentDate = require('../core/fluent-date');
###*
/**
Analyze the resume's use of keywords.
TODO: BUG: Keyword search regex is inaccurate, especially for one or two
letter keywords like "C" or "CLI".
@class keywordInspector
###
keywordInspector = module.exports =
*/
const keywordInspector = (module.exports = {
###* A unique name for this inspector. ###
moniker: 'keyword-inspector'
/** A unique name for this inspector. */
moniker: 'keyword-inspector',
###*
/**
Run the Keyword Inspector on a resume.
@method run
@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
# example, if the keyword is "C++", yield "C\+\+".
# http://stackoverflow.com/a/2593661/4942583
regex_quote = (str) -> (str + '').replace(/[.?*+^$[\]\\(){}|-]/ig, "\\$&")
// "Quote" or safely escape a keyword so it can be used as a regex. For
// example, if the keyword is "C++", yield "C\+\+".
// http://stackoverflow.com/a/2593661/4942583
const regex_quote = str => (str + '').replace(/[.?*+^$[\]\\(){}|-]/ig, "\\$&");
# Create a searchable plain-text digest of the resume
# TODO: BUG: Don't search within keywords for other keywords. Job A
# declares the "foo" keyword. Job B declares the "foo & bar" keyword. Job
# B's mention of "foobar" should not count as a mention of "foo".
# To achieve this, remove keywords from the search digest and treat them
# separately.
searchable = ''
rez.transformStrings ['imp', 'computed', 'safe'], ( key, val ) ->
searchable += ' ' + val
// Create a searchable plain-text digest of the resume
// TODO: BUG: Don't search within keywords for other keywords. Job A
// declares the "foo" keyword. Job B declares the "foo & bar" keyword. Job
// B's mention of "foobar" should not count as a mention of "foo".
// To achieve this, remove keywords from the search digest and treat them
// separately.
let searchable = '';
rez.transformStrings(['imp', 'computed', 'safe'], ( key, val ) => searchable += ` ${val}`);
# Assemble a regex skeleton we can use to test for keywords with a bit
# more
prefix = '(?:' + ['^', '\\s+', '[\\.,]+'].join('|') + ')'
suffix = '(?:' + ['$', '\\s+', '[\\.,]+'].join('|') + ')'
// Assemble a regex skeleton we can use to test for keywords with a bit
// more
const prefix = `(?:${['^', '\\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
#
# var regex = new RegExp( '\\b' + regex_quote( kw )/* + '\\b'*/, 'ig');
#
# 2. Searching for the raw keyword is inaccurate ("C" will match any
# word containing a 'c'!).
#
# var regex = new RegExp( regex_quote( kw ), 'ig');
#
# 3. Instead, use a custom regex with special delimeters.
// 1. Using word boundary or other regex class is inaccurate
//
// var regex = new RegExp( '\\b' + regex_quote( kw )/* + '\\b'*/, 'ig');
//
// 2. Searching for the raw keyword is inaccurate ("C" will match any
// word containing a 'c'!).
//
// var regex = new RegExp( regex_quote( kw ), 'ig');
//
// 3. Instead, use a custom regex with special delimeters.
regex_str = prefix + regex_quote( kw ) + suffix
regex = new RegExp( regex_str, 'ig')
myArray = null
count = 0
while (myArray = regex.exec( searchable )) != null
count++
name: kw
count: count
const regex_str = prefix + regex_quote( kw ) + suffix;
const regex = new RegExp( regex_str, 'ig');
let myArray = null;
let count = 0;
while ((myArray = regex.exec( searchable )) !== null) {
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.
@license MIT. See LICENSE.md for details.
@module inspectors/totals-inspector
###
*/
_ = require 'underscore'
FluentDate = require '../core/fluent-date'
const _ = require('underscore');
const FluentDate = require('../core/fluent-date');
###*
/**
Retrieve sectional overview and summary information.
@class totalsInspector
###
totalsInspector = module.exports =
*/
const totalsInspector = (module.exports = {
moniker: 'totals-inspector'
moniker: 'totals-inspector',
###*
/**
Run the Totals Inspector on a resume.
@method run
@return An object containing summary information for each section on the
resume.
###
run: ( rez ) ->
*/
run( rez ) {
sectionTotals = { }
_.each rez, (val, key) ->
if _.isArray( val ) && !_.isString(val)
sectionTotals[ key ] = val.length
else if val.history && _.isArray( val.history )
sectionTotals[ key ] = val.history.length;
else if val.sets && _.isArray( val.sets )
sectionTotals[ key ] = val.sets.length;
const sectionTotals = { };
_.each(rez, function(val, key) {
if (_.isArray( val ) && !_.isString(val)) {
return sectionTotals[ key ] = val.length;
} else if (val.history && _.isArray( val.history )) {
return sectionTotals[ key ] = val.history.length;
} else if (val.sets && _.isArray( val.sets )) {
return sectionTotals[ key ] = val.sets.length;
}
});
totals: sectionTotals,
numSections: Object.keys( sectionTotals ).length
return {
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.
@license MIT. See LICENSE.md for details.
@module renderers/handlebars-generator
###
*/
_ = require 'underscore'
HANDLEBARS = require 'handlebars'
FS = require 'fs'
registerHelpers = require '../helpers/handlebars-helpers'
PATH = require 'path'
parsePath = require 'parse-filepath'
READFILES = require 'recursive-readdir-sync'
HMSTATUS = require '../core/status-codes'
SLASH = require 'slash'
const _ = require('underscore');
const HANDLEBARS = require('handlebars');
const FS = require('fs');
const registerHelpers = require('../helpers/handlebars-helpers');
const PATH = require('path');
const parsePath = require('parse-filepath');
const READFILES = require('recursive-readdir-sync');
const HMSTATUS = require('../core/status-codes');
const SLASH = require('slash');
###*
/**
Perform template-based resume generation using Handlebars.js.
@class HandlebarsGenerator
###
HandlebarsGenerator = module.exports =
*/
const HandlebarsGenerator = (module.exports = {
generateSimple: ( data, tpl ) ->
generateSimple( data, tpl ) {
try
# Compile and run the Handlebars template.
template = HANDLEBARS.compile tpl,
strict: false
assumeObjects: false
let template;
try {
// Compile and run the Handlebars template.
template = HANDLEBARS.compile(tpl, {
strict: false,
assumeObjects: false,
noEscape: data.opts.noescape
return template data
catch err
throw
}
);
return template(data);
} catch (err) {
throw{
fluenterror:
HMSTATUS[ if template then 'invokeTemplate' else 'compileTemplate' ]
HMSTATUS[ template ? 'invokeTemplate' : 'compileTemplate' ],
inner: err
};
}
},
generate: ( json, jst, format, curFmt, opts, theme ) ->
generate( json, jst, format, curFmt, opts, theme ) {
# Preprocess text
encData = json
if format == 'html' || format == 'pdf'
encData = json.markdownify()
if( format == 'doc' )
encData = json.xmlify()
// Preprocess text
let encData = json;
if ((format === 'html') || (format === 'pdf')) {
encData = json.markdownify();
}
if( format === 'doc' ) {
encData = json.xmlify();
}
# Set up partials and helpers
registerPartials format, theme
registerHelpers theme, encData, opts
// Set up partials and helpers
registerPartials(format, theme);
registerHelpers(theme, encData, opts);
# Set up the context
ctx =
r: encData
RAW: json
filt: opts.filters
format: format
opts: opts
engine: @
results: curFmt.files
// Set up the context
const ctx = {
r: encData,
RAW: json,
filt: opts.filters,
format,
opts,
engine: this,
results: curFmt.files,
headFragment: opts.headFragment || ''
};
# Render the template
return this.generateSimple ctx, jst
// Render the template
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
partialsFolder = PATH.join(
// Locate the global partials folder
const partialsFolder = PATH.join(
parsePath( require.resolve('fresh-themes') ).dirname,
'/partials/',
if format == 'pdf' then 'html' else format
)
format === 'pdf' ? 'html' : format
);
# Register global partials in the /partials/[format] folder
# TODO: Only do this once per HMR invocation.
_.each READFILES( partialsFolder, (error)->{ }), ( el ) ->
pathInfo = parsePath el
name = SLASH PATH.relative( partialsFolder, el ).replace(/\.(?:html|xml|hbs|md|txt)$/i, '')
tplData = FS.readFileSync el, 'utf8'
compiledTemplate = HANDLEBARS.compile tplData
HANDLEBARS.registerPartial name, compiledTemplate
theme.partialsInitialized = true
// Register global partials in the /partials/[format] folder
// TODO: Only do this once per HMR invocation.
_.each(READFILES( partialsFolder, error=> ({ })), function( el ) {
const pathInfo = parsePath(el);
const name = SLASH(PATH.relative( partialsFolder, el ).replace(/\.(?:html|xml|hbs|md|txt)$/i, ''));
const tplData = FS.readFileSync(el, 'utf8');
const compiledTemplate = HANDLEBARS.compile(tplData);
HANDLEBARS.registerPartial(name, compiledTemplate);
return theme.partialsInitialized = true;
});
}
# Register theme-specific partials
_.each theme.partials, ( el ) ->
tplData = FS.readFileSync el.path, 'utf8'
compiledTemplate = HANDLEBARS.compile tplData
HANDLEBARS.registerPartial el.name, compiledTemplate
// Register theme-specific partials
return _.each(theme.partials, function( el ) {
const tplData = FS.readFileSync(el.path, 'utf8');
const compiledTemplate = HANDLEBARS.compile(tplData);
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.
@license MIT. See LICENSE.md for details.
@module renderers/jrs-generator
###
*/
_ = require('underscore')
HANDLEBARS = require('handlebars')
FS = require('fs')
registerHelpers = require('../helpers/handlebars-helpers')
PATH = require('path')
parsePath = require('parse-filepath')
READFILES = require('recursive-readdir-sync')
SLASH = require('slash')
MD = require('marked')
const _ = require('underscore');
const HANDLEBARS = require('handlebars');
const FS = require('fs');
const registerHelpers = require('../helpers/handlebars-helpers');
const PATH = require('path');
const parsePath = require('parse-filepath');
const READFILES = require('recursive-readdir-sync');
const SLASH = require('slash');
const MD = require('marked');
###*
/**
Perform template-based resume generation for JSON Resume themes.
@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.)
turnoff = ['log', 'error', 'dir'];
org = turnoff.map (c) ->
ret = console[c]
console[c] = () ->
ret
// Disable JRS theme chatter (console.log, console.error, etc.)
const turnoff = ['log', 'error', 'dir'];
const org = turnoff.map(function(c) {
const ret = console[c];
console[c] = function() {};
return ret;
});
# Freeze and render
rezHtml = theme.render json.harden()
// Freeze and render
let rezHtml = theme.render(json.harden());
# Turn logging back on
turnoff.forEach (c, idx) -> console[c] = org[idx]
// Turn logging back on
turnoff.forEach((c, idx) => console[c] = org[idx]);
# Unfreeze and apply Markdown
rezHtml = rezHtml.replace /@@@@~[\s\S]*?~@@@@/g, (val) ->
MDIN( val.replace( /~@@@@/g,'' ).replace( /@@@@~/g,'' ) )
// Unfreeze and apply Markdown
return rezHtml = rezHtml.replace(/@@@@~[\s\S]*?~@@@@/g, val => 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, '')
;

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.
@license MIT. See LICENSE.md for details.
@module underscore-generator.js
###
*/
_ = require 'underscore'
registerHelpers = require '../helpers/underscore-helpers'
require '../utils/string'
escapeLaTeX = require 'escape-latex'
const _ = require('underscore');
const registerHelpers = require('../helpers/underscore-helpers');
require('../utils/string');
const escapeLaTeX = require('escape-latex');
###*
/**
Perform template-based resume generation using Underscore.js.
@class UnderscoreGenerator
###
UnderscoreGenerator = module.exports =
*/
const UnderscoreGenerator = (module.exports = {
generateSimple: ( data, tpl ) ->
try
# Compile and run the Handlebars template.
t = _.template tpl
t data
catch err
#console.dir _error
HMS = require '../core/status-codes'
throw
fluenterror: HMS[if t then 'invokeTemplate' else 'compileTemplate']
generateSimple( data, tpl ) {
let t;
try {
// Compile and run the Handlebars template.
t = _.template(tpl);
return t(data);
} catch (err) {
//console.dir _error
const HMS = require('../core/status-codes');
throw{
fluenterror: HMS[t ? 'invokeTemplate' : 'compileTemplate'],
inner: err
};
}
},
generate: ( json, jst, format, cssInfo, opts, theme ) ->
generate( json, jst, format, cssInfo, opts, theme ) {
# Tweak underscore's default template delimeters
delims = (opts.themeObj && opts.themeObj.delimeters) || opts.template;
if opts.themeObj && opts.themeObj.delimeters
delims = _.mapObject delims, (val,key) -> new RegExp val, "ig"
// Tweak underscore's default template delimeters
let delims = (opts.themeObj && opts.themeObj.delimeters) || opts.template;
if (opts.themeObj && opts.themeObj.delimeters) {
delims = _.mapObject(delims, (val,key) => new RegExp(val, "ig"));
}
_.templateSettings = delims;
# Massage resume strings / text
r = null
switch format
when 'html' then r = json.markdownify()
when 'pdf' then r = json.markdownify()
when 'png' then r = json.markdownify()
when 'latex'
traverse = require 'traverse'
r = traverse(json).map (x) ->
if @isLeaf && String.is @node
return escapeLaTeX @node
@node
else r = json
// Massage resume strings / text
let r = null;
switch (format) {
case 'html': r = json.markdownify(); break;
case 'pdf': r = json.markdownify(); break;
case 'png': r = json.markdownify(); break;
case 'latex':
var traverse = require('traverse');
r = traverse(json).map(function(x) {
if (this.isLeaf && String.is(this.node)) {
return escapeLaTeX(this.node);
}
return this.node;
});
break;
default: r = json;
}
# Set up the context
ctx =
r: r
filt: opts.filters
XML: require 'xml-escape'
RAW: json
cssInfo: cssInfo
#engine: @
headFragment: opts.headFragment || ''
opts: opts
// Set up the context
const ctx = {
r,
filt: opts.filters,
XML: require('xml-escape'),
RAW: json,
cssInfo,
//engine: @
headFragment: opts.headFragment || '',
opts
};
# Link to our helpers
registerHelpers theme, opts, cssInfo, ctx, @
// Link to our helpers
registerHelpers(theme, opts, cssInfo, ctx, this);
# Generate!
@generateSimple ctx, jst
// Generate!
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.
@module file-contains.js
###
*/
module.exports = ( file, needle ) ->
require('fs').readFileSync(file,'utf-8').indexOf( needle ) > -1
module.exports = ( file, needle ) => 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.
@module file-contains.js
###
*/
# Set up a regex that matches all of the following:
#
# - FRESH
# - JRS
# - FRESCA
# - FRESH@1.0.0
# - FRESH@1.0
# - FRESH@1
# - JRS@0.16.0
# - JRS@0.16
# - JRS@0
#
# Don't use a SEMVER regex (eg, NPM's semver-regex) because a) we want to
# support partial semvers like "0" or "1.2" and b) we'll expand this later to
# support fully scoped FRESH versions.
// Set up a regex that matches all of the following:
//
// - FRESH
// - JRS
// - FRESCA
// - FRESH@1.0.0
// - FRESH@1.0
// - FRESH@1
// - JRS@0.16.0
// - JRS@0.16
// - JRS@0
//
// Don't use a SEMVER regex (eg, NPM's semver-regex) because a) we want to
// support partial semvers like "0" or "1.2" and b) we'll expand this later to
// support fully scoped FRESH versions.
module.exports = () ->
RegExp '^(FRESH|FRESCA|JRS)(?:@(\\d+(?:\\.\\d+)?(?:\\.\\d+)?))?$'
module.exports = () => 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.
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
@module utils/html-to-wpml
###
*/
XML = require 'xml-escape'
_ = require 'underscore'
HTML5Tokenizer = require 'simple-html-tokenizer'
const XML = require('xml-escape');
const _ = require('underscore');
const HTML5Tokenizer = require('simple-html-tokenizer');
module.exports = ( html ) ->
module.exports = function( html ) {
# Tokenize the HTML stream.
tokens = HTML5Tokenizer.tokenize( html )
final = is_bold = is_italic = is_link = link_url = ''
// Tokenize the HTML stream.
let 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
# equivalent WordProcessingML that can be dumped into a <w:p> or other
# text container element.
_.each tokens, ( tok ) ->
// Process <em>, <strong>, and <a> elements in the HTML stream, producing
// equivalent WordProcessingML that can be dumped into a <w:p> or other
// text container element.
_.each(tokens, function( tok ) {
switch tok.type
switch (tok.type) {
when 'StartTag'
switch tok.tagName
when 'p' then final += '<w:p>'
when 'strong' then is_bold = true
when 'em' then is_italic = true
when 'a'
case 'StartTag':
switch (tok.tagName) {
case 'p': return final += '<w:p>';
case 'strong': return is_bold = true;
case 'em': return is_italic = true;
case 'a':
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'
switch tok.tagName
when 'p' then final += '</w:p>'
when 'strong' then is_bold = false
when 'em' then is_italic = false
when 'a' then is_link = false
case 'EndTag':
switch (tok.tagName) {
case 'p': return final += '</w:p>';
case 'strong': return is_bold = false;
case 'em': return is_italic = false;
case 'a': return is_link = false;
}
break;
when 'Chars'
if( tok.chars.trim().length )
style = if is_bold then '<w:b/>' else ''
style += if is_italic then '<w:i/>' else ''
style += if is_link then '<w:rStyle w:val="Hyperlink"/>' else ''
final +=
(if is_link then ('<w:hlink w:dest="' + link_url + '">') else '') +
case 'Chars':
if( tok.chars.trim().length ) {
let style = is_bold ? '<w:b/>' : '';
style += is_italic ? '<w:i/>' : '';
style += is_link ? '<w:rStyle w:val="Hyperlink"/>' : '';
return final +=
(is_link ? (`<w:hlink w:dest="${link_url}">`) : '') +
'<w:r><w:rPr>' + style + '</w:rPr><w:t>' + XML(tok.chars) +
'</w:t></w:r>' + (if is_link then '</w:hlink>' else '')
final
'</w:t></w:r>' + (is_link ? '</w:hlink>' : '');
}
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.
@license MIT. See LICENSE.md for details.
@module utils/md2chalk
###
*/
MD = require 'marked'
CHALK = require 'chalk'
LO = require 'lodash'
const MD = require('marked');
const CHALK = require('chalk');
const LO = require('lodash');
module.exports = ( v, style, boldStyle ) ->
boldStyle = boldStyle || 'bold'
temp = v.replace(/\*\*(.*?)\*\*/g, LO.get( CHALK, boldStyle )('$1'))
if style then LO.get( CHALK, style )(temp) else temp
module.exports = function( v, style, boldStyle ) {
boldStyle = boldStyle || 'bold';
const temp = v.replace(/\*\*(.*?)\*\*/g, LO.get( CHALK, boldStyle )('$1'));
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
# Converted to CoffeeScript by hacksalot
/*
* decaffeinate suggestions:
* 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";
page = require('webpage').create()
system = require('system')
address = output = size = null
"use strict";let output, size;
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]'
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
if ((system.args.length < 3) || (system.args.length > 5)) {
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]
output = system.args[2]
page.viewportSize = width: 600, height: 600
} else {
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 =
if size.length == 2 then width: size[0], height: size[1], margin: '0px'
else format: system.args[3], orientation: 'portrait', margin: '1cm'
size.length === 2 ? {width: size[0], height: size[1], margin: '0px'}
: {format: system.args[3], orientation: 'portrait', margin: '1cm'};
else if system.args.length > 3 && system.args[3].substr(-2) == "px"
size = system.args[3].split '*'
if size.length == 2
pageWidth = parseInt size[0], 10
pageHeight = parseInt size[1], 10
page.viewportSize = width: pageWidth, height: pageHeight
page.clipRect = top: 0, left: 0, width: pageWidth, height: pageHeight
else
console.log "size:", system.args[3]
pageWidth = parseInt system.args[3], 10
pageHeight = parseInt pageWidth * 3/4, 10 # it's as good an assumption as any
console.log "pageHeight:", pageHeight
page.viewportSize = width: pageWidth, height: pageHeight
} else if ((system.args.length > 3) && (system.args[3].substr(-2) === "px")) {
let pageHeight, pageWidth;
size = system.args[3].split('*');
if (size.length === 2) {
pageWidth = parseInt(size[0], 10);
pageHeight = parseInt(size[1], 10);
page.viewportSize = {width: pageWidth, height: pageHeight};
page.clipRect = {top: 0, left: 0, width: pageWidth, height: pageHeight};
} else {
console.log("size:", system.args[3]);
pageWidth = parseInt(system.args[3], 10);
pageHeight = parseInt((pageWidth * 3)/4, 10); // it's as good an assumption as any
console.log("pageHeight:", pageHeight);
page.viewportSize = {width: pageWidth, height: pageHeight};
}
}
if system.args.length > 4
page.zoomFactor = system.args[4]
if (system.args.length > 4) {
page.zoomFactor = system.args[4];
}
page.open address, (status) ->
if status != 'success'
console.log 'Unable to load the address!'
phantom.exit(1)
return
else
window.setTimeout () ->
page.render(output)
phantom.exit()
return
, 200
page.open(address, function(status) {
if (status !== 'success') {
console.log('Unable to load the address!');
phantom.exit(1);
return;
} else {
return window.setTimeout(function() {
page.render(output);
phantom.exit();
}
, 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.
@module utils/resume-detector
@license MIT. See LICENSE.md for details.
###
*/
module.exports = ( rez ) ->
if rez.meta && rez.meta.format #&& rez.meta.format.substr(0, 5).toUpperCase() == 'FRESH'
'fresh'
else if rez.basics
'jrs'
else
'unk'
module.exports = function( rez ) {
if (rez.meta && rez.meta.format) { //&& rez.meta.format.substr(0, 5).toUpperCase() == 'FRESH'
return 'fresh';
} else if (rez.basics) {
return 'jrs';
} else {
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
@returns an object with the following structure:
{
@ -8,40 +13,47 @@ module.exports =
ignoreList: an array of ignored nodes that were removed
privateList: an array of private nodes that were removed
}
###
scrubResume: (rep, opts) ->
traverse = require 'traverse'
ignoreList = []
privateList = []
includePrivates = opts && opts.private
*/
scrubResume(rep, opts) {
const traverse = require('traverse');
const ignoreList = [];
const privateList = [];
const includePrivates = opts && opts.private;
scrubbed = traverse( rep ).map () -> # [^1]
if !@isLeaf
if @node.ignore == true || @node.ignore == 'true'
ignoreList.push @node
@delete()
else if (@node.private == true || @node.private == 'true') && !includePrivates
privateList.push @node
@delete()
if _.isArray(@node) # [^2]
@after () ->
@update _.compact this.node
return
return
const scrubbed = traverse( rep ).map(function() { // [^1]
if (!this.isLeaf) {
if ((this.node.ignore === true) || (this.node.ignore === 'true')) {
ignoreList.push(this.node);
this.delete();
} else if (((this.node.private === true) || (this.node.private === 'true')) && !includePrivates) {
privateList.push(this.node);
this.delete();
}
}
if (_.isArray(this.node)) { // [^2]
this.after(function() {
this.update(_.compact(this.node));
});
}
});
scrubbed: scrubbed
ingoreList: ignoreList
privateList: privateList
return {
scrubbed,
ingoreList: ignoreList,
privateList
};
}
};
# [^1]: As of v0.6.6, the NPM traverse library has a quirk when attempting
# to remove array elements directly using traverse's `this.remove`. See:
#
# https://github.com/substack/js-traverse/issues/48
#
# [^2]: The workaround is to use traverse's 'this.delete' to nullify the value
# first, followed by removal with something like _.compact.
#
# https://github.com/substack/js-traverse/issues/48#issuecomment-142607200
#
// [^1]: As of v0.6.6, the NPM traverse library has a quirk when attempting
// to remove array elements directly using traverse's `this.remove`. See:
//
// https://github.com/substack/js-traverse/issues/48
//
// [^2]: The workaround is to use traverse's 'this.delete' to nullify the value
// first, followed by removal with something like _.compact.
//
// https://github.com/substack/js-traverse/issues/48#issuecomment-142607200
//

View File

@ -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.
@module utils/safe-json-loader
@license MIT. See LICENSE.md for details.
###
*/
FS = require('fs')
SyntaxErrorEx = require('./syntax-error-ex')
const FS = require('fs');
const SyntaxErrorEx = require('./syntax-error-ex');
module.exports = ( file ) ->
ret = { }
try
module.exports = function( file ) {
const ret = { };
try {
ret.raw = FS.readFileSync( file, 'utf8' );
ret.json = JSON.parse( ret.raw );
catch err
# If we get here, either FS.readFileSync or JSON.parse failed.
# We'll return HMSTATUS.readError or HMSTATUS.parseError.
retRaw = ret.raw && ret.raw.trim()
ret.ex =
op: if retRaw then 'parse' else 'read'
} catch (err) {
// If we get here, either FS.readFileSync or JSON.parse failed.
// We'll return HMSTATUS.readError or HMSTATUS.parseError.
const retRaw = ret.raw && ret.raw.trim();
ret.ex = {
op: retRaw ? 'parse' : 'read',
inner:
if SyntaxErrorEx.is( err )
then (new SyntaxErrorEx( err, retRaw ))
else err
file: file
ret
SyntaxErrorEx.is( err )
? (new SyntaxErrorEx( err, retRaw ))
: err,
file
};
}
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.
@module utils/safe-spawn
@license MIT. See LICENSE.md for details.
###
*/
###* Safely spawn a process synchronously or asynchronously without throwing an
exception ###
module.exports = ( cmd, args, isSync, callback, param ) ->
/** Safely spawn a process synchronously or asynchronously without throwing an
exception */
module.exports = function( cmd, args, isSync, callback, param ) {
try
# .spawnSync not available on earlier Node.js, so default to .spawn
spawn = require('child_process')[ if isSync then 'spawnSync' else 'spawn']
info = spawn cmd, args
try {
// .spawnSync not available on earlier Node.js, so default to .spawn
const spawn = require('child_process')[ isSync ? 'spawnSync' : 'spawn'];
const info = spawn(cmd, args);
# Check for error depending on whether we're sync or async TODO: Promises
if !isSync
info.on 'error', (err) ->
callback?(err, param)
return
return
else
if info.error
callback?(info.error, param)
return cmd: cmd, inner: info.error
// Check for error depending on whether we're sync or async TODO: Promises
if (!isSync) {
info.on('error', function(err) {
if (typeof callback === 'function') {
callback(err, param);
}
});
return;
} else {
if (info.error) {
if (typeof callback === 'function') {
callback(info.error, param);
}
return {cmd, inner: info.error};
}
}
catch ex
callback?(ex, param)
ex
} catch (ex) {
if (typeof callback === 'function') {
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.
@module utils/string-transformer
@license MIT. See LICENSE.md for details.
###
*/
_ = require 'underscore'
moment = require 'moment'
const _ = require('underscore');
const moment = require('moment');
###*
/**
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).
###
*/
module.exports = ( ret, filt, transformer ) ->
module.exports = function( ret, filt, transformer ) {
that = @
const that = this;
# TODO: refactor recursion
transformStringsInObject = ( obj, filters ) ->
return if !obj
return if moment.isMoment obj
// TODO: refactor recursion
var transformStringsInObject = function( obj, filters ) {
if (!obj) { return; }
if (moment.isMoment(obj)) { return; }
if _.isArray( obj )
obj.forEach (elem, idx, ar) ->
if typeof elem == 'string' || elem instanceof String
ar[idx] = transformer( null, elem )
else if _.isObject(elem)
transformStringsInObject( elem, filters )
else if _.isObject( obj )
Object.keys( obj ).forEach (k) ->
if filters.length && _.contains(filters, k)
return
sub = obj[k]
if typeof sub == 'string' || sub instanceof String
obj[k] = transformer( k, sub )
else if _.isObject( sub )
transformStringsInObject( sub, filters )
if (_.isArray( obj )) {
return obj.forEach(function(elem, idx, ar) {
if ((typeof elem === 'string') || elem instanceof String) {
return ar[idx] = transformer( null, elem );
} else if (_.isObject(elem)) {
return transformStringsInObject( elem, filters );
}
});
} else if (_.isObject( obj )) {
return Object.keys( obj ).forEach(function(k) {
if (filters.length && _.contains(filters, k)) {
return;
}
const sub = obj[k];
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) ->
if !filt || !filt.length || !_.contains(filt, member)
transformStringsInObject( ret[ member ], filt || [] )
ret
Object.keys( ret ).forEach(function(member) {
if (!filt || !filt.length || !_.contains(filt, member)) {
return transformStringsInObject( ret[ member ], filt || [] );
}
});
return ret;
};

View File

@ -1,15 +1,15 @@
###*
/**
Definitions of string utility functions.
@module utils/string
###
*/
###*
/**
Determine if the string is null, empty, or whitespace.
See: http://stackoverflow.com/a/32800728/4942583
@method isNullOrWhitespace
###
*/
String.isNullOrWhitespace = ( input ) -> !input || !input.trim()
String.prototype.endsWith = (suffix) -> @indexOf(suffix, this.length - suffix.length) != -1
String.is = ( val ) -> typeof val == 'string' || val instanceof String
String.isNullOrWhitespace = input => !input || !input.trim();
String.prototype.endsWith = function(suffix) { return this.indexOf(suffix, this.length - suffix.length) !== -1; };
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.
@module utils/syntax-error-ex
@license MIT. See LICENSE.md for details.
###
*/
###*
/**
Represents a SyntaxError exception with line and column info.
Collect syntax error information from the provided exception object. The
JavaScript `SyntaxError` exception isn't interpreted uniformly across environ-
ments, so we reparse on error to grab the line and column.
See: http://stackoverflow.com/q/13323356
@class SyntaxErrorEx
###
*/
class SyntaxErrorEx
constructor: ( ex, rawData ) ->
lineNum = null
colNum = null
JSONLint = require 'json-lint'
lint = JSONLint rawData, { comments: false }
[@line, @col] = [lint.line, lint.character] if lint.error
if !lint.error
JSONLint = require 'jsonlint'
try
JSONLint.parse rawData
catch err
@line = (/on line (\d+)/.exec err)[1]
class SyntaxErrorEx {
constructor( ex, rawData ) {
const lineNum = null;
const colNum = null;
let JSONLint = require('json-lint');
const lint = JSONLint(rawData, { comments: false });
if (lint.error) { [this.line, this.col] = Array.from([lint.line, lint.character]); }
if (!lint.error) {
JSONLint = require('jsonlint');
try {
JSONLint.parse(rawData);
} catch (err) {
this.line = (/on line (\d+)/.exec(err))[1];
}
}
}
}
# Return true if the supplied parameter is a JavaScript SyntaxError
SyntaxErrorEx.is = ( ex ) -> ex instanceof SyntaxError
// Return true if the supplied parameter is a JavaScript SyntaxError
SyntaxErrorEx.is = ex => ex instanceof SyntaxError;
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.
@module verbs/analyze
@license MIT. See LICENSE.md for details.
###
*/
MKDIRP = require('mkdirp')
PATH = require('path')
HMEVENT = require('../core/event-codes')
HMSTATUS = require('../core/status-codes')
_ = require('underscore')
ResumeFactory = require('../core/resume-factory')
Verb = require('../verbs/verb')
chalk = require('chalk')
let AnalyzeVerb;
const MKDIRP = require('mkdirp');
const PATH = require('path');
const HMEVENT = require('../core/event-codes');
const HMSTATUS = require('../core/status-codes');
const _ = require('underscore');
const ResumeFactory = require('../core/resume-factory');
const Verb = require('../verbs/verb');
const chalk = require('chalk');
###* An invokable resume analysis command. ###
module.exports = class AnalyzeVerb extends Verb
/** An invokable resume analysis command. */
module.exports = (AnalyzeVerb = class AnalyzeVerb extends Verb {
constructor: -> super 'analyze', _analyze
constructor() { super('analyze', _analyze); }
});
###* Private workhorse for the 'analyze' command. ###
_analyze = ( sources, dst, opts ) ->
/** Private workhorse for the 'analyze' command. */
var _analyze = function( sources, dst, opts ) {
if !sources || !sources.length
@err HMSTATUS.resumeNotFound, { quit: true }
return null
if (!sources || !sources.length) {
this.err(HMSTATUS.resumeNotFound, { quit: true });
return null;
}
nlzrs = _loadInspectors()
results = _.map sources, (src) ->
r = ResumeFactory.loadOne src, format: 'FRESH', objectify: true, inner: {
private: opts.private is true
}, @
return { } if opts.assert and @hasError()
const nlzrs = _loadInspectors();
const results = _.map(sources, function(src) {
const r = ResumeFactory.loadOne(src, { format: 'FRESH', objectify: true, inner: {
private: opts.private === true
}
}, this);
if (opts.assert && this.hasError()) { return { }; }
if r.fluenterror
r.quit = opts.assert
@err r.fluenterror, r
r
else
_analyzeOne.call @, r, nlzrs, opts
, @
if (r.fluenterror) {
r.quit = opts.assert;
this.err(r.fluenterror, r);
return r;
} else {
return _analyzeOne.call(this, r, nlzrs, opts);
}
}
, this);
if @hasError() and !opts.assert
@reject @errorCode
else if !@hasError()
@resolve results
results
if (this.hasError() && !opts.assert) {
this.reject(this.errorCode);
} else if (!this.hasError()) {
this.resolve(results);
}
return results;
};
###* Analyze a single resume. ###
_analyzeOne = ( resumeObject, nlzrs, opts ) ->
rez = resumeObject.rez
safeFormat =
if rez.meta and rez.meta.format and rez.meta.format.startsWith 'FRESH'
then 'FRESH' else 'JRS'
/** Analyze a single resume. */
var _analyzeOne = function( resumeObject, nlzrs, opts ) {
const { rez } = resumeObject;
const safeFormat =
rez.meta && rez.meta.format && rez.meta.format.startsWith('FRESH')
? 'FRESH' : 'JRS';
this.stat( HMEVENT.beforeAnalyze, { fmt: safeFormat, file: resumeObject.file })
info = _.mapObject nlzrs, (val, key) -> val.run rez
this.stat HMEVENT.afterAnalyze, { info: info }
info
this.stat( HMEVENT.beforeAnalyze, { fmt: safeFormat, file: resumeObject.file });
const info = _.mapObject(nlzrs, (val, key) => val.run(rez));
this.stat(HMEVENT.afterAnalyze, { info });
return info;
};
_loadInspectors = ->
totals: require '../inspectors/totals-inspector'
coverage: require '../inspectors/gap-inspector'
keywords: require '../inspectors/keyword-inspector'
var _loadInspectors = () =>
({
totals: require('../inspectors/totals-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.
@module verbs/build
@license MIT. See LICENSE.md for details.
###
*/
_ = require 'underscore'
PATH = require 'path'
FS = require 'fs'
MD = require 'marked'
MKDIRP = require 'mkdirp'
extend = require 'extend'
parsePath = require 'parse-filepath'
RConverter = require 'fresh-jrs-converter'
HMSTATUS = require '../core/status-codes'
HMEVENT = require '../core/event-codes'
RTYPES =
FRESH: require '../core/fresh-resume'
JRS: require '../core/jrs-resume'
_opts = require '../core/default-options'
FRESHTheme = require '../core/fresh-theme'
JRSTheme = require '../core/jrs-theme'
ResumeFactory = require '../core/resume-factory'
_fmts = require '../core/default-formats'
Verb = require '../verbs/verb'
let BuildVerb;
const _ = require('underscore');
const PATH = require('path');
const FS = require('fs');
const MD = require('marked');
const MKDIRP = require('mkdirp');
const extend = require('extend');
const parsePath = require('parse-filepath');
const RConverter = require('fresh-jrs-converter');
const HMSTATUS = require('../core/status-codes');
const HMEVENT = require('../core/event-codes');
const RTYPES = {
FRESH: require('../core/fresh-resume'),
JRS: require('../core/jrs-resume')
};
const _opts = require('../core/default-options');
const FRESHTheme = require('../core/fresh-theme');
const JRSTheme = require('../core/jrs-theme');
const ResumeFactory = require('../core/resume-factory');
const _fmts = require('../core/default-formats');
const Verb = require('../verbs/verb');
_err = null
_log = null
_rezObj = null
build = null
prep = null
single = null
verifyOutputs = null
addFreebieFormats = null
expand = null
verifyTheme = null
loadTheme = null
const _err = null;
const _log = null;
let _rezObj = null;
const build = null;
const prep = null;
const single = null;
const verifyOutputs = null;
const addFreebieFormats = null;
const expand = null;
const verifyTheme = null;
const loadTheme = null;
###* An invokable resume generation command. ###
module.exports = class BuildVerb extends Verb
/** An invokable resume generation command. */
module.exports = (BuildVerb = class BuildVerb extends Verb {
###* Create a new build verb. ###
constructor: () -> super 'build', _build
/** Create a new build verb. */
constructor() { super('build', _build); }
});
###*
/**
Given a source resume in FRESH or JRS format, a destination resume path, and a
theme file, generate 0..N resumes in the desired formats.
@param src Path to the source JSON resume file: "rez/resume.json".
@param dst An array of paths to the target resume file(s).
@param opts Generation options.
###
_build = ( src, dst, opts ) ->
*/
var _build = function( src, dst, opts ) {
if !src || !src.length
@err HMSTATUS.resumeNotFound, quit: true
return null
let err;
if (!src || !src.length) {
this.err(HMSTATUS.resumeNotFound, {quit: true});
return null;
}
_prep.call @, src, dst, opts
_prep.call(this, src, dst, opts);
# Load input resumes as JSON...
sheetObjects = ResumeFactory.load src,
// Load input resumes as JSON...
const sheetObjects = ResumeFactory.load(src, {
format: null, objectify: false, quit: true, inner: {
sort: _opts.sort
sort: _opts.sort,
private: _opts.private
}
, @
}
, this);
# Explicit check for any resume loading errors...
problemSheets = _.filter sheetObjects, (so) -> so.fluenterror
if problemSheets and problemSheets.length
problemSheets[0].quit = true # can't go on
@err problemSheets[0].fluenterror, problemSheets[0]
return null
// Explicit check for any resume loading errors...
const problemSheets = _.filter(sheetObjects, so => so.fluenterror);
if (problemSheets && problemSheets.length) {
problemSheets[0].quit = true; // can't go on
this.err(problemSheets[0].fluenterror, problemSheets[0]);
return null;
}
# Get the collection of raw JSON sheets
sheets = sheetObjects.map (r) -> r.json
// Get the collection of raw JSON sheets
const sheets = sheetObjects.map(r => r.json);
# Load the theme...
theme = null
@stat HMEVENT.beforeTheme, { theme: _opts.theme }
try
tFolder = _verifyTheme.call @, _opts.theme
if tFolder.fluenterror
tFolder.quit = true
@err tFolder.fluenterror, tFolder
return
theme = _opts.themeObj = _loadTheme tFolder
_addFreebieFormats theme
catch err
newEx =
fluenterror: HMSTATUS.themeLoad
inner: err
attempted: _opts.theme
// Load the theme...
let theme = null;
this.stat(HMEVENT.beforeTheme, { theme: _opts.theme });
try {
const tFolder = _verifyTheme.call(this, _opts.theme);
if (tFolder.fluenterror) {
tFolder.quit = true;
this.err(tFolder.fluenterror, tFolder);
return;
}
theme = (_opts.themeObj = _loadTheme(tFolder));
_addFreebieFormats(theme);
} catch (error) {
err = error;
const newEx = {
fluenterror: HMSTATUS.themeLoad,
inner: err,
attempted: _opts.theme,
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...
inv = _verifyOutputs.call @, dst, theme
if inv && inv.length
@err HMSTATUS.invalidFormat, data: inv, theme: theme, quit: true
return null
// Check for invalid outputs...
const inv = _verifyOutputs.call(this, dst, theme);
if (inv && inv.length) {
this.err(HMSTATUS.invalidFormat, {data: inv, theme, quit: true});
return null;
}
## Merge input resumes, yielding a single source resume...
rez = null
if sheets.length > 1
isFRESH = !sheets[0].basics
mixed = _.any sheets, (s) -> return if isFRESH then s.basics else !s.basics
@stat HMEVENT.beforeMerge, { f: _.clone(sheetObjects), mixed: mixed }
if mixed
@err HMSTATUS.mixedMerge
rez = _.reduceRight sheets, ( a, b, idx ) ->
extend( true, b, a )
@stat HMEVENT.afterMerge, { r: rez }
else
//# Merge input resumes, yielding a single source resume...
let rez = null;
if (sheets.length > 1) {
const isFRESH = !sheets[0].basics;
const mixed = _.any(sheets, function(s) { if (isFRESH) { return s.basics; } else { return !s.basics; } });
this.stat(HMEVENT.beforeMerge, { f: _.clone(sheetObjects), mixed });
if (mixed) {
this.err(HMSTATUS.mixedMerge);
}
rez = _.reduceRight(sheets, ( a, b, idx ) => extend( true, b, a ));
this.stat(HMEVENT.afterMerge, { r: rez });
} else {
rez = sheets[0];
}
# Convert the merged source resume to the theme's format, if necessary..
orgFormat = if rez.basics then 'JRS' else 'FRESH';
toFormat = if theme.render then 'JRS' else 'FRESH';
if toFormat != orgFormat
@stat HMEVENT.beforeInlineConvert
rez = RConverter[ 'to' + toFormat ]( rez );
@stat HMEVENT.afterInlineConvert, { file: sheetObjects[0].file, fmt: toFormat }
// Convert the merged source resume to the theme's format, if necessary..
const orgFormat = rez.basics ? 'JRS' : 'FRESH';
const toFormat = theme.render ? 'JRS' : 'FRESH';
if (toFormat !== orgFormat) {
this.stat(HMEVENT.beforeInlineConvert);
rez = RConverter[ `to${toFormat}` ]( rez );
this.stat(HMEVENT.afterInlineConvert, { file: sheetObjects[0].file, fmt: toFormat });
}
# Announce the theme
@stat HMEVENT.applyTheme, r: rez, theme: theme
// Announce the theme
this.stat(HMEVENT.applyTheme, {r: rez, theme});
# Load the resume into a FRESHResume or JRSResume object
_rezObj = new (RTYPES[ toFormat ])().parseJSON( rez, private: _opts.private );
// Load the resume into a FRESHResume or JRSResume object
_rezObj = new (RTYPES[ toFormat ])().parseJSON( rez, {private: _opts.private} );
# Expand output resumes...
targets = _expand dst, theme
// Expand output resumes...
const targets = _expand(dst, theme);
# Run the transformation!
_.each targets, (t) ->
return { } if @hasError() and opts.assert
t.final = _single.call @, t, theme, targets
if t.final?.fluenterror
t.final.quit = opts.assert
@err t.final.fluenterror, t.final
return
, @
// Run the transformation!
_.each(targets, function(t) {
if (this.hasError() && opts.assert) { return { }; }
t.final = _single.call(this, t, theme, targets);
if (t.final != null ? t.final.fluenterror : undefined) {
t.final.quit = opts.assert;
this.err(t.final.fluenterror, t.final);
}
}
, this);
results =
sheet: _rezObj
targets: targets
const results = {
sheet: _rezObj,
targets,
processed: targets
};
if @hasError() and !opts.assert
@reject results
else if !@hasError()
@resolve results
if (this.hasError() && !opts.assert) {
this.reject(results);
} else if (!this.hasError()) {
this.resolve(results);
}
results
return results;
};
###*
/**
Prepare for a BUILD run.
###
_prep = ( src, dst, opts ) ->
# Cherry-pick options //_opts = extend( true, _opts, opts );
*/
var _prep = function( src, dst, opts ) {
// Cherry-pick options //_opts = extend( true, _opts, opts );
_opts.theme = (opts.theme && opts.theme.toLowerCase().trim()) || 'modern';
_opts.prettify = opts.prettify is true
_opts.private = opts.private is true
_opts.noescape = opts.noescape is true
_opts.css = opts.css
_opts.pdf = opts.pdf
_opts.wrap = opts.wrap || 60
_opts.stitles = opts.sectionTitles
_opts.tips = opts.tips
_opts.errHandler = opts.errHandler
_opts.noTips = opts.noTips
_opts.debug = opts.debug
_opts.sort = opts.sort
_opts.wkhtmltopdf = opts.wkhtmltopdf
that = @
_opts.prettify = opts.prettify === true;
_opts.private = opts.private === true;
_opts.noescape = opts.noescape === true;
_opts.css = opts.css;
_opts.pdf = opts.pdf;
_opts.wrap = opts.wrap || 60;
_opts.stitles = opts.sectionTitles;
_opts.tips = opts.tips;
_opts.errHandler = opts.errHandler;
_opts.noTips = opts.noTips;
_opts.debug = opts.debug;
_opts.sort = opts.sort;
_opts.wkhtmltopdf = opts.wkhtmltopdf;
const that = this;
# Set up callbacks for internal generators
_opts.onTransform = (info) ->
that.stat HMEVENT.afterTransform, info; return
_opts.beforeWrite = (info) ->
that.stat HMEVENT.beforeWrite, info; return
_opts.afterWrite = (info) ->
that.stat HMEVENT.afterWrite, info; return
// Set up callbacks for internal generators
_opts.onTransform = function(info) {
that.stat(HMEVENT.afterTransform, info);
};
_opts.beforeWrite = function(info) {
that.stat(HMEVENT.beforeWrite, info);
};
_opts.afterWrite = function(info) {
that.stat(HMEVENT.afterWrite, info);
};
# If two or more files are passed to the GENERATE command and the TO
# keyword is omitted, the last file specifies the output file.
( src.length > 1 && ( !dst || !dst.length ) ) && dst.push( src.pop() )
return
// If two or more files are passed to the GENERATE command and the TO
// keyword is omitted, the last file specifies the output file.
( (src.length > 1) && ( !dst || !dst.length ) ) && dst.push( src.pop() );
};
###*
/**
Generate a single target resume such as "out/rez.html" or "out/rez.doc".
TODO: Refactor.
@param targInfo Information for the target resume.
@param theme A FRESHTheme or JRSTheme object.
###
_single = ( targInfo, theme, finished ) ->
*/
var _single = function( targInfo, theme, finished ) {
ret = null
ex = null
f = targInfo.file
let ret = null;
let ex = null;
const f = targInfo.file;
try
try {
if !targInfo.fmt
return { }
fType = targInfo.fmt.outFormat
fName = PATH.basename f, '.' + fType
theFormat = null
if (!targInfo.fmt) {
return { };
}
const fType = targInfo.fmt.outFormat;
const fName = PATH.basename(f, `.${fType}`);
let theFormat = null;
@stat HMEVENT.beforeGenerate,
fmt: targInfo.fmt.outFormat
file: PATH.relative process.cwd(), f
this.stat(HMEVENT.beforeGenerate, {
fmt: targInfo.fmt.outFormat,
file: PATH.relative(process.cwd(), f)
}
);
_opts.targets = finished
_opts.targets = finished;
# If targInfo.fmt.files exists, this format is backed by a document.
# Fluent/FRESH themes are handled here.
if targInfo.fmt.files && targInfo.fmt.files.length
theFormat = _fmts.filter( (fmt) ->
return fmt.name == targInfo.fmt.outFormat
)[0];
MKDIRP.sync PATH.dirname( f )
ret = theFormat.gen.generate _rezObj, f, _opts
// If targInfo.fmt.files exists, this format is backed by a document.
// Fluent/FRESH themes are handled here.
if (targInfo.fmt.files && targInfo.fmt.files.length) {
theFormat = _fmts.filter( fmt => fmt.name === targInfo.fmt.outFormat)[0];
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
# gets "for free".
else
theFormat = _fmts.filter( (fmt) ->
return fmt.name == targInfo.fmt.outFormat
)[0];
outFolder = PATH.dirname f
MKDIRP.sync outFolder # Ensure dest folder exists;
ret = theFormat.gen.generate _rezObj, f, _opts
// Otherwise this is an ad-hoc format (JSON, YML, or PNG) that every theme
// gets "for free".
} else {
theFormat = _fmts.filter( fmt => fmt.name === targInfo.fmt.outFormat)[0];
const outFolder = PATH.dirname(f);
MKDIRP.sync(outFolder); // Ensure dest folder exists;
ret = theFormat.gen.generate(_rezObj, f, _opts);
}
catch e
ex = e
} catch (e) {
ex = e;
}
this.stat HMEVENT.afterGenerate,
fmt: targInfo.fmt.outFormat
file: PATH.relative process.cwd(), f
this.stat(HMEVENT.afterGenerate, {
fmt: targInfo.fmt.outFormat,
file: PATH.relative(process.cwd(), f),
error: ex
}
);
if ex
if ex.fluenterror
ret = ex
else
ret = fluenterror: HMSTATUS.generateError, inner: ex
ret
if (ex) {
if (ex.fluenterror) {
ret = ex;
} else {
ret = {fluenterror: HMSTATUS.generateError, inner: ex};
}
}
return ret;
};
###* Ensure that user-specified outputs/targets are valid. ###
_verifyOutputs = ( targets, theme ) ->
@stat HMEVENT.verifyOutputs, targets: targets, theme: theme
_.reject targets.map( ( t ) ->
pathInfo = parsePath t
format: pathInfo.extname.substr(1) ),
(t) -> t.format == 'all' || theme.hasFormat( t.format )
/** Ensure that user-specified outputs/targets are valid. */
var _verifyOutputs = function( targets, theme ) {
this.stat(HMEVENT.verifyOutputs, {targets, theme});
return _.reject(targets.map( function( t ) {
const pathInfo = parsePath(t);
return {format: pathInfo.extname.substr(1)}; }),
t => (t.format === 'all') || theme.hasFormat( t.format ));
};
###*
/**
Reinforce the chosen theme with "freebie" formats provided by HackMyResume.
A "freebie" format is an output format such as JSON, YML, or PNG that can be
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
PNG template.
@param theTheme A FRESHTheme or JRSTheme object.
###
_addFreebieFormats = ( theTheme ) ->
# Add freebie formats (JSON, YAML, PNG) every theme gets...
# Add HTML-driven PNG only if the theme has an HTML format.
*/
var _addFreebieFormats = function( theTheme ) {
// Add freebie formats (JSON, YAML, PNG) every theme gets...
// Add HTML-driven PNG only if the theme has an HTML format.
theTheme.formats.json = theTheme.formats.json || {
freebie: true, title: 'json', outFormat: 'json', pre: 'json',
ext: 'json', path: null, data: null
}
};
theTheme.formats.yml = theTheme.formats.yml || {
freebie: true, title: 'yaml', outFormat: 'yml', pre: 'yml',
ext: 'yml', path: null, data: null
}
if theTheme.formats.html && !theTheme.formats.png
};
if (theTheme.formats.html && !theTheme.formats.png) {
theTheme.formats.png = {
freebie: true, title: 'png', outFormat: 'png',
ext: 'yml', path: null, data: null
}
return
};
}
};
###*
/**
Expand output files. For example, "foo.all" should be expanded to
["foo.html", "foo.doc", "foo.pdf", "etc"].
@param dst An array of output files as specified by the user.
@param theTheme A FRESHTheme or JRSTheme object.
###
_expand = ( dst, theTheme ) ->
*/
var _expand = function( dst, theTheme ) {
# Set up the destination collection. It's either the array of files passed
# by the user or 'out/resume.all' if no targets were specified.
destColl = (dst && dst.length && dst) || [PATH.normalize('out/resume.all')];
// 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.
const destColl = (dst && dst.length && dst) || [PATH.normalize('out/resume.all')];
# Assemble an array of expanded target files... (can't use map() here)
targets = [];
destColl.forEach (t) ->
to = PATH.resolve(t)
pa = parsePath(to)
fmat = pa.extname || '.all';
targets.push.apply( targets,
if fmat == '.all'
then Object.keys( theTheme.formats ).map( ( k ) ->
z = theTheme.formats[k]
return { file: to.replace( /all$/g, z.outFormat ), fmt: z }
)
else [{ file: to, fmt: theTheme.getFormat( fmat.slice(1) ) }]
)
targets
// Assemble an array of expanded target files... (can't use map() here)
const targets = [];
destColl.forEach(function(t) {
const to = PATH.resolve(t);
const pa = parsePath(to);
const fmat = pa.extname || '.all';
return targets.push.apply( targets,
fmat === '.all'
? Object.keys( theTheme.formats ).map( function( k ) {
const z = theTheme.formats[k];
return { file: to.replace( /all$/g, z.outFormat ), fmt: z };
})
: [{ file: to, fmt: theTheme.getFormat( fmat.slice(1) ) }]
);
});
return targets;
};
###*
/**
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
# handful of these, but they may change over time, so we need to query
# the official source of truth: the fresh-themes repository, which mounts the
# themes conveniently by name to the module object, and which is embedded
# locally inside the HackMyResume installation.
themesObj = require 'fresh-themes'
if _.has themesObj.themes, themeNameOrPath
// First, see if this is one of the predefined FRESH themes. There are only a
// handful of these, but they may change over time, so we need to query
// the official source of truth: the fresh-themes repository, which mounts the
// themes conveniently by name to the module object, and which is embedded
// locally inside the HackMyResume installation.
let tFolder;
const themesObj = require('fresh-themes');
if (_.has(themesObj.themes, themeNameOrPath)) {
tFolder = PATH.join(
parsePath( require.resolve('fresh-themes') ).dirname,
'/themes/',
themeNameOrPath
)
else
# Otherwsie it's a path to an arbitrary FRESH or JRS theme sitting somewhere
# on the user's system (or, in the future, at a URI).
tFolder = PATH.resolve themeNameOrPath
);
} else {
// Otherwsie it's a path to an arbitrary FRESH or JRS theme sitting somewhere
// on the user's system (or, in the future, at a URI).
tFolder = PATH.resolve(themeNameOrPath);
}
# In either case, make sure the theme folder exists
exists = require('path-exists').sync
if exists tFolder
tFolder
else
fluenterror: HMSTATUS.themeNotFound, data: _opts.theme
// In either case, make sure the theme folder exists
const exists = require('path-exists').sync;
if (exists(tFolder)) {
return tFolder;
} else {
return {fluenterror: HMSTATUS.themeNotFound, data: _opts.theme};
}
};
###*
/**
Load the specified theme, which could be either a FRESH theme or a JSON Resume
theme (or both).
###
_loadTheme = ( tFolder ) ->
*/
var _loadTheme = function( tFolder ) {
themeJsonPath = PATH.join tFolder, 'theme.json' # [^1]
exists = require('path-exists').sync
const themeJsonPath = PATH.join(tFolder, 'theme.json'); // [^1]
const exists = require('path-exists').sync;
# Create a FRESH or JRS theme object
theTheme =
if exists themeJsonPath
then new FRESHTheme().open tFolder
else new JRSTheme().open tFolder
// Create a FRESH or JRS theme object
const theTheme =
exists(themeJsonPath)
? new FRESHTheme().open(tFolder)
: new JRSTheme().open(tFolder);
# Cache the theme object
// Cache the theme object
_opts.themeObj = theTheme;
theTheme
return theTheme;
};
# FOOTNOTES
# ------------------------------------------------------------------------------
# [^1] We don't know ahead of time whether this is a FRESH or JRS theme.
# However, all FRESH themes have a theme.json file, so we'll use that as a
# canary for now, as an interim solution.
#
# Unfortunately, with the exception of FRESH's theme.json, both FRESH and
# JRS themes are free-form and don't have a ton of reliable distinguishing
# marks, which makes a simple task like ad hoc theme detection harder than
# it should be to do cleanly.
#
# Another complicating factor is that it's possible for a theme to be BOTH.
# That is, a single set of theme files can serve as a FRESH theme -and- a
# JRS theme.
#
# TODO: The most robust way to deal with all these issues is with a strong
# theme validator. If a theme structure validates as a particular theme
# type, then for all intents and purposes, it IS a theme of that type.
// FOOTNOTES
// ------------------------------------------------------------------------------
// [^1] We don't know ahead of time whether this is a FRESH or JRS theme.
// However, all FRESH themes have a theme.json file, so we'll use that as a
// canary for now, as an interim solution.
//
// Unfortunately, with the exception of FRESH's theme.json, both FRESH and
// JRS themes are free-form and don't have a ton of reliable distinguishing
// marks, which makes a simple task like ad hoc theme detection harder than
// it should be to do cleanly.
//
// Another complicating factor is that it's possible for a theme to be BOTH.
// That is, a single set of theme files can serve as a FRESH theme -and- a
// JRS theme.
//
// TODO: The most robust way to deal with all these issues is with a strong
// theme validator. If a theme structure validates as a particular theme
// type, then for all intents and purposes, it IS a theme of that type.

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.
@module verbs/convert
@license MIT. See LICENSE.md for details.
###
*/
ResumeFactory = require('../core/resume-factory')
chalk = require('chalk')
Verb = require('../verbs/verb')
HMSTATUS = require('../core/status-codes')
_ = require('underscore')
HMEVENT = require('../core/event-codes');
let ConvertVerb;
const ResumeFactory = require('../core/resume-factory');
const chalk = require('chalk');
const Verb = require('../verbs/verb');
const HMSTATUS = require('../core/status-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
formats. ###
/** Private workhorse method. Convert 0..N resumes between FRESH and JRS
formats. */
_convert = ( srcs, dst, opts ) ->
var _convert = function( srcs, dst, opts ) {
# If no source resumes are specified, error out
if !srcs || !srcs.length
@err HMSTATUS.resumeNotFound, { quit: true }
return null
// If no source resumes are specified, error out
let fmtUp;
if (!srcs || !srcs.length) {
this.err(HMSTATUS.resumeNotFound, { quit: true });
return null;
}
# If no destination resumes are specified, error out except for the special
# case of two resumes:
# hackmyresume CONVERT r1.json r2.json
if !dst || !dst.length
if srcs.length == 1
@err HMSTATUS.inputOutputParity, { quit: true }
else if srcs.length == 2
dst = dst || []; dst.push( srcs.pop() )
else
@err HMSTATUS.inputOutputParity, { quit: true }
// If no destination resumes are specified, error out except for the special
// case of two resumes:
// hackmyresume CONVERT r1.json r2.json
if (!dst || !dst.length) {
if (srcs.length === 1) {
this.err(HMSTATUS.inputOutputParity, { quit: true });
} else if (srcs.length === 2) {
dst = dst || []; dst.push( srcs.pop() );
} else {
this.err(HMSTATUS.inputOutputParity, { quit: true });
}
}
# Different number of source and dest resumes? Error out.
if srcs && dst && srcs.length && dst.length && srcs.length != dst.length
@err HMSTATUS.inputOutputParity, { quit: true }
// Different number of source and dest resumes? Error out.
if (srcs && dst && srcs.length && dst.length && (srcs.length !== dst.length)) {
this.err(HMSTATUS.inputOutputParity, { quit: true });
}
# Validate the destination format (if specified)
targetVer = null
if opts.format
fmtUp = opts.format.trim().toUpperCase()
if not _.contains ['FRESH','FRESCA','JRS','JRS@1','JRS@edge'], fmtUp
@err HMSTATUS.invalidSchemaVersion, data: opts.format.trim(), quit: true
# freshVerRegex = require '../utils/fresh-version-regex'
# matches = fmtUp.match freshVerRegex()
# # null
# # [ 'JRS@1.0', 'JRS', '1.0', index: 0, input: 'FRESH' ]
# # [ 'FRESH', 'FRESH', undefined, index: 0, input: 'FRESH' ]
# if not matches
# @err HMSTATUS.invalidSchemaVersion, data: opts.format.trim(), quit: true
# targetSchema = matches[1]
# targetVer = matches[2] || '1'
// Validate the destination format (if specified)
const targetVer = null;
if (opts.format) {
fmtUp = opts.format.trim().toUpperCase();
if (!_.contains(['FRESH','FRESCA','JRS','JRS@1','JRS@edge'], fmtUp)) {
this.err(HMSTATUS.invalidSchemaVersion, {data: opts.format.trim(), quit: true});
}
}
// freshVerRegex = require '../utils/fresh-version-regex'
// matches = fmtUp.match freshVerRegex()
// # null
// # [ 'JRS@1.0', 'JRS', '1.0', index: 0, input: 'FRESH' ]
// # [ 'FRESH', 'FRESH', undefined, index: 0, input: 'FRESH' ]
// if not matches
// @err HMSTATUS.invalidSchemaVersion, data: opts.format.trim(), quit: true
// targetSchema = matches[1]
// targetVer = matches[2] || '1'
# If any errors have occurred this early, we're done.
if @hasError()
@reject @errorCode
return null
// If any errors have occurred this early, we're done.
if (this.hasError()) {
this.reject(this.errorCode);
return null;
}
# Map each source resume to the converted destination resume
results = _.map srcs, ( src, idx ) ->
// Map each source resume to the converted destination resume
const results = _.map(srcs, function( src, idx ) {
# Convert each resume in turn
r = _convertOne.call @, src, dst, idx, fmtUp
// Convert each resume in turn
const r = _convertOne.call(this, src, dst, idx, fmtUp);
# Handle conversion errors
if r.fluenterror
r.quit = opts.assert
@err r.fluenterror, r
r
, @
// Handle conversion errors
if (r.fluenterror) {
r.quit = opts.assert;
this.err(r.fluenterror, r);
}
return r;
}
, this);
if @hasError() and !opts.assert
@reject results
else if !@hasError()
@resolve results
results
if (this.hasError() && !opts.assert) {
this.reject(results);
} else if (!this.hasError()) {
this.resolve(results);
}
return results;
};
###* Private workhorse method. Convert a single resume. ###
_convertOne = (src, dst, idx, targetSchema) ->
/** Private workhorse method. Convert a single resume. */
var _convertOne = function(src, dst, idx, targetSchema) {
# Load the resume
rinfo = ResumeFactory.loadOne src,
format: null
objectify: true
inner:
// Load the resume
const rinfo = ResumeFactory.loadOne(src, {
format: null,
objectify: true,
inner: {
privatize: false
}
}
);
# If a load error occurs, report it and move on to the next file (if any)
if rinfo.fluenterror
@stat HMEVENT.beforeConvert,
srcFile: src #rinfo.file
srcFmt: '???'
dstFile: dst[idx]
dstFmt: '???'
// If a load error occurs, report it and move on to the next file (if any)
if (rinfo.fluenterror) {
this.stat(HMEVENT.beforeConvert, {
srcFile: src, //rinfo.file
srcFmt: '???',
dstFile: dst[idx],
dstFmt: '???',
error: true
#@err rinfo.fluenterror, rinfo
return rinfo
}
);
//@err rinfo.fluenterror, rinfo
return rinfo;
}
# Determine the resume's SOURCE format
# TODO: replace with detector component
rez = rinfo.rez
srcFmt = ''
if rez.meta && rez.meta.format #&& rez.meta.format.substr(0, 5).toUpperCase() == 'FRESH'
srcFmt = 'FRESH'
else if rez.basics
srcFmt = 'JRS'
else
rinfo.fluenterror = HMSTATUS.unknownSchema
return rinfo
// Determine the resume's SOURCE format
// TODO: replace with detector component
const { rez } = rinfo;
let srcFmt = '';
if (rez.meta && rez.meta.format) { //&& rez.meta.format.substr(0, 5).toUpperCase() == 'FRESH'
srcFmt = 'FRESH';
} else if (rez.basics) {
srcFmt = 'JRS';
} else {
rinfo.fluenterror = HMSTATUS.unknownSchema;
return rinfo;
}
# Determine the TARGET format for the conversion
targetFormat = targetSchema or (if srcFmt == 'JRS' then 'FRESH' else 'JRS')
// Determine the TARGET format for the conversion
const targetFormat = targetSchema || (srcFmt === 'JRS' ? 'FRESH' : 'JRS');
# Fire the beforeConvert event
this.stat HMEVENT.beforeConvert,
srcFile: rinfo.file
srcFmt: srcFmt
dstFile: dst[idx]
// Fire the beforeConvert event
this.stat(HMEVENT.beforeConvert, {
srcFile: rinfo.file,
srcFmt,
dstFile: dst[idx],
dstFmt: targetFormat
}
);
# Save it to the destination format
try
rez.saveAs dst[idx], targetFormat
catch err
if err.badVer
return fluenterror: HMSTATUS.invalidSchemaVersion, quit: true, data: err.badVer
rez
// Save it to the destination format
try {
rez.saveAs(dst[idx], targetFormat);
} catch (err) {
if (err.badVer) {
return {fluenterror: HMSTATUS.invalidSchemaVersion, quit: true, data: err.badVer};
}
}
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.
@module verbs/create
@license MIT. See LICENSE.md for details.
###
*/
MKDIRP = require 'mkdirp'
PATH = require 'path'
chalk = require 'chalk'
Verb = require '../verbs/verb'
_ = require 'underscore'
HMSTATUS = require '../core/status-codes'
HMEVENT = require '../core/event-codes'
let CreateVerb;
const MKDIRP = require('mkdirp');
const PATH = require('path');
const chalk = require('chalk');
const Verb = require('../verbs/verb');
const _ = require('underscore');
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 = ( src, dst, opts ) ->
/** Create a new empty resume in either FRESH or JRS format. */
var _create = function( src, dst, opts ) {
if !src || !src.length
@err HMSTATUS.createNameMissing, { quit: true }
return null
if (!src || !src.length) {
this.err(HMSTATUS.createNameMissing, { quit: true });
return null;
}
results = _.map src, ( t ) ->
return { } if opts.assert and @hasError()
r = _createOne.call @, t, opts
if r.fluenterror
r.quit = opts.assert
@err r.fluenterror, r
r
, @
const results = _.map(src, function( t ) {
if (opts.assert && this.hasError()) { return { }; }
const r = _createOne.call(this, t, opts);
if (r.fluenterror) {
r.quit = opts.assert;
this.err(r.fluenterror, r);
}
return r;
}
, this);
if @hasError() and !opts.assert
@reject @errorCode
else if !@hasError()
@resolve results
results
if (this.hasError() && !opts.assert) {
this.reject(this.errorCode);
} else if (!this.hasError()) {
this.resolve(results);
}
return results;
};
###* Create a single new resume ###
_createOne = ( t, opts ) ->
try
ret = null
safeFmt = opts.format.toUpperCase()
@.stat HMEVENT.beforeCreate, { fmt: safeFmt, file: t }
MKDIRP.sync PATH.dirname( t ) # Ensure dest folder exists;
RezClass = require '../core/' + safeFmt.toLowerCase() + '-resume'
newRez = RezClass.default()
newRez.save t
ret = newRez
return
catch err
ret =
fluenterror: HMSTATUS.createError
/** Create a single new resume */
var _createOne = function( t, opts ) {
let ret, safeFmt;
try {
ret = null;
safeFmt = opts.format.toUpperCase();
this.stat(HMEVENT.beforeCreate, { fmt: safeFmt, file: t });
MKDIRP.sync(PATH.dirname( t )); // Ensure dest folder exists;
const RezClass = require(`../core/${safeFmt.toLowerCase()}-resume`);
const newRez = RezClass.default();
newRez.save(t);
ret = newRez;
return;
} catch (err) {
ret = {
fluenterror: HMSTATUS.createError,
inner: err
return
finally
@.stat HMEVENT.afterCreate, fmt: safeFmt, file: t, isError: ret.fluenterror
return ret
};
return;
}
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.
@module verbs/peek
@license MIT. See LICENSE.md for details.
###
*/
Verb = require('../verbs/verb')
_ = require('underscore')
__ = require('lodash')
safeLoadJSON = require('../utils/safe-json-loader')
HMSTATUS = require('../core/status-codes')
HMEVENT = require('../core/event-codes')
let PeekVerb;
const Verb = require('../verbs/verb');
const _ = require('underscore');
const __ = require('lodash');
const safeLoadJSON = require('../utils/safe-json-loader');
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 = ( src, dst, opts ) ->
/** Peek at a resume, resume section, or resume field. */
var _peek = function( src, dst, opts ) {
if !src || !src.length
@err HMSTATUS.resumeNotFound, { quit: true }
return null
if (!src || !src.length) {
this.err(HMSTATUS.resumeNotFound, { quit: true });
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
if tgt.error
@setError tgt.error.fluenterror, tgt.error
#tgt.error.quit = opts.assert
#@err tgt.error.fluenterror, tgt.error
tgt
, @
const tgt = _peekOne.call(this, t, objPath);
if (tgt.error) {
this.setError(tgt.error.fluenterror, tgt.error);
}
//tgt.error.quit = opts.assert
//@err tgt.error.fluenterror, tgt.error
return tgt;
}
, this);
if @hasError() and !opts.assert
@reject @errorCode
else if !@hasError()
@resolve results
results
if (this.hasError() && !opts.assert) {
this.reject(this.errorCode);
} else if (!this.hasError()) {
this.resolve(results);
}
return results;
};
###* Peek at a single resume, resume section, or resume field. ###
_peekOne = ( t, objPath ) ->
/** Peek at a single resume, resume section, or resume field. */
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
obj = safeLoadJSON t
// Load the input file JSON 1st
const obj = safeLoadJSON(t);
# Fetch the requested object path (or the entire file)
tgt = null
if !obj.ex
tgt = if objPath then __.get obj.json, objPath else obj.json
// Fetch the requested object path (or the entire file)
let tgt = null;
if (!obj.ex) {
tgt = objPath ? __.get(obj.json, objPath) : obj.json;
}
## safeLoadJSON can only return a READ error or a PARSE error
pkgError = null
if obj.ex
errCode = if obj.ex.op == 'parse' then HMSTATUS.parseError else HMSTATUS.readError
if errCode == HMSTATUS.readError
obj.ex.quiet = true
pkgError = fluenterror: errCode, inner: obj.ex
//# safeLoadJSON can only return a READ error or a PARSE error
let pkgError = null;
if (obj.ex) {
const errCode = obj.ex.op === 'parse' ? HMSTATUS.parseError : HMSTATUS.readError;
if (errCode === HMSTATUS.readError) {
obj.ex.quiet = true;
}
pkgError = {fluenterror: errCode, inner: obj.ex};
}
# Fire the 'afterPeek' event with collected info
@stat HMEVENT.afterPeek,
file: t
requested: objPath
target: if obj.ex then undefined else tgt
// Fire the 'afterPeek' event with collected info
this.stat(HMEVENT.afterPeek, {
file: t,
requested: objPath,
target: obj.ex ? undefined : tgt,
error: pkgError
}
);
val: if obj.ex then undefined else tgt
error: pkgError
return {
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.
@module verbs/validate
@license MIT. See LICENSE.md for details.
###
*/
FS = require 'fs'
ResumeFactory = require '../core/resume-factory'
SyntaxErrorEx = require '../utils/syntax-error-ex'
chalk = require 'chalk'
Verb = require '../verbs/verb'
HMSTATUS = require '../core/status-codes'
HMEVENT = require '../core/event-codes'
_ = require 'underscore'
safeLoadJSON = require '../utils/safe-json-loader'
let ValidateVerb;
const FS = require('fs');
const ResumeFactory = require('../core/resume-factory');
const SyntaxErrorEx = require('../utils/syntax-error-ex');
const chalk = require('chalk');
const Verb = require('../verbs/verb');
const HMSTATUS = require('../core/status-codes');
const HMEVENT = require('../core/event-codes');
const _ = require('underscore');
const safeLoadJSON = require('../utils/safe-json-loader');
###* An invokable resume validation command. ###
module.exports = class ValidateVerb extends Verb
/** An invokable resume validation command. */
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 = (sources, unused, opts) ->
// Validate 1 to N resumes in FRESH or JSON Resume format.
var _validate = function(sources, unused, opts) {
if !sources || !sources.length
@err HMSTATUS.resumeNotFoundAlt, quit: true
return null
if (!sources || !sources.length) {
this.err(HMSTATUS.resumeNotFoundAlt, {quit: true});
return null;
}
validator = require 'is-my-json-valid'
schemas =
fresh: require 'fresh-resume-schema'
jars: require '../core/resume.json'
const validator = require('is-my-json-valid');
const schemas = {
fresh: require('fresh-resume-schema'),
jars: require('../core/resume.json')
};
results = _.map sources, (t) ->
r = _validateOne.call @, t, validator, schemas, opts
@err r.error.fluenterror, r.error if r.error
r
, @
const results = _.map(sources, function(t) {
const r = _validateOne.call(this, t, validator, schemas, opts);
if (r.error) { this.err(r.error.fluenterror, r.error); }
return r;
}
, this);
if @hasError() and !opts.assert
@reject @errorCode
else if !@hasError()
@resolve results
results
if (this.hasError() && !opts.assert) {
this.reject(this.errorCode);
} else if (!this.hasError()) {
this.resolve(results);
}
return results;
};
###*
/**
Validate a single resume.
@returns {
file: <fileName>,
@ -62,41 +74,47 @@ Validate a single resume.
schema: <schemaType>,
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.
obj = safeLoadJSON t
// Read and parse the resume JSON. Won't throw.
const obj = safeLoadJSON(t);
# If success, validate the resume against the schema
if !obj.ex
if obj.json.basics then ret.schema = 'jars' else ret.schema = 'fresh'
validate = validator schemas[ ret.schema ], # Note [1]
formats: { date: /^\d{4}(?:-(?:0[0-9]{1}|1[0-2]{1})(?:-[0-9]{2})?)?$/ }
ret.isValid = validate obj.json
ret.status = if ret.isValid then 'valid' else 'invalid'
ret.violations = validate.errors if !ret.isValid
// If success, validate the resume against the schema
if (!obj.ex) {
if (obj.json.basics) { ret.schema = 'jars'; } else { ret.schema = 'fresh'; }
const validate = validator(schemas[ ret.schema ], // Note [1]
{formats: { date: /^\d{4}(?:-(?:0[0-9]{1}|1[0-2]{1})(?:-[0-9]{2})?)?$/ }});
ret.isValid = validate(obj.json);
ret.status = ret.isValid ? 'valid' : 'invalid';
if (!ret.isValid) { ret.violations = validate.errors; }
# If failure, package JSON read/parse errors
else
if obj.ex.op == 'parse'
errCode = HMSTATUS.parseError
ret.status = 'broken'
else
errCode = HMSTATUS.readError
ret.status = 'missing'
ret.error =
// If failure, package JSON read/parse errors
} else {
let errCode;
if (obj.ex.op === 'parse') {
errCode = HMSTATUS.parseError;
ret.status = 'broken';
} else {
errCode = HMSTATUS.readError;
ret.status = 'missing';
}
ret.error = {
fluenterror: errCode,
inner: obj.ex.inner,
quiet: errCode == HMSTATUS.readError
quiet: errCode === HMSTATUS.readError
};
}
catch err
# Package any unexpected exceptions
ret.error = fluenterror: HMSTATUS.validateError, inner: err
} catch (err) {
// Package any unexpected exceptions
ret.error = {fluenterror: HMSTATUS.validateError, inner: err};
}
@stat HMEVENT.afterValidate, ret
ret
this.stat(HMEVENT.afterValidate, 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.
@module verbs/verb
@license MIT. See LICENSE.md for details.
###
*/
EVENTS = require 'events'
HMEVENT = require '../core/event-codes'
Promise = require 'pinkie-promise'
let Verb;
const EVENTS = require('events');
const HMEVENT = require('../core/event-codes');
const Promise = require('pinkie-promise');
###*
/**
An abstract invokable verb.
Provides base class functionality for verbs. Provide common services such as
error handling, event management, and promise support.
@class Verb
###
*/
module.exports = class Verb
module.exports = (Verb = class Verb {
###* Constructor. Automatically called at creation. ###
constructor: ( @moniker, @workhorse ) ->
@emitter = new EVENTS.EventEmitter()
return
/** Constructor. Automatically called at creation. */
constructor( moniker, workhorse ) {
this.moniker = moniker;
this.workhorse = workhorse;
this.emitter = new EVENTS.EventEmitter();
}
###* Invoke the command. ###
invoke: ->
/** Invoke the command. */
invoke() {
# Sent the 'begin' notification for this verb
@stat HMEVENT.begin, cmd: @moniker
// Sent the 'begin' notification for this verb
this.stat(HMEVENT.begin, {cmd: this.moniker});
# Prepare command arguments
argsArray = Array::slice.call arguments
// Prepare command arguments
const argsArray = Array.prototype.slice.call(arguments);
# Create a promise for this verb instance
that = @
@promise = new Promise (res, rej) ->
that.resolve = res
that.reject = rej
that.workhorse.apply that, argsArray
return
// Create a promise for this verb instance
const that = this;
return this.promise = new Promise(function(res, rej) {
that.resolve = res;
that.reject = rej;
that.workhorse.apply(that, argsArray);
});
}
###* Forward subscriptions to the event emitter. ###
on: -> @emitter.on.apply @emitter, arguments
/** Forward subscriptions to the event emitter. */
on() { return this.emitter.on.apply(this.emitter, arguments); }
###* Fire an arbitrary event, scoped to "hmr:". ###
fire: (evtName, payload) ->
payload = payload || { }
payload.cmd = @moniker
@emitter.emit 'hmr:' + evtName, payload
true
/** Fire an arbitrary event, scoped to "hmr:". */
fire(evtName, payload) {
payload = payload || { };
payload.cmd = this.moniker;
this.emitter.emit(`hmr:${evtName}`, payload);
return true;
}
###* Handle an error condition. ###
err: ( errorCode, payload, hot ) ->
payload = payload || { }
payload.sub = payload.fluenterror = errorCode
payload.throw = hot
@setError errorCode, payload
if payload.quit
@reject errorCode
@fire 'error', payload
if hot
throw payload
true
/** Handle an error condition. */
err( errorCode, payload, hot ) {
payload = payload || { };
payload.sub = (payload.fluenterror = errorCode);
payload.throw = hot;
this.setError(errorCode, payload);
if (payload.quit) {
this.reject(errorCode);
}
this.fire('error', payload);
if (hot) {
throw payload;
}
return true;
}
###* Fire the 'hmr:status' error event. ###
stat: ( subEvent, payload ) ->
payload = payload || { }
payload.sub = subEvent
@fire 'status', payload
true
/** Fire the 'hmr:status' error event. */
stat( subEvent, payload ) {
payload = payload || { };
payload.sub = subEvent;
this.fire('status', payload);
return true;
}
###* Has an error occurred during this verb invocation? ###
hasError: -> @errorCode || @errorObj
/** Has an error occurred during this verb invocation? */
hasError() { return this.errorCode || this.errorObj; }
###* Associate error info with the invocation. ###
setError: ( code, obj ) ->
@errorCode = code
@errorObj = obj
return
/** Associate error info with the invocation. */
setError( code, obj ) {
this.errorCode = code;
this.errorObj = obj;
}
});