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

Relocate internal sources to HackMyAPI.

Move internal sources and related tests to:

https://github.com/hacksalot/HackMyAPI
This commit is contained in:
hacksalot
2016-01-24 09:55:04 -05:00
parent fa29f9794d
commit d3194fba19
61 changed files with 15 additions and 6109 deletions

View File

@ -1,94 +0,0 @@
/**
Implementation of the 'analyze' verb for HackMyResume.
@module verbs/analyze
@license MIT. See LICENSE.md for details.
*/
(function(){
var 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');
var AnalyzeVerb = module.exports = Verb.extend({
init: function() {
this._super('analyze');
},
invoke: function() {
this.stat( HMEVENT.begin, { cmd: 'analyze' });
analyze.apply( this, arguments );
this.stat( HMEVENT.end );
}
});
/**
Run the 'analyze' command.
*/
function analyze( sources, dst, opts ) {
if( !sources || !sources.length )
throw { fluenterror: HMSTATUS.resumeNotFound, quit: true };
var nlzrs = _loadInspectors();
_.each(sources, function(src) {
var result = ResumeFactory.loadOne( src, {
format: 'FRESH', objectify: true
}, this);
if( result.fluenterror )
this.setError( result.fluenterror, result );
else
_analyze.call(this, result, nlzrs, opts );
}, this);
}
/**
Analyze a single resume.
*/
function _analyze( resumeObject, nlzrs, opts ) {
var rez = resumeObject.rez;
var safeFormat =
(rez.meta && rez.meta.format && rez.meta.format.startsWith('FRESH')) ?
'FRESH' : 'JRS';
this.stat( HMEVENT.beforeAnalyze, { fmt: safeFormat, file: resumeObject.file });
var info = _.mapObject( nlzrs, function(val, key) {
return val.run( resumeObject.rez );
});
this.stat( HMEVENT.afterAnalyze, { info: info } );
}
/**
Load inspectors.
*/
function _loadInspectors() {
return {
totals: require('../inspectors/totals-inspector'),
coverage: require('../inspectors/gap-inspector'),
keywords: require('../inspectors/keyword-inspector')
};
}
}());

View File

@ -1,385 +0,0 @@
/**
Implementation of the 'build' verb for HackMyResume.
@module verbs/build
@license MIT. See LICENSE.md for details.
*/
(function() {
var _ = 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');
var _err, _log, _rezObj;
/** An invokable resume generation command. */
var BuildVerb = module.exports = Verb.extend({
/** Create a new build verb. */
init: function() {
this._super('build');
},
/** Invoke the Build command. */
invoke: function() {
this.stat( HMEVENT.begin, { cmd: 'build' } );
var ret = build.apply( this, arguments );
this.stat( HMEVENT.end );
return ret;
}
});
/**
Given a source resume in FRESH or JRS format, a destination resume path, and a
theme file, generate 0..N resumes in the desired formats.
@param src Path to the source JSON resume file: "rez/resume.json".
@param dst An array of paths to the target resume file(s).
@param theme Friendly name of the resume theme. Defaults to "modern".
@param logger Optional logging override.
*/
function build( src, dst, opts ) {
if( !src || !src.length ) {
this.err( HMSTATUS.resumeNotFound, { quit: true } );
}
prep( src, dst, opts );
// Load input resumes as JSON...
var sheetObjects = ResumeFactory.load(src, {
format: null, objectify: false, quit: true, inner: { sort: _opts.sort }
}, this);
// Explicit check for any resume loading errors...
if( !sheetObjects ||
_.some( sheetObjects, function(so) { return so.fluenterror; } ) ) {
return null;
}
var sheets = sheetObjects.map(function(r) { return r.json; });
// Load the theme...
var theme;
this.stat( HMEVENT.beforeTheme, { theme: _opts.theme });
try {
var tFolder = verifyTheme.call( this, _opts.theme );
theme = _opts.themeObj = loadTheme( tFolder );
}
catch( ex ) {
var newEx = {
fluenterror: HMSTATUS.themeLoad,
inner: ex,
attempted: _opts.theme
};
this.err( HMSTATUS.themeLoad, newEx );
return null;
}
this.stat( HMEVENT.afterTheme, { theme: theme });
// Check for invalid outputs...
var inv = verifyOutputs.call( this, dst, theme );
if( inv && inv.length ) {
this.err( HMSTATUS.invalidFormat, { data: inv, theme: theme } );
}
// Merge input resumes, yielding a single source resume.
var rez;
if( sheets.length > 1 ) {
var isFRESH = !sheets[0].basics;
var mixed = _.any( sheets, function(s) { return isFRESH ? s.basics : !s.basics; });
this.stat( HMEVENT.beforeMerge, { f: _.clone(sheetObjects), mixed: mixed });
if( mixed ) {
this.err( HMSTATUS.mixedMerge );
}
rez = _.reduceRight( sheets, function( a, b, idx ) {
return 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
var orgFormat = rez.basics ? 'JRS' : 'FRESH';
var 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 });
}
// Add freebie formats to the theme
addFreebieFormats( theme );
this.stat( HMEVENT.applyTheme, { r: rez, theme: theme });
// Load the resume into a FRESHResume or JRSResume object
_rezObj = new (RTYPES[ toFormat ])().parseJSON( rez );
// Expand output resumes...
var targets = expand( dst, theme );
// Run the transformation!
_.each(targets, function(t) {
t.final = single.call( this, t, theme, targets );
}, this);
// Don't send the client back empty-handed
return { sheet: _rezObj, targets: targets, processed: targets };
}
/**
Prepare for a BUILD run.
*/
function prep( src, dst, opts ) {
// Cherry-pick options //_opts = extend( true, _opts, opts );
_opts.theme = (opts.theme && opts.theme.toLowerCase().trim()) || 'modern';
_opts.prettify = opts.prettify === 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;
// 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.
*/
function single( targInfo, theme, finished ) {
var ret, ex, f = targInfo.file;
try {
if( !targInfo.fmt ) { return; }
var fType = targInfo.fmt.outFormat
, fName = PATH.basename(f, '.' + fType)
, theFormat;
this.stat( HMEVENT.beforeGenerate, {
fmt: targInfo.fmt.outFormat,
file: PATH.relative(process.cwd(), f)
});
// 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(
function(fmt) { return fmt.name === targInfo.fmt.outFormat; })[0];
MKDIRP.sync( PATH.dirname( f ) ); // Ensure dest folder exists;
_opts.targets = finished;
ret = theFormat.gen.generate( _rezObj, f, _opts );
}
//Otherwise this is an ad-hoc format (JSON, YML, or PNG) that every theme
// gets "for free".
else {
theFormat = _fmts.filter( function(fmt) {
return fmt.name === targInfo.fmt.outFormat;
})[0];
var outFolder = PATH.dirname( f );
MKDIRP.sync( outFolder ); // Ensure dest folder exists;
ret = theFormat.gen.generate( _rezObj, f, _opts );
}
}
catch( e ) {
// Catch any errors caused by generating this file and don't let them
// propagate -- typically we want to continue processing other formats
// even if this format failed.
ex = e;
}
this.stat( HMEVENT.afterGenerate, {
fmt: targInfo.fmt.outFormat,
file: PATH.relative( process.cwd(), f ),
error: ex
});
if( ex ) {
if( ex.fluenterror )
this.err( ex.fluenterror, ex );
else
this.err( HMSTATUS.generateError, { inner: ex } );
}
return ret;
}
/**
Ensure that user-specified outputs/targets are valid.
*/
function verifyOutputs( targets, theme ) {
this.stat(HMEVENT.verifyOutputs, { targets: targets, theme: theme });
return _.reject(
targets.map( function( t ) {
var pathInfo = parsePath( t );
return {
format: pathInfo.extname.substr(1)
};
}),
function(t) {
return 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
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.
*/
function addFreebieFormats( 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 ) {
theTheme.formats.png = {
freebie: true, title: 'png', outFormat: 'png',
ext: 'yml', path: null, data: null
};
}
}
/**
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.
*/
function expand( 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.
var destColl = (dst && dst.length && dst) ||
[PATH.normalize('out/resume.all')];
// Assemble an array of expanded target files... (can't use map() here)
var targets = [];
destColl.forEach( function(t) {
var to = PATH.resolve(t), pa = parsePath(to),fmat = pa.extname || '.all';
targets.push.apply(
targets, fmat === '.all' ?
Object.keys( theTheme.formats ).map( function( k ) {
var z = theTheme.formats[k];
return { file: to.replace( /all$/g, z.outFormat ), fmt: z };
}) :
[{ file: to, fmt: theTheme.getFormat( fmat.slice(1) ) }]);
// targets.push.apply(
// targets, fmat === '.all' ?
// Object.keys( explicitFormats ).map( function( k ) {
// var 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.
*/
function verifyTheme( themeNameOrPath ) {
var tFolder = PATH.join(
parsePath ( require.resolve('fresh-themes') ).dirname,
'/themes/',
themeNameOrPath
);
var exists = require('path-exists').sync;
if( !exists( tFolder ) ) {
tFolder = PATH.resolve( themeNameOrPath );
if( !exists( tFolder ) ) {
this.err( HMSTATUS.themeNotFound, { data: _opts.theme } );
}
}
return tFolder;
}
/**
Load the specified theme, which could be either a FRESH theme or a JSON Resume
theme.
*/
function loadTheme( tFolder ) {
// Create a FRESH or JRS theme object
var theTheme = _opts.theme.indexOf('jsonresume-theme-') > -1 ?
new JRSTheme().open(tFolder) : new FRESHTheme().open( tFolder );
// Cache the theme object
_opts.themeObj = theTheme;
return theTheme;
}
}());

View File

@ -1,89 +0,0 @@
/**
Implementation of the 'convert' verb for HackMyResume.
@module verbs/convert
@license MIT. See LICENSE.md for details.
*/
(function(){
var ResumeFactory = require('../core/resume-factory')
, chalk = require('chalk')
, Verb = require('../verbs/verb')
, HMSTATUS = require('../core/status-codes')
, _ = require('underscore')
, HMEVENT = require('../core/event-codes');
var ConvertVerb = module.exports = Verb.extend({
init: function() {
this._super('convert');
},
invoke: function() {
this.stat( HMEVENT.begin, { cmd: 'convert' });
convert.apply( this, arguments );
this.stat( HMEVENT.end );
}
});
/**
Convert between FRESH and JRS formats.
*/
function convert( srcs, dst, opts ) {
// Housekeeping
if( !srcs || !srcs.length ) { throw { fluenterror: 6, quit: true }; }
if( !dst || !dst.length ) {
if( srcs.length === 1 ) {
throw { fluenterror: HMSTATUS.inputOutputParity, quit: true };
}
else if( srcs.length === 2 ) {
dst = dst || []; dst.push( srcs.pop() );
}
else {
throw { fluenterror: HMSTATUS.inputOutputParity, quit: true };
}
}
if(srcs && dst && srcs.length && dst.length && srcs.length !== dst.length){
throw { fluenterror: HMSTATUS.inputOutputParity, quit: true };
}
// Load source resumes
_.each(srcs, function( src, idx ) {
// Load the resume
var rinfo = ResumeFactory.loadOne( src, {
format: null, objectify: true, throw: false
});
// If a load error occurs, report it and move on to the next file (if any)
if( rinfo.fluenterror ) {
this.err( rinfo.fluenterror, rinfo );
return;
}
var s = rinfo.rez
, srcFmt = ((s.basics && s.basics.imp) || s.imp).orgFormat === 'JRS' ?
'JRS' : 'FRESH'
, targetFormat = srcFmt === 'JRS' ? 'FRESH' : 'JRS';
this.stat(HMEVENT.beforeConvert, { srcFile: rinfo.file, srcFmt: srcFmt, dstFile: dst[idx], dstFmt: targetFormat });
// Save it to the destination format
s.saveAs( dst[idx], targetFormat );
}, this);
}
}());

View File

@ -1,60 +0,0 @@
/**
Implementation of the 'create' verb for HackMyResume.
@module verbs/create
@license MIT. See LICENSE.md for details.
*/
(function(){
var 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');
var CreateVerb = module.exports = Verb.extend({
init: function() {
this._super('new');
},
invoke: function() {
this.stat( HMEVENT.begin, { cmd: 'create' });
create.apply( this, arguments );
this.stat( HMEVENT.begin, { cmd: 'convert' });
}
});
/**
Create a new empty resume in either FRESH or JRS format.
*/
function create( src, dst, opts ) {
if( !src || !src.length )
throw { fluenterror: HMSTATUS.createNameMissing, quit: true };
_.each( src, function( t ) {
var safeFmt = opts.format.toUpperCase();
this.stat( HMEVENT.beforeCreate, { fmt: safeFmt, file: t } );
MKDIRP.sync( PATH.dirname( t ) ); // Ensure dest folder exists;
var RezClass = require('../core/' + safeFmt.toLowerCase() + '-resume' );
RezClass.default().save(t);
this.stat( HMEVENT.afterCreate, { fmt: safeFmt, file: t } );
}, this);
}
}());

View File

@ -1,83 +0,0 @@
/**
Implementation of the 'peek' verb for HackMyResume.
@module verbs/peek
@license MIT. See LICENSE.md for details.
*/
(function(){
var Verb = require('../verbs/verb')
, _ = require('underscore')
, __ = require('lodash')
, safeLoadJSON = require('../utils/safe-json-loader')
, HMSTATUS = require('../core/status-codes')
, HMEVENT = require('../core/event-codes');
var PeekVerb = module.exports = Verb.extend({
init: function() {
this._super('peek');
},
invoke: function() {
this.stat( HMEVENT.begin, { cmd: 'peek' } );
peek.apply( this, arguments );
this.stat( HMEVENT.end );
}
});
/**
Peek at a resume, resume section, or resume field.
*/
function peek( src, dst, opts ) {
if(!src || !src.length) throw {fluenterror: HMSTATUS.resumeNotFound};
var objPath = (dst && dst[0]) || '';
_.each( src, function( t ) {
// Fire the 'beforePeek' event 2nd, so we have error/warning/success
this.stat( HMEVENT.beforePeek, { file: t, target: objPath } );
// Load the input file JSON 1st
var obj = safeLoadJSON( t );
// Fetch the requested object path (or the entire file)
var tgt;
if( !obj.ex )
tgt = objPath ? __.get( obj.json, objPath ) : obj.json;
// Fire the 'afterPeek' event with collected info
this.stat( HMEVENT.afterPeek, {
file: t,
requested: objPath,
target: tgt,
error: obj.ex
});
// safeLoadJSON can only return a READ error or a PARSE error
if( obj.ex ) {
var errCode = obj.ex.operation === 'parse' ? HMSTATUS.parseError : HMSTATUS.readError;
if( errCode === HMSTATUS.readError )
obj.ex.quiet = true;
this.setError( errCode, obj.ex );
this.err( errCode, obj.ex );
}
}, this);
}
}());

View File

@ -1,103 +0,0 @@
/**
Implementation of the 'validate' verb for HackMyResume.
@module verbs/validate
@license MIT. See LICENSE.md for details.
*/
(function() {
var FS = require('fs');
var ResumeFactory = require('../core/resume-factory');
var SyntaxErrorEx = require('../utils/syntax-error-ex');
var chalk = require('chalk');
var Verb = require('../verbs/verb');
var HMSTATUS = require('../core/status-codes');
var HMEVENT = require('../core/event-codes');
var _ = require('underscore');
/** An invokable resume validation command. */
var ValidateVerb = module.exports = Verb.extend({
init: function() {
this._super('validate');
},
invoke: function() {
this.stat( HMEVENT.begin, { cmd: 'validate' } );
validate.apply( this, arguments );
this.stat( HMEVENT.end );
}
});
/** Validate 1 to N resumes in FRESH or JSON Resume format. */
function validate( sources, unused, opts ) {
if( !sources || !sources.length )
throw { fluenterror: HMSTATUS.resumeNotFoundAlt, quit: true };
var validator = require('is-my-json-valid');
var schemas = {
fresh: require('fresca'),
jars: require('../core/resume.json')
};
var resumes = ResumeFactory.load( sources, {
format: null,
objectify: false
}, this );
// Validate input resumes. Return a { file: <f>, isValid: <v>} object for
// each resume (valid, invalid, or broken).
return resumes.map( function( src ) {
var ret = { file: src, isValid: false };
// If there was an error reading the resume
if( src.fluenterror ) {
if( opts.assert ) throw src;
this.setError( src.fluenterror, src );
return ret;
}
// Successfully read the resume. Now parse it as JSON.
var json = src.json, fmt = json.basics ? 'jrs' : 'fresh', errors = [];
try {
var validate = validator( schemas[ fmt ], { // Note [1]
formats: {
date: /^\d{4}(?:-(?:0[0-9]{1}|1[0-2]{1})(?:-[0-9]{2})?)?$/
}
});
ret.isValid = validate( json );
if( !ret.isValid ) {
errors = validate.errors;
}
}
catch( exc ) {
return ret;
}
this.stat( HMEVENT.afterValidate, { file: src.file, isValid: ret.isValid,
fmt: fmt.replace( 'jars', 'JSON Resume' ), errors: errors });
if( opts.assert && !ret.isValid ) {
throw { fluenterror: HMSTATUS.invalid, shouldExit: true };
}
return ret;
}, this);
}
}());

View File

@ -1,96 +0,0 @@
/**
Definition of the Verb class.
@module verbs/verb
@license MIT. See LICENSE.md for details.
*/
(function(){
// Use J. Resig's nifty class implementation
var Class = require( '../utils/class' )
, EVENTS = require('events');
/**
An instantiation of a HackMyResume command.
@class Verb
*/
var Verb = module.exports = Class.extend({
/**
Constructor. Automatically called at creation.
*/
init: function( moniker ) {
this.moniker = moniker;
this.emitter = new EVENTS.EventEmitter();
},
/**
Forward subscriptions to the event emitter.
*/
on: function() {
this.emitter.on.apply( this.emitter, arguments );
},
/**
Fire an arbitrary event, scoped to "hmr:".
*/
fire: function(evtName, payload) {
payload = payload || { };
payload.cmd = this.moniker;
this.emitter.emit( 'hmr:' + evtName, payload );
return true;
},
/**
Handle an error condition.
*/
err: function( errorCode, payload, hot ) {
payload = payload || { };
payload.sub = payload.fluenterror = errorCode;
payload.throw = hot;
this.fire( 'error', payload );
if( hot ) throw payload;
return true;
},
/**
Fire the 'hmr:status' error event.
*/
stat: function( subEvent, payload ) {
payload = payload || { };
payload.sub = subEvent;
this.fire('status', payload);
return true;
},
/**
Associate error info with the invocation.
*/
setError: function( code, obj ) {
this.errorCode = code;
this.errorObj = obj;
}
});
}());