1
0
mirror of https://github.com/JuanCanham/HackMyResume.git synced 2024-09-29 04:29:12 +01:00
HackMyResume/src/fluentcmd.js

260 lines
8.2 KiB
JavaScript
Raw Normal View History

/**
2015-10-27 07:54:50 +00:00
Internal resume generation logic for FluentCV.
2015-11-19 06:57:15 +00:00
@license MIT. Copyright (c) 2015 James M. Devlin / FluentDesk
2015-11-21 10:56:16 +00:00
@module fluentcmd.js
*/
module.exports = function () {
2015-10-06 17:21:22 +01:00
// We don't mind pseudo-globals here
2015-10-06 17:24:29 +01:00
var path = require( 'path' )
, extend = require( './utils/extend' )
2015-09-26 20:05:37 +01:00
, unused = require('./utils/string')
, FS = require('fs')
, _ = require('underscore')
2015-10-26 16:30:00 +00:00
, FLUENT = require('./fluentlib')
, PATH = require('path')
, MKDIRP = require('mkdirp')
2015-11-21 10:56:16 +00:00
, COLORS = require('colors')
2015-10-07 14:29:41 +01:00
, rez, _log, _err;
/**
Given a source JSON resume, 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).
2015-10-10 20:39:13 +01:00
@param theme Friendly name of the resume theme. Defaults to "modern".
2015-10-06 17:21:22 +01:00
@param logger Optional logging override.
*/
2015-11-20 14:53:36 +00:00
function generate( src, dst, opts, logger, errHandler ) {
2015-09-29 09:15:04 +01:00
_log = logger || console.log;
2015-10-07 14:29:41 +01:00
_err = errHandler || error;
2015-10-26 06:45:37 +00:00
//_opts = extend( true, _opts, opts );
_opts.theme = (opts.theme && opts.theme.toLowerCase().trim()) || 'modern';
_opts.prettify = opts.prettify === true ? _opts.prettify : false;
// Load input resumes...
2015-10-07 14:29:41 +01:00
if(!src || !src.length) { throw { fluenterror: 3 }; }
2015-11-21 12:59:30 +00:00
var sheets = loadSourceResumes( src );
// Merge input resumes...
2015-10-07 14:29:41 +01:00
var msg = '';
rez = _.reduceRight( sheets, function( a, b, idx ) {
2015-11-21 10:56:16 +00:00
msg += ((idx == sheets.length - 2) ? 'Merging '.gray + a.imp.fileName : '')
+ ' onto '.gray + b.imp.fileName;
return extend( true, b, a );
});
msg && _log(msg);
// Verify the specified theme name/path
2015-11-20 14:53:36 +00:00
var relativeThemeFolder = '../node_modules/fluent-themes/themes';
var tFolder = PATH.resolve( __dirname, relativeThemeFolder, _opts.theme );
var exists = require('./utils/file-exists');
if (!exists( tFolder )) {
tFolder = PATH.resolve( _opts.theme );
if (!exists( tFolder )) {
throw { fluenterror: 1, data: _opts.theme };
}
}
2015-11-20 14:53:36 +00:00
// Load the theme
var theTheme = new FLUENT.Theme().open( tFolder );
_opts.themeObj = theTheme;
2015-11-21 10:56:16 +00:00
_log( 'Applying '.yellow + theTheme.name.toUpperCase().yellow.bold + (' theme (' +
Object.keys(theTheme.formats).length + ' formats)').yellow );
// Expand output resumes... (can't use map() here)
2015-11-20 14:53:36 +00:00
var targets = [], that = this;
( (dst && dst.length && dst) || ['resume.all'] ).forEach( function(t) {
2015-11-20 14:53:36 +00:00
var to = path.resolve(t),
pa = path.parse(to),
fmat = pa.ext || '.all';
targets.push.apply(targets, fmat === '.all' ?
2015-11-20 14:53:36 +00:00
Object.keys( theTheme.formats ).map(function(k){
var z = theTheme.formats[k];
return { file: to.replace(/all$/g,z.pre), fmt: z }
}) : [{ file: to, fmt: theTheme.getFormat( fmat.slice(1) ) }]);
});
// Run the transformation!
var finished = targets.map( function(t) { return single(t, theTheme); } );
// Don't send the client back empty-handed
return { sheet: rez, targets: targets, processed: finished };
}
/**
Generate a single resume of a specific format.
@param f Full path to the destination resume to generate, for example,
"/foo/bar/resume.pdf" or "c:\foo\bar\resume.txt".
*/
function single( fi, theme ) {
try {
2015-11-20 14:53:36 +00:00
var f = fi.file, fType = fi.fmt.ext, fName = path.basename(f,'.'+fType);
var fObj = _.property( fi.fmt.pre )( theme.formats );
2015-11-20 14:53:36 +00:00
var fOut = path.join( f.substring( 0, f.lastIndexOf('.')+1 ) + fObj.pre);
2015-11-21 10:56:16 +00:00
_log( 'Generating '.green + fi.fmt.title.toUpperCase().green.bold + ' resume: '.green +
path.relative(process.cwd(), f ).green.bold );
2015-11-20 14:53:36 +00:00
var theFormat = _fmts.filter(
function( fmt ) { return fmt.name === fi.fmt.pre; })[0];
2015-11-21 15:33:16 +00:00
MKDIRP.sync( path.dirname(fOut) ); // Ensure dest folder exists;
theFormat.gen.generate( rez, fOut, _opts );
}
catch( ex ) {
2015-10-07 14:29:41 +01:00
_err( ex );
}
}
/**
Handle an exception.
*/
2015-10-07 14:29:41 +01:00
function error( ex ) {
throw ex;
}
/**
Validate 1 to N resumes in either FRESH or JSON Resume format.
*/
function validate( src, unused, opts, logger ) {
_log = logger || console.log;
if( !src || !src.length ) { throw { fluenterror: 3 }; }
var isValid = true;
2015-11-21 10:56:16 +00:00
var validator = require('is-my-json-valid');
var schemas = {
fresh: require('FRESCA'),
jars: require('./core/resume.json')
};
2015-11-21 12:59:30 +00:00
// Load input resumes...
var sheets = loadSourceResumes(src, function( res ) {
try {
return {
file: res,
raw: FS.readFileSync( res, 'utf8' )
};
}
catch( ex ) {
throw ex;
}
});
sheets.forEach( function( rep ) {
2015-11-21 10:56:16 +00:00
try {
2015-11-21 12:59:30 +00:00
var rez = JSON.parse( rep.raw );
2015-11-21 10:56:16 +00:00
}
catch( ex ) {
2015-11-21 12:59:30 +00:00
_log('Validating '.gray + rep.file.cyan.bold + ' against FRESH/JRS schema: '.gray + 'ERROR!'.red.bold);
2015-11-21 10:56:16 +00:00
if (ex instanceof SyntaxError) {
// Invalid JSON
2015-11-21 12:59:30 +00:00
_log( '--> '.bold.red + rep.file.toUpperCase().red + ' contains invalid JSON. Unable to validate.'.red );
2015-11-21 10:56:16 +00:00
_log( (' INTERNAL: ' + ex).red );
}
else {
_log(('ERROR: ' + ex.toString()).red.bold);
}
return;
}
2015-11-21 10:56:16 +00:00
var fmt = rez.meta && rez.meta.format === 'FRESH@0.1.0' ? 'fresh':'jars';
2015-11-21 12:59:30 +00:00
process.stdout.write( 'Validating '.gray + rep.file + ' against '.gray +
fmt.replace('jars','JSON Resume').toUpperCase() + ' schema: '.gray );
2015-11-21 10:56:16 +00:00
var validate = validator( schemas[ fmt ], { // Note [1]
formats: { date: /^\d{4}(?:-(?:0[0-9]{1}|1[0-2]{1})(?:-[0-9]{2})?)?$/ }
});
var ret = validate( rez );
if( !ret ) {
rez.imp = rez.imp || { };
rez.imp.validationErrors = validate.errors;
_log('INVALID'.bold.yellow);
rez.imp.validationErrors.forEach(function(err,idx){
_log( '--> '.bold.yellow + ( err.field.replace('data.','resume.').toUpperCase()
+ ' ' + err.message).yellow );
});
}
else {
_log('VALID!'.bold.green);
}
});
}
2015-11-20 13:29:19 +00:00
/**
Convert between FRESH and JRS formats.
*/
function convert( src, dst, opts, logger ) {
_log = logger || console.log;
2015-11-21 15:33:16 +00:00
if( !src || !src.length ) { throw { fluenterror: 3 }; }
if( !dst || !dst.length ) { throw { fluenterror: 5 }; }
2015-11-21 12:59:30 +00:00
var sheet = loadSourceResumes( src )[ 0 ];
2015-11-20 14:53:36 +00:00
var sourceFormat = sheet.imp.orgFormat === 'JRS' ? 'JRS' : 'FRESH';
var targetFormat = sourceFormat === 'JRS' ? 'FRESH' : 'JRS';
2015-11-21 12:59:30 +00:00
_log( 'Converting '.gray + src[0] + (' (' + sourceFormat + ') to ').gray + dst[0] +
(' (' + targetFormat + ').').gray );
2015-11-20 14:53:36 +00:00
sheet.saveAs( dst[0], targetFormat );
2015-11-20 13:29:19 +00:00
}
2015-11-21 12:59:30 +00:00
function loadSourceResumes( src, fn ) {
return src.map( function( res ) {
_log( 'Reading '.gray + 'SOURCE' + ' resume: '.gray + res.cyan.bold );
return (fn && fn(res)) || (new FLUENT.FRESHResume()).open( res );
});
}
/**
Supported resume formats.
*/
var _fmts = [
{ name: 'html', ext: 'html', gen: new FLUENT.HtmlGenerator() },
{ name: 'txt', ext: 'txt', gen: new FLUENT.TextGenerator() },
{ name: 'doc', ext: 'doc', fmt: 'xml', gen: new FLUENT.WordGenerator() },
2015-10-10 22:49:29 +01:00
{ name: 'pdf', ext: 'pdf', fmt: 'html', is: false, gen: new FLUENT.HtmlPdfGenerator() },
{ name: 'md', ext: 'md', fmt: 'txt', gen: new FLUENT.MarkdownGenerator() },
2015-10-26 06:45:37 +00:00
{ name: 'json', ext: 'json', gen: new FLUENT.JsonGenerator() },
{ name: 'yml', ext: 'yml', fmt: 'yml', gen: new FLUENT.JsonYamlGenerator() }
];
/**
2015-11-05 05:56:41 +00:00
Default FluentCV options.
*/
var _opts = {
2015-10-10 20:39:13 +01:00
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
}
};
/**
Internal module interface. Used by FCV Desktop and HMR.
*/
return {
verbs: {
2015-11-21 15:33:16 +00:00
build: generate,
2015-11-20 13:29:19 +00:00
validate: validate,
convert: convert
},
2015-10-26 16:54:27 +00:00
lib: require('./fluentlib'),
options: _opts,
formats: _fmts
};
}();
// [1]: JSON.parse throws SyntaxError on invalid JSON. See:
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse