mirror of
https://github.com/JuanCanham/HackMyResume.git
synced 2025-04-19 22:30:26 +01:00
317 lines
9.6 KiB
JavaScript
317 lines
9.6 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')
|
|
, extend = require('../utils/extend')
|
|
, chalk = require('chalk')
|
|
, pad = require('string-padding')
|
|
, _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
|
|
//_opts = extend( true, _opts, opts );
|
|
_log = logger || console.log;
|
|
_err = errHandler || error;
|
|
_opts.theme = (opts.theme && opts.theme.toLowerCase().trim())|| 'modern';
|
|
_opts.prettify = opts.prettify === true ? _opts.prettify : false;
|
|
_opts.css = opts.css;
|
|
_opts.pdf = opts.pdf;
|
|
_opts.wrap = opts.wrap || 60;
|
|
|
|
// If two or more files are passed to the GENERATE command and the TO
|
|
// keyword is omitted, the last file specifies the output file.
|
|
if( src.length > 1 && ( !dst || !dst.length ) ) {
|
|
dst.push( src.pop() );
|
|
}
|
|
|
|
// Load the theme...we do this first because the theme choice (FRESH or
|
|
// JSON Resume) determines what format we'll convert the resume to.
|
|
var tFolder = verify_theme( _opts.theme );
|
|
var theme = load_theme( tFolder );
|
|
|
|
// Load input resumes...
|
|
if( !src || !src.length ) { throw { fluenterror: 3 }; }
|
|
var sheets = ResumeFactory.load(src, {
|
|
log: _log, format: theme.render ? 'JRS' : 'FRESH',
|
|
objectify: true, throw: true
|
|
}).map(function(sh){ return sh.rez; });
|
|
|
|
// Merge input resumes...
|
|
var msg = '';
|
|
rez = _.reduceRight( sheets, function( a, b, idx ) {
|
|
msg += ((idx == sheets.length - 2) ?
|
|
chalk.cyan('Merging ') + chalk.cyan.bold(a.imp().file) : '') +
|
|
chalk.cyan(' onto ') + chalk.cyan.bold(b.imp().file);
|
|
return extend( true, b, a );
|
|
});
|
|
msg && _log(msg);
|
|
|
|
// Output theme messages
|
|
var numFormats = Object.keys(theme.formats).length;
|
|
var themeName = theme.name.toUpperCase();
|
|
_log( chalk.yellow('Applying ') + chalk.yellow.bold(themeName) +
|
|
chalk.yellow(' theme (' + numFormats + ' format' + ( numFormats === 1 ? ')' : 's)') ));
|
|
|
|
// Expand output resumes...
|
|
var targets = expand( dst, theme );
|
|
|
|
// Run the transformation!
|
|
targets.forEach( function(t) {
|
|
t.final = single( t, theme, targets );
|
|
});
|
|
|
|
if( theme.message ) {
|
|
var WRAP = require('word-wrap');
|
|
_log( WRAP( chalk.gray('The ' + themeName +
|
|
' theme says: "') + chalk.white(theme.message) + chalk.gray('"'),
|
|
{ width: _opts.wrap, indent: '' } ));
|
|
}
|
|
|
|
// 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;
|
|
|
|
var suffix = '';
|
|
if( targInfo.fmt.outFormat === 'pdf' && _opts.pdf ) {
|
|
suffix = chalk.green(' (with ' + _opts.pdf + ')');
|
|
}
|
|
|
|
_log( chalk.green('Generating ') +
|
|
chalk.green.bold(pad(targInfo.fmt.outFormat.toUpperCase(),4,null,pad.RIGHT)) +
|
|
chalk.green(' resume') + suffix + chalk.green(': ') +
|
|
chalk.green.bold( 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;
|
|
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( /@@@@~.*?~@@@@/gm, 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
|
|
};
|
|
if( theTheme.formats.html && !theTheme.formats.png ) {
|
|
theTheme.formats.png = {
|
|
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, key) {
|
|
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;
|
|
|
|
return theTheme;
|
|
}
|
|
|
|
|
|
|
|
module.exports = build;
|
|
|
|
|
|
|
|
}());
|