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

Compare commits

...

16 Commits

Author SHA1 Message Date
78a8b9c58e Merge pull request #40 from hacksalot/rel/v1.2.2
rel/v1.2.2
2015-12-24 17:44:56 -05:00
5e7abb66bd Safer source format conversions.
Quick fix against missing fields in FRESH and/or JRS (ahead of introing
more robust standalone converter thing). Address portions of #31 and
#33.
2015-12-24 17:51:26 -05:00
358c397bb9 Show call stack on error.
Hat tip @Furchin.
2015-12-24 16:22:29 -05:00
3d41528059 Fix path parsing issue on prev versions of Node.js.
Work around absence of path.parse in Node versions < v0.12. Addresses
#31 and #33.
2015-12-24 16:18:38 -05:00
79637b611a Bump version to 1.2.2. 2015-12-24 16:09:37 -05:00
5de796b119 Merge pull request #35 from hacksalot/rel/v1.2.1
rel/v1.2.1
2015-12-24 07:41:12 -05:00
bf84341acf Version -> 1.2.1
Holding off on 1.3.0 pending HTTP/HTTPS support. #rebase #semver
2015-12-24 07:42:07 -05:00
bbac1fdceb Improve test coverage around incomplete JRS resumes.
Add quick sanity checks around incomplete or irregular JRS-format
resumes. Also decorate existing JRS test resumes (/tests/resumes/) with
current 0.0.0 JRS version ahead of 1.0.0 release.
2015-12-24 06:08:45 -05:00
c5ee1ee33c Quick fix for ".history" errors.
Affects #31 and #33.
2015-12-24 04:05:56 -05:00
c74eda90ed Introduce lodash dependency. 2015-12-24 04:05:17 -05:00
ef2fe95bd8 Remove unused method. 2015-12-24 04:04:44 -05:00
e2589b3730 Fix validate command error.
Still hitting some inconsistent behavior in different NPM
versions/platforms with invalid uppercase dependency names per
https://github.com/npm/npm/issues/3692. Partial fix for #33.
2015-12-24 03:23:56 -05:00
ebad1677bc Replace file-exists.js with NPM path-exists. 2015-12-22 18:55:17 -05:00
dab6ebfd82 Bump fluent-themes version to 0.8.0-beta. 2015-12-22 18:49:29 -05:00
dd61b5360a Update package.json contact info. 2015-12-22 18:49:11 -05:00
fced92a5a0 Bump version to 1.3.0. 2015-12-22 18:47:23 -05:00
17 changed files with 271 additions and 86 deletions

View File

@ -1,6 +1,6 @@
{
"name": "hackmyresume",
"version": "1.2.0",
"version": "1.2.2",
"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",
@ -24,7 +24,7 @@
"Underscore",
"template"
],
"author": "hacksalot <hacksalot@fluentdesk.com> (https://github.com/hacksalot)",
"author": "hacksalot <hacksalot@indevious.com> (https://github.com/hacksalot)",
"license": "MIT",
"preferGlobal": "true",
"bugs": {
@ -37,17 +37,20 @@
"homepage": "https://github.com/hacksalot/HackMyResume",
"dependencies": {
"colors": "^1.1.2",
"fluent-themes": "~0.7.0-beta",
"fluent-themes": "~0.7.1-beta",
"fresca": "~0.2.2",
"fs-extra": "^0.24.0",
"handlebars": "^4.0.5",
"html": "0.0.10",
"is-my-json-valid": "^2.12.2",
"jst": "0.0.13",
"lodash": "^3.10.1",
"marked": "^0.3.5",
"minimist": "^1.2.0",
"mkdirp": "^0.5.1",
"moment": "^2.10.6",
"parse-filepath": "^0.6.3",
"path-exists": "^2.1.0",
"recursive-readdir-sync": "^1.0.6",
"simple-html-tokenizer": "^0.2.0",
"underscore": "^1.8.3",

View File

@ -16,6 +16,7 @@ FRESH to JSON Resume conversion routiens.
/**
Convert from JSON Resume format to FRESH.
@method toFresh
@todo Refactor
*/
toFRESH: function( src, foreign ) {
@ -67,6 +68,7 @@ FRESH to JSON Resume conversion routiens.
Convert from FRESH format to JSON Resume.
@param foreign True if non-JSON-Resume properties should be included in
the result, false if those properties should be excluded.
@todo Refactor
*/
toJRS: function( src, foreign ) {
@ -112,6 +114,7 @@ FRESH to JSON Resume conversion routiens.
};
function meta( direction, obj ) {
if( !obj ) return obj; // preserve null and undefined
if( direction ) {
obj = obj || { };
obj.format = obj.format || "FRESH@0.1.0";
@ -121,6 +124,7 @@ FRESH to JSON Resume conversion routiens.
}
function employment( obj, direction ) {
if( !obj ) return obj;
if( !direction ) {
return obj && obj.history ?
obj.history.map(function(emp){
@ -157,6 +161,7 @@ FRESH to JSON Resume conversion routiens.
function education( obj, direction ) {
if( !obj ) return obj;
if( direction ) {
return obj && obj.length ? {
history: obj.map(function(edu){
@ -191,6 +196,7 @@ FRESH to JSON Resume conversion routiens.
}
function service( obj, direction, foreign ) {
if( !obj ) return obj;
if( direction ) {
return {
history: obj && obj.length ? obj.map(function(vol) {
@ -225,6 +231,7 @@ FRESH to JSON Resume conversion routiens.
}
function social( obj, direction ) {
if( !obj ) return obj;
if( direction ) {
return obj.map(function(pro){
return {
@ -247,6 +254,7 @@ FRESH to JSON Resume conversion routiens.
}
function recognition( obj, direction, foreign ) {
if( !obj ) return obj;
if( direction ) {
return obj && obj.length ? obj.map(
function(awd){
@ -275,6 +283,7 @@ FRESH to JSON Resume conversion routiens.
}
function references( obj, direction ) {
if( !obj ) return obj;
if( direction ) {
return obj && obj.length && obj.map(function(ref){
return {
@ -296,6 +305,7 @@ FRESH to JSON Resume conversion routiens.
}
function writing( obj, direction ) {
if( !obj ) return obj;
if( direction ) {
return obj.map(function( pub ) {
return {

View File

@ -10,6 +10,7 @@ Definition of the FRESHResume class.
, extend = require('../utils/extend')
, validator = require('is-my-json-valid')
, _ = require('underscore')
, __ = require('lodash')
, PATH = require('path')
, moment = require('moment')
, MD = require('marked')
@ -187,15 +188,6 @@ Definition of the FRESHResume class.
return flatSkills;
},
/**
Update the sheet's raw data. TODO: remove/refactor
*/
FreshResume.prototype.updateData = function( str ) {
this.clear( false );
this.parse( str );
return this;
};
/**
Reset the sheet to an empty state.
*/
@ -299,7 +291,7 @@ Definition of the FRESHResume class.
Validate the sheet against the FRESH Resume schema.
*/
FreshResume.prototype.isValid = function( info ) {
var schemaObj = require('FRESCA');
var schemaObj = require('fresca');
var validator = require('is-my-json-valid');
var validate = validator( schemaObj, { // Note [1]
formats: { date: /^\d{4}(?:-(?:0[0-9]{1}|1[0-2]{1})(?:-[0-9]{2})?)?$/ }
@ -321,7 +313,8 @@ Definition of the FRESHResume class.
sheets that have overlapping jobs.
*/
FreshResume.prototype.duration = function() {
if( this.employment.history && this.employment.history.length ) {
var empHist = __.get(this, 'employment.history');
if( empHist && empHist.length ) {
var firstJob = _.last( this.employment.history );
var careerStart = firstJob.start ? firstJob.safe.start : '';
if ((typeof careerStart === 'string' || careerStart instanceof String) &&
@ -341,9 +334,9 @@ Definition of the FRESHResume class.
*/
FreshResume.prototype.sort = function( ) {
this.employment.history && this.employment.history.sort( byDateDesc );
this.education.history && this.education.history.sort( byDateDesc );
this.service.history && this.service.history.sort( byDateDesc );
__.get(this, 'employment.history') && this.employment.history.sort( byDateDesc );
__.get(this, 'education.history') && this.education.history.sort( byDateDesc );
__.get(this, 'service.history') && this.service.history.sort( byDateDesc );
// this.awards && this.awards.sort( function(a, b) {
// return( a.safeDate.isBefore(b.safeDate) ) ? 1

View File

@ -107,15 +107,6 @@ Definition of the JRSResume class.
return flatSkills;
};
/**
Update the sheet's raw data. TODO: remove/refactor
*/
JRSResume.prototype.updateData = function( str ) {
this.clear( false );
this.parse( str );
return this;
};
/**
Reset the sheet to an empty state.
*/

View File

@ -11,6 +11,7 @@ Definition of the Theme class.
, validator = require('is-my-json-valid')
, _ = require('underscore')
, PATH = require('path')
, parsePath = require('parse-filepath')
, EXTEND = require('../utils/extend')
, moment = require('moment')
, RECURSIVE_READ_DIR = require('recursive-readdir-sync');
@ -30,8 +31,8 @@ Definition of the Theme class.
// Open the [theme-name].json file; should have the same name as folder
this.folder = themeFolder;
var pathInfo = PATH.parse( themeFolder );
var themeFile = PATH.join( themeFolder, pathInfo.base + '.json' );
var pathInfo = parsePath( themeFolder );
var themeFile = PATH.join( themeFolder, pathInfo.basename + '.json' );
var themeInfo = JSON.parse( FS.readFileSync( themeFile, 'utf8' ) );
var that = this;
@ -59,7 +60,7 @@ Definition of the Theme class.
this.formats = formatsHash;
// Set the official theme name
this.name = PATH.parse( this.folder ).name;
this.name = parsePath( this.folder ).name;
return this;
};
@ -96,9 +97,9 @@ Definition of the Theme class.
// If this file lives in a specific format folder within the theme,
// such as "/latex" or "/html", then that format is the output format
// for all files within the folder.
var pathInfo = PATH.parse(absPath);
var pathInfo = parsePath(absPath);
var outFmt = '', isMajor = false;
var portion = pathInfo.dir.replace(tplFolder,'');
var portion = pathInfo.dirname.replace(tplFolder,'');
if( portion && portion.trim() ) {
if( portion[1] === '_' ) return;
var reg = /^(?:\/|\\)(html|latex|doc|pdf|partials)(?:\/|\\)?/ig;
@ -135,7 +136,7 @@ Definition of the Theme class.
path: absPath,
major: isMajor,
orgPath: PATH.relative(tplFolder, absPath),
ext: pathInfo.ext.slice(1),
ext: pathInfo.extname.slice(1),
title: friendlyName( outFmt ),
pre: outFmt,
// outFormat: outFmt || pathInfo.name,
@ -186,7 +187,7 @@ Definition of the Theme class.
act = null;
// If this file is mentioned in the theme's JSON file under "transforms"
var pathInfo = PATH.parse(absPath);
var pathInfo = parsePath(absPath);
var absPathSafe = absPath.trim().toLowerCase();
var outFmt = _.find( Object.keys( that.formats ), function( fmtKey ) {
var fmtVal = that.formats[ fmtKey ];
@ -203,7 +204,7 @@ Definition of the Theme class.
// such as "/latex" or "/html", then that format is the output format
// for all files within the folder.
if( !outFmt ) {
var portion = pathInfo.dir.replace(tplFolder,'');
var portion = pathInfo.dirname.replace(tplFolder,'');
if( portion && portion.trim() ) {
var reg = /^(?:\/|\\)(html|latex|doc|pdf)(?:\/|\\)?/ig;
var res = reg.exec( portion );
@ -231,7 +232,7 @@ Definition of the Theme class.
action: act,
orgPath: PATH.relative(that.folder, absPath),
path: absPath,
ext: pathInfo.ext.slice(1),
ext: pathInfo.extname.slice(1),
title: friendlyName( outFmt ),
pre: outFmt,
// outFormat: outFmt || pathInfo.name,

View File

@ -13,6 +13,7 @@ Definition of the TemplateGenerator class.
, MD = require( 'marked' )
, XML = require( 'xml-escape' )
, PATH = require('path')
, parsePath = require('parse-filepath')
, MKDIRP = require('mkdirp')
, BaseGenerator = require( './base-generator' )
, EXTEND = require('../utils/extend')
@ -127,7 +128,7 @@ Definition of the TemplateGenerator class.
var theme = themeInfo.theme;
var tFolder = themeInfo.folder;
var tplFolder = PATH.join( tFolder, 'src' );
var outFolder = PATH.parse(f).dir;
var outFolder = parsePath(f).dirname;
var curFmt = theme.getFormat( this.format );
var that = this;
@ -176,7 +177,7 @@ Definition of the TemplateGenerator class.
var absLoc = PATH.join(outFolder, loc);
var absTarg = PATH.join(PATH.dirname(absLoc), curFmt.symLinks[loc]);
// 'file', 'dir', or 'junction' (Windows only)
var type = PATH.parse( absLoc ).ext ? 'file' : 'junction';
var type = parsePath( absLoc ).extname ? 'file' : 'junction';
FS.symlinkSync( absTarg, absLoc, type);
});
}
@ -221,10 +222,10 @@ Definition of the TemplateGenerator class.
function themeFromMoniker() {
// Verify the specified theme name/path
var tFolder = PATH.join(
PATH.parse( require.resolve('fluent-themes') ).dir,
parsePath( require.resolve('fluent-themes') ).dirname,
this.opts.theme
);
var exists = require('../utils/file-exists');
var exists = require('path-exists').sync;
if( !exists( tFolder ) ) {
tFolder = PATH.resolve( this.opts.theme );
if( !exists( tFolder ) ) {

View File

@ -91,6 +91,7 @@ function getOpts( args ) {
};
}
// TODO: refactor
function handleError( ex ) {
var msg = '', exitCode;
@ -123,8 +124,10 @@ function handleError( ex ) {
var idx = msg.indexOf('Error: ');
var trimmed = idx === -1 ? msg : msg.substring( idx + 7 );
if( !ex.fluenterror || ex.fluenterror < 3 )
if( !ex.fluenterror || ex.fluenterror < 3 ) { // TODO: magic #s
console.log( ('ERROR: ' + trimmed.toString()).red.bold );
console.log( ex.stack.gray);
}
else
console.log( trimmed.toString() );

View File

@ -1,19 +0,0 @@
/**
Definition of the `fileExists` method.
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
@module file-exists.js
*/
var FS = require('fs');
// Yup, this is now the recommended way to check for file existence on Node.
// fs.exists is deprecated and the recommended fs.statSync/lstatSync throws
// exceptions on non-existent paths :)
module.exports = function (path) {
try {
FS.statSync( path );
return true;
} catch( err ) {
return !(err && err.code === 'ENOENT');
}
};

View File

@ -1,6 +1,7 @@
(function() {
var PATH = require('path')
, parsePath = require('parse-filepath')
, MKDIRP = require('mkdirp')
, _opts = require('../core/default-options')
, FluentTheme = require('../core/theme')
@ -51,7 +52,7 @@
// Verify the specified theme name/path
var relativeThemeFolder = '../../node_modules/fluent-themes/themes';
var tFolder = PATH.resolve( __dirname, relativeThemeFolder, _opts.theme);
var exists = require('../utils/file-exists');
var exists = require('path-exists').sync;
if (!exists( tFolder )) {
tFolder = PATH.resolve( _opts.theme );
if (!exists( tFolder )) {
@ -60,7 +61,7 @@
}
// Load the theme
var theTheme = new FluentTheme().open( tFolder );
var theTheme = (new FluentTheme()).open( tFolder );
_opts.themeObj = theTheme;
_log( 'Applying '.info + theTheme.name.toUpperCase().infoBold +
(' theme (' + Object.keys(theTheme.formats).length + ' formats)').info);
@ -70,8 +71,8 @@
( (dst && dst.length && dst) || ['resume.all'] ).forEach( function(t) {
var to = PATH.resolve(t),
pa = PATH.parse(to),
fmat = pa.ext || '.all';
pa = parsePath(to),
fmat = pa.extname || '.all';
targets.push.apply(targets, fmat === '.all' ?
Object.keys( theTheme.formats ).map(function(k){

View File

@ -15,7 +15,7 @@
var validator = require('is-my-json-valid');
var schemas = {
fresh: require('FRESCA'),
fresh: require('fresca'),
jars: require('../core/resume.json')
};

View File

@ -0,0 +1,91 @@
{
"basics": {
"name": "",
"label": "",
"picture": "",
"email": "",
"phone": "",
"degree": "",
"website": "",
"summary": "",
"location": {
"address": "",
"postalCode": "",
"city": "",
"countryCode": "",
"region": ""
},
"profiles": [
{
"network": "",
"username": "",
"url": ""
}
]
},
"work": [
{
"company": "",
"position": "",
"website": "",
"startDate": "",
"endDate": "",
"summary": "",
"highlights": [
""
]
}
],
"awards": [
{
"title": "",
"date": "",
"awarder": "",
"summary": ""
}
],
"education": [
{
"institution": "",
"area": "",
"studyType": "",
"startDate": "",
"endDate": "",
"gpa": "",
"courses": [
""
]
}
],
"publications": [
{
"name": "",
"publisher": "",
"releaseDate": "",
"website": "",
"summary": ""
}
],
"volunteer": [
{
"organization": "",
"position": "",
"website": "",
"startDate": "",
"endDate": "",
"summary": "",
"highlights": [
""
]
}
],
"skills": [
{
"name": "",
"level": "",
"keywords": [
""
]
}
]
}

View File

@ -0,0 +1,104 @@
{
"basics": {
"name": "Jane Q. Fullstacker"
},
"education": [
{
"gpa": "3.5",
"courses": [
"Course 1",
"Course 2",
"Course 2"
],
"startDate": "2005-09",
"endDate": "2008-05"
},
{
"institution": "Medfield College",
"gpa": "3.2",
"courses": [
"Course 1",
"Course 2",
"Course 2"
],
"startDate": "2003-09",
"endDate": "2005-06"
}
],
"skills": [
{
"name": "Web Dev",
"keywords": [
"JavaScript",
"HTML 5",
"CSS",
"LAMP",
"MVC",
"REST"
]
},
{
"name": "JavaScript"
}
],
"volunteer": [],
"publications": [
{
"name": "Building User Interfaces with Electron and Atom",
"releaseDate": "2011",
"website": "http://codeproject.com/build-ui-electron-atom.aspx"
},
{
"name": "Jane Fullstacker's Blog",
"publisher": "self",
"releaseDate": "2011",
"website": "http://janef.me"
},
{
"name": "Teach Yourself GORFF in 21 Days",
"publisher": "Amazon"
}
],
"interests": [
{
"name": "reading",
"summary": "Jane is a fan of mystery novels and courtroom dramas including Agatha Christie and John Grisham.",
"keywords": [
"mystery",
"Agatha Christie",
"John Grisham"
]
},
{
"name": "hiking",
"summary": "Jane enjoys hiking, light mountain climbing, and has four summits under her belt!"
},
{
"name": "yoga"
}
],
"references": [
{
"name": "John Davidson",
"reference": "Jane is awesome! I'd hire her again in a heartbeat."
},
{
"name": "Elias Fullstacker",
"reference": "I worked with Jane on Jabberwocky and can vouch for her awesome technical capabilities and attention to detail. Insta-hire."
},
{
"name": "Dana Nevins",
"reference": "I've known Jane personally and professionally for almost ten years. She is one in a million."
}
],
"languages": [
{
"language": "English",
"level": "Native"
},
{
"language": "Spanish",
"level": "Moderate"
}
]
}

View File

@ -18,7 +18,7 @@ describe('FRESH/JRS converter', function () {
it('should round-trip from JRS to FRESH to JRS without modifying or losing data', function () {
var fileA = path.join( __dirname, 'resumes/jrs/richard-hendriks.json' );
var fileA = path.join( __dirname, 'resumes/jrs-0.0.0/richard-hendriks.json' );
var fileB = path.join( __dirname, 'sandbox/richard-hendriks.json' );
_sheet = new FRESHResume().open( fileA );

View File

@ -55,7 +55,7 @@ describe('jane-doe.json (FRESH)', function () {
it('should validate against the FRESH resume schema', function() {
var result = _sheet.isValid();
// var schemaJson = require('FRESCA');
// var schemaJson = require('fresca');
// var validate = validator( schemaJson, { verbose: true } );
// var result = validate( JSON.parse( _sheet.imp.raw ) );
result || console.log("\n\nOops, resume didn't validate. " +

View File

@ -9,55 +9,61 @@ var chai = require('chai')
chai.config.includeStack = false;
describe('jane-doe.json (JRS)', function () {
function testResume( opts ) {
describe( opts.title + ' (JRS)', function() {
opts.isValid = opts.isValid !== false;
var _sheet;
it('should open without throwing an exception', function () {
it('should open without throwing an exception', function () {
var that = this;
function tryOpen() {
_sheet = new JRSResume().open(
path.join( __dirname, 'resumes/jrs/jane-q-fullstacker.json' ) );
path.join( __dirname, 'resumes/jrs-0.0.0/' + opts.title + '.json' ) );
}
tryOpen.should.not.Throw();
});
it('should have one or more of each section', function() {
expect(
(_sheet.basics) &&
(_sheet.work && _sheet.work.length > 0) &&
(_sheet.skills && _sheet.skills.length > 0) &&
(_sheet.education && _sheet.education.length > 0) &&
(_sheet.volunteer && _sheet.volunteer.length > 0) &&
(_sheet.publications && _sheet.publications.length > 0) &&
(_sheet.awards && _sheet.awards.length > 0)
).to.equal( true );
var newObj = _.pick( _sheet, opts.sections );
expect( Object.keys(newObj).length ).to.equal( opts.sections.length );
});
it('should have a work duration of 7 years', function() {
_sheet.basics.computed.numYears.should.equal( 7 );
it('should have a work duration of ' + opts.duration + ' years', function() {
_sheet.basics.computed.numYears.should.equal( opts.duration );
});
it('should save without throwing an exception', function(){
it('should save without throwing an exception', function() {
var that = this;
function trySave() {
_sheet.save( 'tests/sandbox/jane-q-fullstacker.json' );
_sheet.save( 'tests/sandbox/' + opts.title + '.json' );
}
trySave.should.not.Throw();
});
it('should not be modified after saving', function() {
var savedSheet = new JRSResume().open( 'tests/sandbox/jane-q-fullstacker.json' );
var savedSheet = new JRSResume().open( 'tests/sandbox/' + opts.title + '.json' );
_sheet.stringify().should.equal( savedSheet.stringify() )
});
it('should validate against the JSON Resume schema', function() {
it('should ' + (opts.isValid ? '' : 'NOT ') + 'validate against the JSON Resume schema', function() {
var result = _sheet.isValid();
// var schemaJson = require('../src/core/resume.json');
// var validate = validator( schemaJson, { verbose: true } );
// var result = validate( JSON.parse( _sheet.imp.raw ) );
result || console.log("\n\nOops, resume didn't validate. " +
"Validation errors:\n\n", _sheet.basics.imp.validationErrors, "\n\n");
result.should.equal( true );
result.should.equal( opts.isValid );
});
});
}
});
var sects = [ 'basics', 'work', 'volunteer', 'skills', 'education', 'publications', 'awards', 'references' ];
testResume({ title: 'jane-q-fullstacker', duration: 7, sections: sects });
testResume({ title: 'jane-incomplete', duration: 0, sections: _.without(sects, 'awards', 'work') });
testResume({ title: 'richard-hendriks', duration: 1, sections: sects });
testResume({ title: 'empty', duration: 0, sections: [], isValid: false });