mirror of
https://github.com/JuanCanham/HackMyResume.git
synced 2025-05-10 07:47:07 +01:00
Rename src/eng --> src/renderers
A renderer is a thing that renders or "paints" an arbitrary format using a templating engine like Handlebars or Underscore. A generator is a thing responsible for generating a given output format like HTML or MS Word.
This commit is contained in:
290
src/renderers/generic-helpers.js
Normal file
290
src/renderers/generic-helpers.js
Normal file
@ -0,0 +1,290 @@
|
||||
/**
|
||||
Generic template helper definitions for HackMyResume / FluentCV.
|
||||
@license MIT. See LICENSE.md for details.
|
||||
@module generic-helpers.js
|
||||
*/
|
||||
|
||||
|
||||
(function() {
|
||||
|
||||
var MD = require('marked')
|
||||
, H2W = require('../utils/html-to-wpml')
|
||||
, XML = require('xml-escape')
|
||||
, moment = require('moment')
|
||||
, LO = require('lodash')
|
||||
, _ = require('underscore')
|
||||
, unused = require('../utils/string');
|
||||
|
||||
/**
|
||||
Generic template helper function definitions.
|
||||
@class GenericHelpers
|
||||
*/
|
||||
var GenericHelpers = module.exports = {
|
||||
|
||||
/**
|
||||
Convert the input date to a specified format through Moment.js.
|
||||
If date is invalid, will return the time provided by the user,
|
||||
or default to the fallback param or 'Present' if that is set to true
|
||||
@method formatDate
|
||||
*/
|
||||
formatDate: function(datetime, format, fallback) {
|
||||
if (moment) {
|
||||
var momentDate = moment( datetime );
|
||||
if (momentDate.isValid()) return momentDate.format(format);
|
||||
}
|
||||
|
||||
return datetime || (typeof fallback == 'string' ? fallback : (fallback === true ? 'Present' : null));
|
||||
},
|
||||
|
||||
/**
|
||||
Format a from/to date range.
|
||||
@method dateRange
|
||||
*/
|
||||
dateRange: function( obj, fmt, sep, options ) {
|
||||
fmt = (fmt && String.is(fmt) && fmt) || 'YYYY-MM';
|
||||
sep = (sep && String.is(sep) && sep) || ' — ';
|
||||
if( obj.safe ) {
|
||||
var dateA = (obj.safe.start && obj.safe.start.format(fmt)) || '';
|
||||
var dateB = (obj.safe.end && obj.safe.end.format(fmt)) || '';
|
||||
if( obj.safe.start && obj.safe.end ) {
|
||||
return dateA + sep + dateB ;
|
||||
}
|
||||
else if( obj.safe.start || obj.safe.end ) {
|
||||
return dateA || dateB;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
},
|
||||
|
||||
/**
|
||||
Return true if the section is present on the resume and has at least one
|
||||
element.
|
||||
@method section
|
||||
*/
|
||||
section: function( title, options ) {
|
||||
title = title.trim().toLowerCase();
|
||||
var obj = LO.get( this.r, title );
|
||||
if( _.isArray( obj ) ) {
|
||||
return obj.length ? options.fn(this) : undefined;
|
||||
}
|
||||
else if( _.isObject( obj )) {
|
||||
return ( (obj.history && obj.history.length) ||
|
||||
( obj.sets && obj.sets.length ) ) ?
|
||||
options.fn(this) : undefined;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
Capitalize the first letter of the word.
|
||||
@method section
|
||||
*/
|
||||
camelCase: function(val) {
|
||||
val = (val && val.trim()) || '';
|
||||
return val ? (val.charAt(0).toUpperCase() + val.slice(1)) : val;
|
||||
},
|
||||
|
||||
/**
|
||||
Return true if the context has the property or subpropery.
|
||||
@method has
|
||||
*/
|
||||
has: function( title, options ) {
|
||||
title = title && title.trim().toLowerCase();
|
||||
if( LO.get( this.r, title ) ) {
|
||||
return options.fn(this);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
Generic template helper function to display a user-overridable section
|
||||
title for a FRESH resume theme. Use this in lieue of hard-coding section
|
||||
titles.
|
||||
|
||||
Usage:
|
||||
|
||||
{{sectionTitle "sectionName"}}
|
||||
{{sectionTitle "sectionName" "sectionTitle"}}
|
||||
|
||||
Example:
|
||||
|
||||
{{sectionTitle "Education"}}
|
||||
{{sectionTitle "Employment" "Project History"}}
|
||||
|
||||
@param sect_name The name of the section being title. Must be one of the
|
||||
top-level FRESH resume sections ("info", "education", "employment", etc.).
|
||||
@param sect_title The theme-specified section title. May be replaced by the
|
||||
user.
|
||||
@method sectionTitle
|
||||
*/
|
||||
sectionTitle: function( sname, stitle ) {
|
||||
|
||||
// If not provided by the user, stitle should default to sname. ps.
|
||||
// Handlebars silently passes in the options object to the last param,
|
||||
// where in Underscore stitle will be null/undefined, so we check both.
|
||||
stitle = (stitle && String.is(stitle) && stitle) || sname;
|
||||
|
||||
// If there's a section title override, use it.
|
||||
return ( this.opts.stitles &&
|
||||
this.opts.stitles[ sname.toLowerCase().trim() ] ) ||
|
||||
stitle;
|
||||
},
|
||||
|
||||
/**
|
||||
Convert inline Markdown to inline WordProcessingML.
|
||||
@method wpml
|
||||
*/
|
||||
wpml: function( txt, inline ) {
|
||||
if(!txt) return '';
|
||||
inline = (inline && !inline.hash) || false;
|
||||
txt = XML(txt.trim());
|
||||
txt = inline ?
|
||||
MD(txt).replace(/^\s*<p>|<\/p>\s*$/gi, '') :
|
||||
MD(txt);
|
||||
txt = H2W( txt );
|
||||
return txt;
|
||||
},
|
||||
|
||||
/**
|
||||
Emit a conditional link.
|
||||
@method link
|
||||
*/
|
||||
link: function( text, url ) {
|
||||
return url && url.trim() ?
|
||||
('<a href="' + url + '">' + text + '</a>') : text;
|
||||
},
|
||||
|
||||
/**
|
||||
Return the last word of the specified text.
|
||||
@method lastWord
|
||||
*/
|
||||
lastWord: function( txt ) {
|
||||
return txt && txt.trim() ? _.last( txt.split(' ') ) : '';
|
||||
},
|
||||
|
||||
/**
|
||||
Convert a skill level to an RGB color triplet. TODO: refactor
|
||||
@method skillColor
|
||||
@param lvl Input skill level. Skill level can be expressed as a string
|
||||
("beginner", "intermediate", etc.), as an integer (1,5,etc), as a string
|
||||
integer ("1", "5", etc.), or as an RRGGBB color triplet ('#C00000',
|
||||
'#FFFFAA').
|
||||
*/
|
||||
skillColor: function( lvl ) {
|
||||
var idx = skillLevelToIndex( lvl );
|
||||
var skillColors = (this.theme && this.theme.palette &&
|
||||
this.theme.palette.skillLevels) ||
|
||||
[ '#FFFFFF', '#5CB85C', '#F1C40F', '#428BCA', '#C00000' ];
|
||||
return skillColors[idx];
|
||||
},
|
||||
|
||||
/**
|
||||
Return an appropriate height. TODO: refactor
|
||||
@method lastWord
|
||||
*/
|
||||
skillHeight: function( lvl ) {
|
||||
var idx = skillLevelToIndex( lvl );
|
||||
return ['38.25', '30', '16', '8', '0'][idx];
|
||||
},
|
||||
|
||||
/**
|
||||
Return all but the last word of the input text.
|
||||
@method initialWords
|
||||
*/
|
||||
initialWords: function( txt ) {
|
||||
return txt && txt.trim() ? _.initial( txt.split(' ') ).join(' ') : '';
|
||||
},
|
||||
|
||||
/**
|
||||
Trim the protocol (http or https) from a URL/
|
||||
@method trimURL
|
||||
*/
|
||||
trimURL: function( url ) {
|
||||
return url && url.trim() ? url.trim().replace(/^https?:\/\//i, '') : '';
|
||||
},
|
||||
|
||||
/**
|
||||
Convert text to lowercase.
|
||||
@method toLower
|
||||
*/
|
||||
toLower: function( txt ) {
|
||||
return txt && txt.trim() ? txt.toLowerCase() : '';
|
||||
},
|
||||
|
||||
/**
|
||||
Return true if either value is truthy.
|
||||
@method either
|
||||
*/
|
||||
either: function( lhs, rhs, options ) {
|
||||
if (lhs || rhs) return options.fn(this);
|
||||
},
|
||||
|
||||
/**
|
||||
Conditional stylesheet link. Either display the link or embed the stylesheet
|
||||
via <style></style> tag.
|
||||
*/
|
||||
styleSheet: function( file, options ) {
|
||||
var styles = ( this.opts.css === 'link') ?
|
||||
'<link href="' + file + '" rel="stylesheet" type="text/css">' :
|
||||
'<style>' + this.cssInfo.data + '</style>';
|
||||
if( this.opts.themeObj.inherits &&
|
||||
this.opts.themeObj.inherits.html &&
|
||||
this.format === 'html' ) {
|
||||
styles += (this.opts.css === 'link') ?
|
||||
'<link href="' + this.opts.themeObj.overrides.path + '" rel="stylesheet" type="text/css">' :
|
||||
'<style>' + this.opts.themeObj.overrides.data + '</style>';
|
||||
}
|
||||
return styles;
|
||||
},
|
||||
|
||||
/**
|
||||
Perform a generic comparison.
|
||||
See: http://doginthehat.com.au/2012/02/comparison-block-helper-for-handlebars-templates
|
||||
@method compare
|
||||
*/
|
||||
compare: function(lvalue, rvalue, options) {
|
||||
if (arguments.length < 3)
|
||||
throw new Error("Handlerbars Helper 'compare' needs 2 parameters");
|
||||
var operator = options.hash.operator || "==";
|
||||
var operators = {
|
||||
'==': function(l,r) { return l == r; },
|
||||
'===': function(l,r) { return l === r; },
|
||||
'!=': function(l,r) { return l != r; },
|
||||
'<': function(l,r) { return l < r; },
|
||||
'>': function(l,r) { return l > r; },
|
||||
'<=': function(l,r) { return l <= r; },
|
||||
'>=': function(l,r) { return l >= r; },
|
||||
'typeof': function(l,r) { return typeof l == r; }
|
||||
};
|
||||
if (!operators[operator])
|
||||
throw new Error("Handlerbars Helper 'compare' doesn't know the operator "+operator);
|
||||
var result = operators[operator](lvalue,rvalue);
|
||||
return result ? options.fn(this) : options.inverse(this);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
function skillLevelToIndex( lvl ) {
|
||||
var idx = 0;
|
||||
if( String.is( lvl ) ) {
|
||||
lvl = lvl.trim().toLowerCase();
|
||||
var intVal = parseInt( lvl );
|
||||
if( isNaN( intVal ) ) {
|
||||
switch( lvl ) {
|
||||
case 'beginner': idx = 1; break;
|
||||
case 'intermediate': idx = 2; break;
|
||||
case 'advanced': idx = 3; break;
|
||||
case 'master': idx = 4; break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
idx = Math.min( intVal / 2, 4 );
|
||||
idx = Math.max( 0, idx );
|
||||
}
|
||||
}
|
||||
else {
|
||||
idx = Math.min( lvl / 2, 4 );
|
||||
idx = Math.max( 0, idx );
|
||||
}
|
||||
return idx;
|
||||
}
|
||||
|
||||
}());
|
99
src/renderers/handlebars-generator.js
Normal file
99
src/renderers/handlebars-generator.js
Normal file
@ -0,0 +1,99 @@
|
||||
/**
|
||||
Definition of the HandlebarsGenerator class.
|
||||
@license MIT. See LICENSE.md for details.
|
||||
@module handlebars-generator.js
|
||||
*/
|
||||
|
||||
|
||||
|
||||
(function() {
|
||||
|
||||
|
||||
|
||||
var _ = require('underscore')
|
||||
, HANDLEBARS = require('handlebars')
|
||||
, FS = require('fs')
|
||||
, registerHelpers = require('./handlebars-helpers')
|
||||
, PATH = require('path')
|
||||
, parsePath = require('parse-filepath')
|
||||
, READFILES = require('recursive-readdir-sync')
|
||||
, SLASH = require('slash');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
Perform template-based resume generation using Handlebars.js.
|
||||
@class HandlebarsGenerator
|
||||
*/
|
||||
var HandlebarsGenerator = module.exports = {
|
||||
|
||||
|
||||
|
||||
|
||||
generate: function( json, jst, format, cssInfo, opts, theme ) {
|
||||
|
||||
registerPartials( format, theme );
|
||||
registerHelpers( theme );
|
||||
|
||||
// Preprocess text
|
||||
var encData = json;
|
||||
( format === 'html' || format === 'pdf' ) && (encData = json.markdownify());
|
||||
( format === 'doc' ) && (encData = json.xmlify());
|
||||
|
||||
// Compile and run the Handlebars template.
|
||||
var template = HANDLEBARS.compile(jst);
|
||||
return template({
|
||||
r: encData,
|
||||
RAW: json,
|
||||
filt: opts.filters,
|
||||
cssInfo: cssInfo,
|
||||
format: format,
|
||||
opts: opts,
|
||||
headFragment: opts.headFragment || ''
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
function registerPartials(format, theme) {
|
||||
if( format !== 'html' && format != 'doc' )
|
||||
return;
|
||||
|
||||
// Locate the global partials folder
|
||||
var partialsFolder = PATH.join(
|
||||
parsePath( require.resolve('fresh-themes') ).dirname,
|
||||
'/partials/',
|
||||
format
|
||||
);
|
||||
|
||||
// Register global partials in the /partials folder
|
||||
// TODO: Only do this once per HMR invocation.
|
||||
_.each( READFILES( partialsFolder, function(error){ }), function( el ) {
|
||||
var pathInfo = parsePath( el );
|
||||
var name = SLASH( PATH.relative( partialsFolder, el )
|
||||
.replace(/\.html$|\.xml$/, '') );
|
||||
if( pathInfo.dirname.endsWith('section') ) {
|
||||
name = SLASH(name.replace(/\.html$|\.xml$/, ''));
|
||||
}
|
||||
var tplData = FS.readFileSync( el, 'utf8' );
|
||||
var compiledTemplate = HANDLEBARS.compile( tplData );
|
||||
HANDLEBARS.registerPartial( name, compiledTemplate );
|
||||
theme.partialsInitialized = true;
|
||||
});
|
||||
|
||||
// Register theme-specific partials
|
||||
_.each( theme.partials, function( el ) {
|
||||
var tplData = FS.readFileSync( el.path, 'utf8' );
|
||||
var compiledTemplate = HANDLEBARS.compile( tplData );
|
||||
HANDLEBARS.registerPartial( el.name, compiledTemplate );
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
}());
|
25
src/renderers/handlebars-helpers.js
Normal file
25
src/renderers/handlebars-helpers.js
Normal file
@ -0,0 +1,25 @@
|
||||
/**
|
||||
Template helper definitions for Handlebars.
|
||||
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
|
||||
@module handlebars-helpers.js
|
||||
*/
|
||||
|
||||
|
||||
(function() {
|
||||
|
||||
var HANDLEBARS = require('handlebars')
|
||||
, _ = require('underscore')
|
||||
, helpers = require('./generic-helpers');
|
||||
|
||||
/**
|
||||
Register useful Handlebars helpers.
|
||||
@method registerHelpers
|
||||
*/
|
||||
module.exports = function( theme ) {
|
||||
|
||||
helpers.theme = theme;
|
||||
HANDLEBARS.registerHelper( helpers );
|
||||
|
||||
};
|
||||
|
||||
}());
|
76
src/renderers/jrs-generator.js
Normal file
76
src/renderers/jrs-generator.js
Normal file
@ -0,0 +1,76 @@
|
||||
/**
|
||||
Definition of the JRSGenerator class.
|
||||
@license MIT. See LICENSE.md for details.
|
||||
@module jrs-generator.js
|
||||
*/
|
||||
|
||||
|
||||
|
||||
(function() {
|
||||
|
||||
|
||||
|
||||
var _ = require('underscore')
|
||||
, HANDLEBARS = require('handlebars')
|
||||
, FS = require('fs')
|
||||
, registerHelpers = require('./handlebars-helpers')
|
||||
, PATH = require('path')
|
||||
, parsePath = require('parse-filepath')
|
||||
, READFILES = require('recursive-readdir-sync')
|
||||
, SLASH = require('slash')
|
||||
, MD = require('marked');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
Perform template-based resume generation for JSON Resume themes.
|
||||
@class JRSGenerator
|
||||
*/
|
||||
var JRSGenerator = module.exports = {
|
||||
|
||||
|
||||
|
||||
|
||||
generate: function( json, jst, format, cssInfo, opts, theme ) {
|
||||
|
||||
// JSON Resume themes don't have a specific structure, so the safest thing
|
||||
// to do is copy all files from source to dest.
|
||||
// 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);
|
||||
// // }
|
||||
// });
|
||||
|
||||
// Disable JRS theme chatter (console.log, console.error, etc.)
|
||||
var off = ['log', 'error', 'dir'], org = off.map(function(c){
|
||||
var ret = console[c]; console[c] = function(){}; return ret;
|
||||
});
|
||||
|
||||
// Freeze and render
|
||||
var rezHtml = theme.render( json.harden() );
|
||||
|
||||
// Turn logging back on
|
||||
off.forEach(function(c, idx){ console[c] = org[idx]; });
|
||||
|
||||
// Unfreeze and apply Markdown
|
||||
rezHtml = rezHtml.replace( /@@@@~.*?~@@@@/gm, function(val){
|
||||
return MDIN( val.replace( /~@@@@/gm,'' ).replace( /@@@@~/gm,'' ) );
|
||||
});
|
||||
|
||||
return rezHtml;
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
function MDIN(txt) { // TODO: Move this
|
||||
return MD(txt || '' ).replace(/^\s*<p>|<\/p>\s*$/gi, '');
|
||||
}
|
||||
|
||||
|
||||
}());
|
57
src/renderers/underscore-generator.js
Normal file
57
src/renderers/underscore-generator.js
Normal file
@ -0,0 +1,57 @@
|
||||
/**
|
||||
Definition of the UnderscoreGenerator class.
|
||||
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
|
||||
@module underscore-generator.js
|
||||
*/
|
||||
|
||||
(function() {
|
||||
|
||||
|
||||
|
||||
var _ = require('underscore');
|
||||
|
||||
|
||||
/**
|
||||
Perform template-based resume generation using Underscore.js.
|
||||
@class UnderscoreGenerator
|
||||
*/
|
||||
var UnderscoreGenerator = module.exports = {
|
||||
|
||||
generate: function( json, jst, format, cssInfo, opts, theme ) {
|
||||
|
||||
// Tweak underscore's default template delimeters
|
||||
var delims = (opts.themeObj && opts.themeObj.delimeters) || opts.template;
|
||||
if( opts.themeObj && opts.themeObj.delimeters ) {
|
||||
delims = _.mapObject( delims, function(val,key) {
|
||||
return new RegExp( val, "ig");
|
||||
});
|
||||
}
|
||||
_.templateSettings = delims;
|
||||
|
||||
// Strip {# comments #}
|
||||
jst = jst.replace( delims.comment, '');
|
||||
|
||||
var helpers = require('./generic-helpers');
|
||||
helpers.opts = opts;
|
||||
helpers.cssInfo = cssInfo;
|
||||
|
||||
// Compile and run the template. TODO: avoid unnecessary recompiles.
|
||||
var compiled = _.template(jst);
|
||||
var ret = compiled({
|
||||
r: format === 'html' || format === 'pdf' || format === 'png' ? json.markdownify() : json,
|
||||
filt: opts.filters,
|
||||
XML: require('xml-escape'),
|
||||
RAW: json,
|
||||
cssInfo: cssInfo,
|
||||
headFragment: opts.headFragment || '',
|
||||
opts: opts,
|
||||
h: helpers
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
}());
|
Reference in New Issue
Block a user