1
0
mirror of https://github.com/JuanCanham/HackMyResume.git synced 2025-05-12 00:27:08 +01:00

Compare commits

...

19 Commits

Author SHA1 Message Date
786b3fd3b2 Merge pull request #25 from fluentdesk/v0.11.0
v0.11.0
2015-12-19 08:58:59 -05:00
f0a22be731 Skip underscore-prefixed folders during theme load. 2015-12-19 08:39:36 -05:00
ade60022fd Introduce new helpers. 2015-12-19 00:24:21 -05:00
7daba910ed Bump fluent-themes version. 2015-12-18 17:27:40 -05:00
a016d6d91a Update README. 2015-12-18 17:23:40 -05:00
fcaa97ed35 Change author info. 2015-12-18 15:35:00 -05:00
bb7373a229 Remove duplicate is-my-json-valid dependency. 2015-12-18 15:34:52 -05:00
759dcc30e7 Update tests. 2015-12-18 15:34:30 -05:00
0e47f02a33 Rename tests/jane-doe.json. 2015-12-18 15:34:00 -05:00
5fe90517e7 Auto-clean tests folder. 2015-12-18 15:33:18 -05:00
92128da381 Install URL-trimming helper. 2015-12-18 14:51:51 -05:00
1441fe3ae5 Class-ify Underscore/Handlebars engine. 2015-12-18 13:17:07 -05:00
b0bc71cd66 Introduce "either" helper for Handlebars themes. 2015-12-18 13:00:47 -05:00
e908e8bb34 Add missing Underscore require(). 2015-12-18 10:13:50 -05:00
d708a6c6d8 Refactor Handlebars helpers. 2015-12-18 10:10:30 -05:00
a630741098 Fix exception. 2015-12-18 10:10:19 -05:00
01d148e47c Bump version to 0.11.0. 2015-12-18 10:08:52 -05:00
dbd41ec439 Bump FRESCA version. 2015-12-17 14:28:24 -05:00
fc9cbab974 Choose template engine from active theme. 2015-12-17 11:04:29 -05:00
14 changed files with 276 additions and 140 deletions

View File

@ -17,6 +17,8 @@ module.exports = function (grunt) {
all: { src: ['tests/*.js'] }
},
clean: ['tests/sandbox'],
yuidoc: {
compile: {
name: '<%= pkg.name %>',
@ -46,9 +48,10 @@ module.exports = function (grunt) {
grunt.loadNpmTasks('grunt-simple-mocha');
grunt.loadNpmTasks('grunt-contrib-yuidoc');
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.registerTask('test', 'Test the FluentCV library.',
function( config ) { grunt.task.run( ['simplemocha:all'] ); });
function( config ) { grunt.task.run( ['clean','simplemocha:all'] ); });
grunt.registerTask('document', 'Generate FluentCV library documentation.',
function( config ) { grunt.task.run( ['yuidoc'] ); });
grunt.registerTask('default', [ 'jshint', 'test', 'yuidoc' ]);

View File

@ -1,12 +1,13 @@
fluentCV
========
*Create polished technical résumés and CVs in multiple formats from your command
line or shell. See [FluentCV Desktop][7] for the desktop version. OS X ~ Windows
~ Linux.*
*Create polished résumés and CVs in multiple formats from your command line or
shell. Author in clean Markdown and JSON, export to Word, HTML, PDF, LaTeX,
plain text, and other arbitrary formats.*
![](assets/fluentcv_cli_ubuntu.png)
![](assets/resume-bouqet.png)
FluentCV is a dev-friendly Swiss Army knife for resumes and CVs. Use it to:
FluentCV is a dev-friendly, local-only Swiss Army knife for resumes and CVs. Use
it to:
1. **Generate** HTML, Markdown, LaTeX, MS Word, PDF, plain text, JSON, XML,
YAML, print, smoke signal, carrier pigeon, and other arbitrary-format resumes
@ -14,7 +15,10 @@ and CVs, from a single source of truth&mdash;without violating DRY.
2. **Convert** resumes between [FRESH][fresca] and [JSON Resume][6] formats.
3. **Validate** resumes against either format.
FluentCV supports both the [FRESH][fresca] and [JSON Resume][6] source formats.
FluentCV is built with Node.js and runs on recent versions of OS X, Linux, or
Windows.
![](assets/fluentcv_cli_ubuntu.png)
## Features

BIN
assets/resume-bouqet.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

View File

@ -1,6 +1,6 @@
{
"name": "fluentcv",
"version": "0.10.3",
"version": "0.11.0",
"description": "Generate polished résumés and CVs in HTML, Markdown, LaTeX, MS Word, PDF, plain text, JSON, XML, YAML, smoke signal, and carrier pigeon.",
"repository": {
"type": "git",
@ -20,7 +20,7 @@
"HTML",
"CLI"
],
"author": "James M. Devlin",
"author": "hacksalot <hacksalot@fluentdesk.com> (https://github.com/hacksalot)",
"license": "MIT",
"preferGlobal": "true",
"bugs": {
@ -33,8 +33,8 @@
"homepage": "https://github.com/fluentdesk/fluentcv",
"dependencies": {
"colors": "^1.1.2",
"fluent-themes": "~0.6.3-beta",
"fresca": "~0.2.1",
"fluent-themes": "~0.7.0-beta",
"fresca": "~0.2.2",
"fs-extra": "^0.24.0",
"handlebars": "^4.0.5",
"html": "0.0.10",
@ -54,10 +54,11 @@
"devDependencies": {
"chai": "*",
"grunt": "*",
"grunt-contrib-clean": "^0.7.0",
"grunt-contrib-jshint": "^0.11.3",
"grunt-contrib-yuidoc": "^0.10.0",
"grunt-simple-mocha": "*",
"is-my-json-valid": "^2.12.2",
"jane-q-fullstacker": "fluentdesk/jane-q-fullstacker",
"mocha": "*",
"resample": "fluentdesk/resample"
}

View File

@ -100,6 +100,7 @@ Definition of the Theme class.
var outFmt = '', isMajor = false;
var portion = pathInfo.dir.replace(tplFolder,'');
if( portion && portion.trim() ) {
if( portion[1] === '_' ) return;
var reg = /^(?:\/|\\)(html|latex|doc|pdf|partials)(?:\/|\\)?/ig;
var res = reg.exec( portion );
if( res ) {
@ -152,7 +153,7 @@ Definition of the Theme class.
.forEach(function( cssf ) {
// For each CSS file, get its corresponding HTML file
var idx = _.findIndex(fmts, function( fmt ) {
return fmt.pre === cssf.pre && fmt.ext === 'html';
return fmt && fmt.pre === cssf.pre && fmt.ext === 'html';
});
cssf.action = null;
fmts[ idx ].css = cssf.data;

View File

@ -11,93 +11,40 @@ Definition of the HandlebarsGenerator class.
var _ = require('underscore')
, HANDLEBARS = require('handlebars')
, FS = require('fs')
, moment = require('moment')
, MD = require('marked')
, H2W = require('../utils/html-to-wpml');
, registerHelpers = require('./handlebars-helpers');
/**
Perform template-based resume generation using Handlebars.js.
@method generate
@class HandlebarsGenerator
*/
module.exports = function( json, jst, format, cssInfo, opts, theme ) {
var HandlebarsGenerator = module.exports = {
// Pre-compile any partials present in the theme.
_.each( theme.partials, function( el ) {
var tplData = FS.readFileSync( el.path, 'utf8' );
var compiledTemplate = HANDLEBARS.compile( tplData );
HANDLEBARS.registerPartial( el.name, compiledTemplate );
});
generate: function( json, jst, format, cssInfo, opts, theme ) {
// Register necessary helpers.
registerHelpers();
// Pre-compile any partials present in the theme.
_.each( theme.partials, function( el ) {
var tplData = FS.readFileSync( el.path, 'utf8' );
var compiledTemplate = HANDLEBARS.compile( tplData );
HANDLEBARS.registerPartial( el.name, compiledTemplate );
});
// Compile and run the Handlebars template.
var template = HANDLEBARS.compile(jst);
return template({
r: json,
filt: opts.filters,
cssInfo: cssInfo,
headFragment: opts.headFragment || ''
});
// Register necessary helpers.
registerHelpers();
// Compile and run the Handlebars template.
var template = HANDLEBARS.compile(jst);
return template({
r: format === 'html' || format === 'pdf' ? json.markdownify() : json,
RAW: json,
filt: opts.filters,
cssInfo: cssInfo,
headFragment: opts.headFragment || ''
});
}
};
/**
Register useful Handlebars helpers.
@method registerHelpers
*/
function registerHelpers() {
// Set up a date formatting helper so we can do:
// {{#formatDate val 'YYYY-MM'}}
HANDLEBARS.registerHelper("formatDate", function(datetime, format) {
if( moment ) {
return moment( datetime ).format( format );
}
else {
return datetime;
}
});
// Set up a Markdown-to-WordProcessingML helper so we can do:
// {{#wmpl val [true|false]}}
HANDLEBARS.registerHelper("wpml", function( txt, inline ) {
inline = (inline && !inline.hash) || false;
txt = inline ?
MD(txt.trim()).replace(/^\s*<p>|<\/p>\s*$/gi, '') :
MD(txt.trim());
txt = H2W( txt.trim() );
return txt;
});
// Set up a generic conditional helper so we can do:
// {{#compare val otherVal operator="<"}}
// http://doginthehat.com.au/2012/02/comparison-block-helper-for-handlebars-templates/
HANDLEBARS.registerHelper('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);
});
}
}());

View File

@ -0,0 +1,124 @@
/**
Template helper definitions for Handlebars.
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
@module handlebars-helpers.js
*/
(function() {
var HANDLEBARS = require('handlebars')
, MD = require('marked')
, H2W = require('../utils/html-to-wpml')
, moment = require('moment')
, _ = require('underscore');
/**
Register useful Handlebars helpers.
@method registerHelpers
*/
module.exports = function() {
// Set up a date formatting helper so we can do:
// {{formatDate val 'YYYY-MM'}}
HANDLEBARS.registerHelper("formatDate", function(datetime, format) {
return moment ? moment( datetime ).format( format ) : datetime;
});
// Set up a Markdown-to-WordProcessingML helper so we can do:
// {{wmpl val [true|false]}}
HANDLEBARS.registerHelper("wpml", function( txt, inline ) {
if(!txt) return '';
inline = (inline && !inline.hash) || false;
txt = inline ?
MD(txt.trim()).replace(/^\s*<p>|<\/p>\s*$/gi, '') :
MD(txt.trim());
txt = H2W( txt.trim() );
return txt;
});
// Set up a last-word helper so we can do:
// {{lastWord val [true|false]}}
HANDLEBARS.registerHelper("link", function( text, url ) {
return url && url.trim() ?
('<a href="' + url + '">' + text + '</a>') : text;
});
// Set up a last-word helper so we can do:
// {{lastWord val [true|false]}}
HANDLEBARS.registerHelper("lastWord", function( txt ) {
return txt && txt.trim() ? _.last( txt.split(' ') ) : '';
});
// Set up a skill colorizing helper:
// {{skillColor val}}
HANDLEBARS.registerHelper("skillColor", function( lvl ) {
switch(lvl) {
case 'beginner': return '#5CB85C';
case 'intermediate': return '#F1C40F';
case 'advanced': return '#428BCA';
case 'master': return '#C00000';
}
});
// Set up a skill colorizing helper:
// {{skillColor val}}
HANDLEBARS.registerHelper("skillHeight", function( lvl ) {
switch(lvl) {
case 'beginner': return '30';
case 'intermediate': return '16';
case 'advanced': return '8';
case 'master': return '0';
}
});
// Set up a Markdown-to-WordProcessingML helper so we can do:
// {{initialWords val [true|false]}}
HANDLEBARS.registerHelper("initialWords", function( txt ) {
return txt && txt.trim() ? _.initial( txt.split(' ') ).join(' ') : '';
});
// Set up a URL-trimming helper to drop the protocol so we can do:
// {{trimURL url}}
HANDLEBARS.registerHelper("trimURL", function( url ) {
return url && url.trim() ? url.trim().replace(/^https?:\/\//i, '') : '';
});
// Set up a URL-trimming helper to drop the protocol so we can do:
// {{trimURL url}}
HANDLEBARS.registerHelper("toLower", function( txt ) {
return txt && txt.trim() ? txt.toLowerCase() : '';
});
// Set up a Markdown-to-WordProcessingML helper so we can do:
// {{either A B}}
HANDLEBARS.registerHelper("either", function( lhs, rhs, options ) {
if (lhs || rhs) return options.fn(this);
});
// Set up a generic conditional helper so we can do:
// {{compare val otherVal operator="<"}}
// http://doginthehat.com.au/2012/02/comparison-block-helper-for-handlebars-templates/
HANDLEBARS.registerHelper('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);
});
};
}());

View File

@ -1,37 +1,52 @@
/**
Definition of the UnderscoreGenerator class.
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
@module underscore-generator.js
*/
(function() {
var _ = require('underscore');
module.exports = 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");
/**
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, '');
// Compile and run the template. TODO: avoid unnecessary recompiles.
var compiled = _.template(jst);
var ret = compiled({
r: format === 'html' || format === 'pdf' ? json.markdownify() : json,
filt: opts.filters,
XML: require('xml-escape'),
RAW: json,
cssInfo: cssInfo,
headFragment: opts.headFragment || ''
});
return ret;
}
_.templateSettings = delims;
// Strip {# comments #}
jst = jst.replace( delims.comment, '');
// Compile and run the template. TODO: avoid unnecessary recompiles.
var compiled = _.template(jst);
var ret = compiled({
r: format === 'html' || format === 'pdf' ? json.markdownify() : json,
filt: opts.filters,
XML: require('xml-escape'),
RAW: json,
cssInfo: cssInfo,
headFragment: opts.headFragment || ''
});
return ret;
};
}());

View File

@ -142,9 +142,8 @@ Definition of the TemplateGenerator class.
*/
single: function( json, jst, format, cssInfo, opts, theme ) {
this.opts.freezeBreaks && ( jst = freeze(jst) );
var eng = require( '../eng/' + ((opts.themeObj && opts.themeObj.engine) ||
opts.engine) + '-generator' );
var result = eng( json, jst, format, cssInfo, opts, theme );
var eng = require( '../eng/' + theme.engine + '-generator' );
var result = eng.generate( json, jst, format, cssInfo, opts, theme );
this.opts.freezeBreaks && ( result = unfreeze(result) );
return result;
}

View File

@ -1,14 +1,14 @@
{
"basics": {
"name": "Jane Doe",
"label": "Senior Developer / Code Ninja",
"name": "Jane Q. Fullstacker",
"label": "Senior Developer",
"summary": "**Full-stack software developer with 6+ years industry experience** specializing in scalable cloud architectures for this, that, and the other. A native of southern CA, Jane enjoys hiking, mystery novels, and the company of Rufus, her two-year-old beagle.",
"website": "http://jane-doe.me",
"website": "http://janef.me/blog",
"phone": "1-650-999-7777",
"email": "jdoe@onecoolstartup.io",
"picture": "jane_doe.png",
"location": {
"address": "Jane Doe\n123 Somewhere Rd.\nMountain View, CA 94035",
"address": "Jane Fullstacker\n123 Somewhere Rd.\nMountain View, CA 94035",
"postalCode": "94035",
"city": "Mountain View",
"countryCode": "US",
@ -17,13 +17,13 @@
"profiles": [
{
"network": "GitHub",
"username": "jane-doe-was-here",
"url": "https://github.com/jane-doe-was-here"
"username": "janef-was-here",
"url": "https://github.com/janef-was-here"
},
{
"network": "Twitter",
"username": "jane-doe-was-here",
"url": "https://twitter.com/jane-doe-was-here"
"username": "janef-was-here",
"url": "https://twitter.com/janef-was-here"
}
]
},
@ -104,17 +104,55 @@
],
"skills": [
{
"name": "Programming",
"name": "Web Dev",
"keywords": [
"C++",
"Ruby",
"Xcode"
"JavaScript",
"HTML 5",
"CSS",
"LAMP",
"MVC",
"REST"
]
},
{
"name": "Project Management",
"name": "JavaScript",
"keywords": [
"Agile"
"Node.js",
"Angular.js",
"jQuery",
"Bootstrap",
"React.js",
"Backbone.js"
]
},
{
"name": "Database",
"keywords": [
"MySQL",
"PostgreSQL",
"NoSQL",
"ORM",
"Hibernate"
]
},
{
"name": "Cloud",
"keywords": [
"AWS",
"EC2",
"RDS",
"S3",
"Azure",
"Dropbox"
]
},
{
"name": "Project",
"keywords": [
"Agile",
"TFS",
"Unified Process",
"MS Project"
]
}
],
@ -166,10 +204,10 @@
"website": "http://codeproject.com/build-ui-electron-atom.aspx"
},
{
"name": "Jane Doe Unplugged",
"name": "Jane Fullstacker's Blog",
"publisher": "self",
"releaseDate": "2011",
"website": "http://jane-doe.me"
"website": "http://janef.me"
},
{
"name": "Teach Yourself GORFF in 21 Days",
@ -218,7 +256,8 @@
},
{
"language": "Spanish",
"level": "Moderate"
"level": "Moderate",
"years": 10
}
]
}
}

View File

@ -7,6 +7,7 @@ var chai = require('chai')
, FRESHResume = require('../src/core/fresh-resume')
, CONVERTER = require('../src/core/convert')
, FS = require('fs')
, MKDIRP = require('mkdirp')
, _ = require('underscore');
chai.config.includeStack = false;
@ -21,6 +22,7 @@ describe('FRESH/JRS converter', function () {
var fileB = path.join( __dirname, 'sandbox/richard-hendriks.json' );
_sheet = new FRESHResume().open( fileA );
MKDIRP.sync( path.parse(fileB).dir );
_sheet.saveAs( fileB, 'JRS' );
var rawA = FS.readFileSync( fileA, 'utf8' );

View File

@ -16,7 +16,7 @@ describe('jane-doe.json (FRESH)', function () {
it('should open without throwing an exception', function () {
function tryOpen() {
_sheet = new FRESHResume().open(
'node_modules/FRESCA/exemplar/jane-doe.json' );
'node_modules/jane-q-fullstacker/resume/jane-resume.json' );
}
tryOpen.should.not.Throw();
});
@ -43,13 +43,13 @@ describe('jane-doe.json (FRESH)', function () {
it('should save without throwing an exception', function(){
function trySave() {
_sheet.save( 'tests/sandbox/jane-doe.json' );
_sheet.save( 'tests/sandbox/jane-q-fullstacker.json' );
}
trySave.should.not.Throw();
});
it('should not be modified after saving', function() {
var savedSheet = new FRESHResume().open('tests/sandbox/jane-doe.json');
var savedSheet = new FRESHResume().open('tests/sandbox/jane-q-fullstacker.json');
_sheet.stringify().should.equal( savedSheet.stringify() )
});

View File

@ -16,7 +16,7 @@ describe('jane-doe.json (JRS)', function () {
it('should open without throwing an exception', function () {
function tryOpen() {
_sheet = new JRSResume().open(
path.join( __dirname, 'resumes/jrs/jane-doe.json' ) );
path.join( __dirname, 'resumes/jrs/jane-q-fullstacker.json' ) );
}
tryOpen.should.not.Throw();
});
@ -39,13 +39,13 @@ describe('jane-doe.json (JRS)', function () {
it('should save without throwing an exception', function(){
function trySave() {
_sheet.save( 'tests/sandbox/jane-doe.json' );
_sheet.save( 'tests/sandbox/jane-q-fullstacker.json' );
}
trySave.should.not.Throw();
});
it('should not be modified after saving', function() {
var savedSheet = new JRSResume().open( 'tests/sandbox/jane-doe.json' );
var savedSheet = new JRSResume().open( 'tests/sandbox/jane-q-fullstacker.json' );
_sheet.stringify().should.equal( savedSheet.stringify() )
});

View File

@ -29,8 +29,8 @@ describe('Testing themes', function () {
function genTheme( themeName ) {
it( themeName.toUpperCase() + ' theme should generate without throwing an exception', function () {
function tryOpen() {
var src = ['node_modules/FRESCA/exemplar/jane-doe.json'];
var dst = ['tests/sandbox/hello-world/resume.all'];
var src = ['node_modules/jane-q-fullstacker/resume/jane-resume.json'];
var dst = ['tests/sandbox/' + themeName + '/resume.all'];
var opts = {
theme: themeName,
format: 'FRESH',
@ -48,5 +48,6 @@ describe('Testing themes', function () {
genTheme('modern');
genTheme('minimist');
genTheme('awesome');
genTheme('positive');
});