1
0
mirror of https://github.com/JuanCanham/HackMyResume.git synced 2025-04-19 14:20:25 +01:00
HackMyResume/src/verbs/generate.js
2015-12-30 20:09:39 -05:00

286 lines
8.5 KiB
JavaScript

/**
Implementation of the 'generate' verb for HackMyResume.
@module generate.js
@license MIT. See LICENSE.md for details.
*/
(function() {
var PATH = require('path')
, FS = require('fs')
, MD = require('marked')
, MKDIRP = require('mkdirp')
, EXTEND = require('../utils/extend')
, parsePath = require('parse-filepath')
, _opts = require('../core/default-options')
, FluentTheme = require('../core/fresh-theme')
, JRSTheme = require('../core/jrs-theme')
, ResumeFactory = require('../core/resume-factory')
, _ = require('underscore')
, _fmts = require('../core/default-formats')
, _err, _log, rez;
/**
Handle an exception.
*/
function error( ex ) {
throw ex;
}
/**
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, logger, errHandler ) {
// Housekeeping...
_log = logger || console.log;
_err = errHandler || error;
//_opts = extend( true, _opts, opts );
_opts.theme = (opts.theme && opts.theme.toLowerCase().trim())|| 'modern';
_opts.prettify = opts.prettify === true ? _opts.prettify : false;
_opts.css = opts.css;
// Load the theme...
var tFolder = verify_theme( _opts.theme );
var theTheme = load_theme( tFolder );
// Load input resumes...
if( !src || !src.length ) { throw { fluenterror: 3 }; }
var sheets = ResumeFactory.load( src, _log, null,
theTheme.render ? 'JRS' : 'FRESH' );
// Merge input resumes...
var msg = '';
rez = _.reduceRight( sheets, function( a, b, idx ) {
msg += ((idx == sheets.length - 2) ?
'Merging '.gray+ a.imp.fileName : '') + ' onto '.gray + b.imp.fileName;
return extend( true, b, a );
});
msg && _log(msg);
// Expand output resumes...
var targets = expand( dst, theTheme );
// Run the transformation!
targets.forEach( function(t) {
t.final = single( t, theTheme, targets );
});
// Don't send the client back empty-handed
return { sheet: rez, targets: targets, processed: targets };
}
/**
Generate a single target resume such as "out/rez.html" or "out/rez.doc".
@param targInfo Information for the target resume.
@param theme A FRESHTheme or JRSTheme object.
@returns
*/
function single( targInfo, theme, finished ) {
function MDIN(txt) { // TODO: Move this
return MD(txt || '' ).replace(/^\s*<p>|<\/p>\s*$/gi, '');
}
try {
var f = targInfo.file
, fType = targInfo.fmt.outFormat
, fName = PATH.basename(f, '.' + fType)
, theFormat;
_log( 'Generating '.useful +
targInfo.fmt.outFormat.toUpperCase().useful.bold +
' resume: '.useful + PATH.relative(process.cwd(), f ).useful.bold );
// 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;
return theFormat.gen.generate( rez, f, _opts );
}
// Otherwise this is either a) a JSON Resume theme or b) 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;
// JSON Resume themes have a 'render' method that needs to be called
if( theme.render ) {
var COPY = require('copy');
var globs = [ /*'**',*/ '*.css', '*.js', '*.png', '*.jpg', '*.gif', '*.bmp' ];
COPY.sync( globs , outFolder, {
cwd: theme.folder, nodir: true,
ignore: ['node_modules/','node_modules/**']
// rewrite: function(p1, p2) {
// return PATH.join(p2, p1);
// }
});
// Prevent JSON Resume theme .js from chattering (TODO: redirect IO)
var consoleLog = console.log;
console.log = function() { };
// Call the theme's render method
var rezDupe = rez.harden();
var rezHtml = theme.render( rezDupe );
// Turn logging back on
console.log = consoleLog;
// Unharden
rezHtml = rezHtml.replace( /@@@@~.+?~@@@@/g, function(val){
return MDIN( val.replace( /~@@@@/gm,'' ).replace( /@@@@~/gm,'' ) );
});
// Save the file
FS.writeFileSync( f, rezHtml );
// Return markup to the client
return rezHtml;
}
else {
return theFormat.gen.generate( rez, f, _opts );
}
}
}
catch( ex ) {
_err( ex );
}
}
/**
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 ) {
// 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
};
theTheme.formats.png = theTheme.formats.png ||
( theTheme.formats.html && {
freebie: true, title: 'png', outFormat: 'png',
ext: 'yml', path: null, data: null
});
// 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';
var explicitFormats = _.omit( theTheme.formats, function(val) {
return !val.freebie;
});
var implicitFormats = _.omit( theTheme.formats, function(val) {
return val.freebie;
});
targets.push.apply(
targets, fmat === '.all' ?
Object.keys( implicitFormats ).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 verify_theme( themeNameOrPath ) {
var tFolder = PATH.resolve(
__dirname,
'../../node_modules/fresh-themes/themes',
themeNameOrPath
);
var exists = require('path-exists').sync;
if( !exists( tFolder ) ) {
tFolder = PATH.resolve( themeNameOrPath );
if( !exists( tFolder ) ) {
throw { fluenterror: 1, data: _opts.theme };
}
}
return tFolder;
}
/**
Load the specified theme.
*/
function load_theme( tFolder ) {
// Create a FRESH or JRS theme object
var theTheme = _opts.theme.indexOf('jsonresume-theme-') > -1 ?
new JRSTheme().open(tFolder) : new FluentTheme().open( tFolder );
// Cache the theme object
_opts.themeObj = theTheme;
// Output a message TODO: core should not log
var numFormats = Object.keys(theTheme.formats).length;
_log( 'Applying '.info + theTheme.name.toUpperCase().infoBold +
(' theme (' + numFormats + ' formats)').info);
return theTheme;
}
module.exports = build;
}());