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

Compare commits

..

15 Commits

Author SHA1 Message Date
3453293c79 Bump version to 1.3.1. 2015-12-31 20:41:54 -05:00
fb32cb0d78 Tests: Bump Johnny's expected duration to 4 years.
Happy New Year, everybody.
2015-12-31 20:00:39 -05:00
baccb75256 Tests: fix Travis error on Node 0.10.
Node 0.10 doesn't have path.parse, so use require('parse-filepath') as a
workaround.
2015-12-31 19:51:06 -05:00
5c39c1c93d Remove extraneous console.log. 2015-12-31 19:47:55 -05:00
48cc315fc8 Update Travis shields.
Add version and a badge for the /dev branch.
2015-12-31 19:17:56 -05:00
ea8da6811a Include Node 0.10 in Travis tests.
We've already done some work to support legacy Node 0.10 (ex
https://github.com/hacksalot/HackMyResume/issues/31#issuecomment-167155845)
no reason to drop this support by omitting tests.
2015-12-31 18:31:39 -05:00
dbda48c16d Add additional validate tests. 2015-12-31 18:24:45 -05:00
bc710b5c6e Merge pull request #77 from hacksalot/dev
v1.3.0 changes
2015-12-31 06:43:34 -05:00
b85d40b1b3 Improve XML encoding for Word docs.
Fix various encoding errors.
2015-12-31 06:38:30 -05:00
069c02ddcc Interim changes supporting v1.3.0. 2015-12-31 03:34:41 -05:00
1f6d77fc28 Bump version to 1.3.0. 2015-12-31 03:18:02 -05:00
2b4266ee42 Merge pull request #69 from zhuangya/missing-extend-def-fix-68
fix: missing extend method
2015-12-30 22:11:20 -05:00
2b3c83c57e Merge branch 'master' of https://github.com/hacksalot/HackMyResume 2015-12-30 22:03:38 -05:00
6280a18c14 fix: missing extend method
fix #68
2015-12-30 19:20:22 +08:00
5bc8b9c987 Merge remote-tracking branch 'refs/remotes/origin/dev' 2015-12-29 17:58:41 -05:00
19 changed files with 347 additions and 95 deletions

View File

@ -1,5 +1,6 @@
language: node_js language: node_js
node_js: node_js:
- "0.10"
- "0.11" - "0.11"
- "0.12" - "0.12"
- "4.0" - "4.0"

View File

@ -1,7 +1,9 @@
HackMyResume HackMyResume
============ ============
[![Build status][travis-image]][travis-url] [![Latest release][img-release]][latest-release]
[![Build status (MASTER)][img-master]][travis-url-master]
[![Build status (DEV)][img-dev]][travis-url-dev]
*Create polished résumés and CVs in multiple formats from your command line or *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, shell. Author in clean Markdown and JSON, export to Word, HTML, PDF, LaTeX,
@ -324,8 +326,12 @@ MIT. Go crazy. See [LICENSE.md][1] for details.
[fresh]: https://github.com/fluentdesk/FRESH [fresh]: https://github.com/fluentdesk/FRESH
[fresca]: https://github.com/fluentdesk/FRESCA [fresca]: https://github.com/fluentdesk/FRESCA
[dry]: https://en.wikipedia.org/wiki/Don%27t_repeat_yourself [dry]: https://en.wikipedia.org/wiki/Don%27t_repeat_yourself
[travis-image]: https://img.shields.io/travis/palomajs/paloma.svg?style=flat-square [img-release]: https://img.shields.io/github/release/hacksalot/HackMyResume.svg?label=version
[travis-url]: https://travis-ci.org/hacksalot/HackMyResume [img-master]: https://img.shields.io/travis/hacksalot/HackMyResume/master.svg
[img-dev]: https://img.shields.io/travis/hacksalot/HackMyResume/dev.svg?label=dev
[travis-url-master]: https://travis-ci.org/hacksalot/HackMyResume/master
[travis-url-dev]: https://travis-ci.org/hacksalot/HackMyResume/dev
[latest-release]: https://github.com/hacksalot/HackMyResume/releases/latest
[contribute]: CONTRIBUTING.md [contribute]: CONTRIBUTING.md
[fresh-themes]: https://github.com/fluentdesk/fresh-themes [fresh-themes]: https://github.com/fluentdesk/fresh-themes
[jrst]: https://www.npmjs.com/search?q=jsonresume-theme [jrst]: https://www.npmjs.com/search?q=jsonresume-theme

View File

@ -1,6 +1,6 @@
{ {
"name": "hackmyresume", "name": "hackmyresume",
"version": "1.3.0-beta", "version": "1.3.1",
"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.", "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": { "repository": {
"type": "git", "type": "git",
@ -47,12 +47,13 @@
"dependencies": { "dependencies": {
"colors": "^1.1.2", "colors": "^1.1.2",
"copy": "^0.1.3", "copy": "^0.1.3",
"fresh-themes": "~0.9.3-beta",
"fresca": "~0.2.2", "fresca": "~0.2.2",
"fresh-themes": "~0.9.3-beta",
"fs-extra": "^0.24.0", "fs-extra": "^0.24.0",
"handlebars": "^4.0.5", "handlebars": "^4.0.5",
"html": "0.0.10", "html": "0.0.10",
"is-my-json-valid": "^2.12.2", "is-my-json-valid": "^2.12.2",
"json-lint": "^0.1.0",
"jst": "0.0.13", "jst": "0.0.13",
"lodash": "^3.10.1", "lodash": "^3.10.1",
"marked": "^0.3.5", "marked": "^0.3.5",

View File

@ -1,11 +1,13 @@
/** /**
FRESH to JSON Resume conversion routiens. FRESH to JSON Resume conversion routiens.
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk. @license MIT. See LICENSE.md for details.
@module convert.js @module convert.js
*/ */
(function(){ (function(){
var _ = require('underscore');
/** /**
Convert between FRESH and JRS resume/CV formats. Convert between FRESH and JRS resume/CV formats.
@class FRESHConverter @class FRESHConverter
@ -26,6 +28,8 @@ FRESH to JSON Resume conversion routiens.
name: src.basics.name, name: src.basics.name,
imp: src.basics.imp,
info: { info: {
label: src.basics.label, label: src.basics.label,
class: src.basics.class, // <--> round-trip class: src.basics.class, // <--> round-trip
@ -92,7 +96,8 @@ FRESH to JSON Resume conversion routiens.
countryCode: src.location.country, countryCode: src.location.country,
region: src.location.region region: src.location.region
}, },
profiles: social( src.social, false ) profiles: social( src.social, false ),
imp: src.imp
}, },
work: employment( src.employment, false ), work: employment( src.employment, false ),
@ -109,12 +114,30 @@ FRESH to JSON Resume conversion routiens.
}; };
},
toSTRING: function( src ) {
function replacerJRS( key,value ) { // Exclude these keys from stringification
return _.some(['imp', 'warnings', 'computed', 'filt', 'ctrl', 'index',
'safeStartDate', 'safeEndDate', 'safeDate', 'safeReleaseDate', 'result',
'isModified', 'htmlPreview', 'display_progress_bar'],
function( val ) { return key.trim() === val; }
) ? undefined : value;
}
function replacerFRESH( key,value ) { // Exclude these keys from stringification
return _.some(['imp', 'warnings', 'computed', 'filt', 'ctrl', 'index',
'safe', 'result', 'isModified', 'htmlPreview', 'display_progress_bar'],
function( val ) { return key.trim() === val; }
) ? undefined : value;
}
return JSON.stringify( src, src.basics ? replacerJRS : replacerFRESH, 2 );
} }
}; };
function meta( direction, obj ) { function meta( direction, obj ) {
if( !obj ) return obj; // preserve null and undefined //if( !obj ) return obj; // preserve null and undefined
if( direction ) { if( direction ) {
obj = obj || { }; obj = obj || { };
obj.format = obj.format || "FRESH@0.1.0"; obj.format = obj.format || "FRESH@0.1.0";
@ -151,7 +174,7 @@ FRESH to JSON Resume conversion routiens.
start: job.startDate, start: job.startDate,
end: job.endDate, end: job.endDate,
url: job.website, url: job.website,
keywords: "", keywords: [],
highlights: job.highlights highlights: job.highlights
}; };
}) : undefined }) : undefined
@ -164,6 +187,7 @@ FRESH to JSON Resume conversion routiens.
if( !obj ) return obj; if( !obj ) return obj;
if( direction ) { if( direction ) {
return obj && obj.length ? { return obj && obj.length ? {
level: "",
history: obj.map(function(edu){ history: obj.map(function(edu){
return { return {
institution: edu.institution, institution: edu.institution,
@ -171,8 +195,8 @@ FRESH to JSON Resume conversion routiens.
end: edu.endDate, end: edu.endDate,
grade: edu.gpa, grade: edu.gpa,
curriculum: edu.courses, curriculum: edu.courses,
url: edu.website || edu.url || null, url: edu.website || edu.url || undefined,
summary: null, summary: edu.summary || "",
area: edu.area, area: edu.area,
studyType: edu.studyType studyType: edu.studyType
}; };

View File

@ -13,6 +13,7 @@ Definition of the FRESHResume class.
, __ = require('lodash') , __ = require('lodash')
, PATH = require('path') , PATH = require('path')
, moment = require('moment') , moment = require('moment')
, XML = require('xml-escape')
, MD = require('marked') , MD = require('marked')
, CONVERTER = require('./convert') , CONVERTER = require('./convert')
, JRSResume = require('./jrs-resume'); , JRSResume = require('./jrs-resume');
@ -81,12 +82,90 @@ Definition of the FRESHResume class.
return JSON.stringify( obj, replacer, 2 ); return JSON.stringify( obj, replacer, 2 );
}; };
/**
Create a copy of this resume in which all string fields have been run through
a transformation function (such as a Markdown filter or XML encoder).
*/
FreshResume.prototype.transformStrings = function( filters, transformer ) {
var that = this;
var ret = this.dupe();
// TODO: refactor recursion
function transformStringsInObject( obj ) {
if( !obj ) return;
if( moment.isMoment( obj ) ) return;
if( _.isArray( obj ) ) {
obj.forEach( function(elem, idx, ar) {
if( typeof elem === 'string' || elem instanceof String )
ar[idx] = transformer( null, elem );
else if (_.isObject(elem))
transformStringsInObject( elem );
});
}
else if (_.isObject( obj )) {
Object.keys( obj ).forEach(function(k) {
var sub = obj[k];
if( typeof sub === 'string' || sub instanceof String ) {
if( filters.length && _.contains(filters, k) )
return;
obj[k] = transformer( k, sub );
}
else if (_.isObject( sub ))
transformStringsInObject( sub );
});
}
}
Object.keys( ret ).forEach(function(member){
transformStringsInObject( ret[ member ] );
});
return ret;
};
/** /**
Create a copy of this resume in which all fields have been interpreted as Create a copy of this resume in which all fields have been interpreted as
Markdown. Markdown.
*/ */
FreshResume.prototype.markdownify = function() { FreshResume.prototype.markdownify = function() {
function MDIN( txt ){
return MD(txt || '' ).replace(/^\s*<p>|<\/p>\s*$/gi, '');
}
function trx(key, val) {
if( key === 'summary' ) {
return MD(val);
}
return MDIN(val);
}
return this.transformStrings( ['skills','url','start','end','date'], trx );
};
/**
Create a copy of this resume in which all fields have been interpreted as
Markdown.
*/
FreshResume.prototype.xmlify = function() {
function trx(key, val) {
return XML(val);
}
return this.transformStrings( [], trx );
};
/**
Create a copy of this resume in which all fields have been interpreted as
Markdown.
*/
FreshResume.prototype.markdownify2 = function() {
var that = this; var that = this;
var ret = this.dupe(); var ret = this.dupe();

View File

@ -1,6 +1,6 @@
/** /**
Definition of the JRSResume class. Definition of the JRSResume class.
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk. @license MIT. See LICENSE.md for details.
@module jrs-resume.js @module jrs-resume.js
*/ */
@ -12,6 +12,7 @@ Definition of the JRSResume class.
, _ = require('underscore') , _ = require('underscore')
, PATH = require('path') , PATH = require('path')
, MD = require('marked') , MD = require('marked')
, CONVERTER = require('./convert')
, moment = require('moment'); , moment = require('moment');
/** /**
@ -51,6 +52,24 @@ Definition of the JRSResume class.
return this; return this;
}; };
/**
Save the sheet to disk in a specific format, either FRESH or JRS.
*/
JRSResume.prototype.saveAs = function( filename, format ) {
if( format === 'JRS' ) {
this.basics.imp.fileName = filename || this.imp.fileName;
FS.writeFileSync( this.basics.imp.fileName, this.stringify(), 'utf8' );
}
else {
var newRep = CONVERTER.toFRESH( this );
var stringRep = CONVERTER.toSTRING( newRep );
FS.writeFileSync( filename, stringRep, 'utf8' );
}
return this;
};
/** /**
Convert this object to a JSON string, sanitizing meta-properties along the Convert this object to a JSON string, sanitizing meta-properties along the
way. Don't override .toString(). way. Don't override .toString().
@ -92,6 +111,7 @@ Definition of the JRSResume class.
if( opts.imp === undefined || opts.imp ) { if( opts.imp === undefined || opts.imp ) {
this.basics.imp = this.basics.imp || { }; this.basics.imp = this.basics.imp || { };
this.basics.imp.title = (opts.title || this.basics.imp.title) || this.basics.name; this.basics.imp.title = (opts.title || this.basics.imp.title) || this.basics.name;
this.basics.imp.orgFormat = 'JRS';
} }
// Parse dates, sort dates, and calculate computed values // Parse dates, sort dates, and calculate computed values
(opts.date === undefined || opts.date) && _parseDates.call( this ); (opts.date === undefined || opts.date) && _parseDates.call( this );

View File

@ -1,43 +1,107 @@
/** /**
Core resume-loading logic for HackMyResume. Definition of the ResumeFactory class.
@license MIT. See LICENSE.md for details.
@module resume-factory.js @module resume-factory.js
*/ */
(function(){ (function(){
require('string.prototype.startswith'); require('string.prototype.startswith');
var FS = require('fs'); var FS = require('fs');
var ResumeConverter = require('./convert'); var ResumeConverter = require('./convert');
/** /**
A simple factory class for FRESH and JSON Resumes. A simple factory class for FRESH and JSON Resumes.
@class ResumeFactory @class ResumeFactory
*/ */
module.exports = { var ResumeFactory = module.exports = {
/** /**
Load one or more resumes in a specific source format. Load one or more resumes from disk.
*/ */
load: function ( src, log, fn, toFormat ) { load: function ( sources, log, toFormat, objectify ) {
// Loop over all inputs, parsing each to JSON and then to a FRESHResume
toFormat = toFormat && (toFormat.toLowerCase().trim()) || 'fresh'; // or JRSResume object.
var ResumeClass = require('../core/' + toFormat + '-resume'); var that = this;
return sources.map( function( src ) {
return src.map( function( res ) { return that.loadOne( src, log, toFormat, objectify );
var rezJson = JSON.parse( FS.readFileSync( res ) );
var orgFormat = ( rezJson.meta && rezJson.meta.format &&
rezJson.meta.format.startsWith('FRESH@') ) ?
'fresh' : 'jrs';
if(orgFormat !== toFormat) {
rezJson = ResumeConverter[ 'to' + toFormat.toUpperCase() ]( rezJson );
}
// TODO: Core should not log
log( 'Reading '.info + orgFormat.toUpperCase().infoBold + ' resume: '.info + res.cyan.bold );
return (fn && fn(res)) || (new ResumeClass()).parseJSON( rezJson );
}); });
},
/**
Load a single resume from disk.
*/
loadOne: function( src, log, toFormat, objectify ) {
// Get the destination format. Can be 'fresh', 'jrs', or null/undefined.
toFormat && (toFormat = toFormat.toLowerCase().trim());
// Load and parse the resume JSON
var info = _parse( src, log, toFormat );
if( info.error ) return info;
var json = info.json;
// Determine the resume format: FRESH or JRS
var orgFormat = ( json.meta && json.meta.format &&
json.meta.format.startsWith('FRESH@') ) ?
'fresh' : 'jrs';
// Convert between formats if necessary
if( toFormat && (orgFormat !== toFormat) ) {
json = ResumeConverter[ 'to' + toFormat.toUpperCase() ]( json );
} }
// Objectify the resume, that is, convert it from JSON to a FRESHResume
// or JRSResume object.
var rez;
if( objectify ) {
var ResumeClass = require('../core/' + (toFormat || orgFormat) + '-resume');
rez = new ResumeClass().parseJSON( json );
}
return {
file: src,
json: info.json,
rez: rez
};
}
}; };
function _parse( fileName, log, toFormat ) {
var rawData;
try {
// TODO: Core should not log
log( 'Reading '.info + /*orgFormat.toUpperCase().infoBold +*/
'resume: '.info + fileName.cyan.bold );
rawData = FS.readFileSync( fileName, 'utf8' );
return {
json: JSON.parse( rawData )
};
}
catch(ex) {
return {
error: ex,
raw: rawData
};
}
}
}()); }());

View File

@ -9,6 +9,7 @@ Generic template helper definitions for HackMyResume / FluentCV.
var MD = require('marked') var MD = require('marked')
, H2W = require('../utils/html-to-wpml') , H2W = require('../utils/html-to-wpml')
, XML = require('xml-escape')
, moment = require('moment') , moment = require('moment')
, _ = require('underscore'); , _ = require('underscore');
@ -33,10 +34,11 @@ Generic template helper definitions for HackMyResume / FluentCV.
wpml: function( txt, inline ) { wpml: function( txt, inline ) {
if(!txt) return ''; if(!txt) return '';
inline = (inline && !inline.hash) || false; inline = (inline && !inline.hash) || false;
txt = XML(txt.trim());
txt = inline ? txt = inline ?
MD(txt.trim()).replace(/^\s*<p>|<\/p>\s*$/gi, '') : MD(txt).replace(/^\s*<p>|<\/p>\s*$/gi, '') :
MD(txt.trim()); MD(txt);
txt = H2W( txt.trim() ); txt = H2W( txt );
return txt; return txt;
}, },

View File

@ -35,8 +35,13 @@ Definition of the HandlebarsGenerator class.
// Compile and run the Handlebars template. // Compile and run the Handlebars template.
var template = HANDLEBARS.compile(jst); var template = HANDLEBARS.compile(jst);
var encData = json;
( format === 'html' || format === 'pdf' ) && (encData = json.markdownify());
( format === 'doc' ) && (encData = json.xmlify());
return template({ return template({
r: format === 'html' || format === 'pdf' || format === 'png' ? json.markdownify() : json, r: encData,
RAW: json, RAW: json,
filt: opts.filters, filt: opts.filters,
cssInfo: cssInfo, cssInfo: cssInfo,

View File

@ -73,7 +73,7 @@ function main() {
// Massage inputs and outputs // Massage inputs and outputs
var src = a._.slice(1, splitAt === -1 ? undefined : splitAt ); var src = a._.slice(1, splitAt === -1 ? undefined : splitAt );
var dst = splitAt === -1 ? [] : a._.slice( splitAt + 1 ); var dst = splitAt === -1 ? [] : a._.slice( splitAt + 1 );
( splitAt === -1 ) && src.length > 1 && dst.push( src.pop() ); // Allow omitting TO keyword ( splitAt === -1 ) && (src.length > 1) && (verb !== 'validate') && dst.push( src.pop() ); // Allow omitting TO keyword
// Invoke the action // Invoke the action
(FCMD.verbs[verb] || FCMD.alias[verb]).apply(null, [src, dst, opts, logMsg]); (FCMD.verbs[verb] || FCMD.alias[verb]).apply(null, [src, dst, opts, logMsg]);

View File

@ -47,6 +47,7 @@ Definition of the Markdown to WordProcessingML conversion routine.
break; break;
case 'Chars': case 'Chars':
if( tok.chars.trim().length ) {
var style = is_bold ? '<w:b/>' : ''; var style = is_bold ? '<w:b/>' : '';
style += is_italic ? '<w:i/>': ''; style += is_italic ? '<w:i/>': '';
style += is_link ? '<w:rStyle w:val="Hyperlink"/>' : ''; style += is_link ? '<w:rStyle w:val="Hyperlink"/>' : '';
@ -54,6 +55,7 @@ Definition of the Markdown to WordProcessingML conversion routine.
(is_link ? ('<w:hlink w:dest="' + link_url + '">') : '') + (is_link ? ('<w:hlink w:dest="' + link_url + '">') : '') +
'<w:r><w:rPr>' + style + '</w:rPr><w:t>' + tok.chars + '<w:r><w:rPr>' + style + '</w:rPr><w:t>' + tok.chars +
'</w:t></w:r>' + (is_link ? '</w:hlink>' : ''); '</w:t></w:r>' + (is_link ? '</w:hlink>' : '');
}
break; break;
} }
}); });

View File

@ -0,0 +1,40 @@
/**
Definition of the SyntaxErrorEx class.
@module syntax-error-ex.js
*/
(function() {
/**
Represents a SyntaxError exception with line and column info.
Collect syntax error information from the provided exception object. The
JavaScript `SyntaxError` exception isn't interpreted uniformly across environ-
ments, so we first check for a .lineNumber and .columnNumber and, if that's
not present, fall back to the JSONLint library, which provides that info.
See: http://stackoverflow.com/q/13323356
@class SyntaxErrorEx
*/
module.exports = function SyntaxErrorEx( ex, rawData ) {
var lineNum = null, colNum = null;
if( ex.lineNumber !== undefined && ex.lineNumber !== null ) {
lineNum = ex.lineNumber;
}
if( ex.columnNumber !== undefined && ex.columnNumber !== null ) {
colNum = ex.columnNumber;
}
if( lineNum === null || colNum === null ) {
var JSONLint = require('json-lint');
var lint = JSONLint( rawData, { comments: false } );
if( lineNum === null ) lineNum = (lint.error ? lint.line : '???');
if( colNum === null ) colNum = (lint.error ? lint.character : '???');
}
this.line = lineNum;
this.col = colNum;
};
}());

View File

@ -1,3 +1,9 @@
/**
Implementation of the 'convert' verb for HackMyResume.
@module convert.js
@license MIT. See LICENSE.md for details.
*/
(function(){ (function(){
var ResumeFactory = require('../core/resume-factory'); var ResumeFactory = require('../core/resume-factory');
@ -5,22 +11,23 @@
/** /**
Convert between FRESH and JRS formats. Convert between FRESH and JRS formats.
*/ */
module.exports = function convert( src, dst, opts, logger ) { module.exports = function convert( sources, dst, opts, logger ) {
var _log = logger || console.log; var _log = logger || console.log;
if( !src || !src.length ) { throw { fluenterror: 6 }; } if( !sources || !sources.length ) { throw { fluenterror: 6 }; }
if( !dst || !dst.length ) { if( !dst || !dst.length ) {
if( src.length === 1 ) { throw { fluenterror: 5 }; } if( sources.length === 1 ) { throw { fluenterror: 5 }; }
else if( src.length === 2 ) { dst = [ src[1] ]; src = [ src[0] ]; } else if( sources.length === 2 ) { dst = [ sources[1] ]; sources = [ sources[0] ]; }
else { throw { fluenterror: 5 }; } else { throw { fluenterror: 5 }; }
} }
if( src && dst && src.length && dst.length && src.length !== dst.length ) { if( sources && dst && sources.length && dst.length && sources.length !== dst.length ) {
throw { fluenterror: 7 }; throw { fluenterror: 7 };
} }
var sheets = ResumeFactory.load( src, _log ); var sourceResumes = ResumeFactory.load( sources, _log, null, true );
sheets.forEach(function(sheet, idx){ sourceResumes.forEach(function( src, idx ) {
var sourceFormat = sheet.imp.orgFormat === 'JRS' ? 'JRS' : 'FRESH'; var sheet = src.rez;
var sourceFormat = ((sheet.basics && sheet.basics.imp) || sheet.imp).orgFormat === 'JRS' ? 'JRS' : 'FRESH';
var targetFormat = sourceFormat === 'JRS' ? 'FRESH' : 'JRS'; var targetFormat = sourceFormat === 'JRS' ? 'FRESH' : 'JRS';
_log( 'Converting '.useful + sheet.imp.fileName.useful.bold + (' (' + _log( 'Converting '.useful + src.file.useful.bold + (' (' +
sourceFormat + ') to ').useful + dst[0].useful.bold + sourceFormat + ') to ').useful + dst[0].useful.bold +
(' (' + targetFormat + ').').useful ); (' (' + targetFormat + ').').useful );
sheet.saveAs( dst[idx], targetFormat ); sheet.saveAs( dst[idx], targetFormat );

View File

@ -1,3 +1,9 @@
/**
Implementation of the 'create' verb for HackMyResume.
@module create.js
@license MIT. See LICENSE.md for details.
*/
(function(){ (function(){
var FLUENT = require('../hackmyapi') var FLUENT = require('../hackmyapi')

View File

@ -20,6 +20,7 @@ Implementation of the 'generate' verb for HackMyResume.
, ResumeFactory = require('../core/resume-factory') , ResumeFactory = require('../core/resume-factory')
, _ = require('underscore') , _ = require('underscore')
, _fmts = require('../core/default-formats') , _fmts = require('../core/default-formats')
, extend = require('../utils/extend')
, _err, _log, rez; , _err, _log, rez;
@ -53,28 +54,28 @@ Implementation of the 'generate' verb for HackMyResume.
// Load the theme... // Load the theme...
var tFolder = verify_theme( _opts.theme ); var tFolder = verify_theme( _opts.theme );
var theTheme = load_theme( tFolder ); var theme = load_theme( tFolder );
// Load input resumes... // Load input resumes...
if( !src || !src.length ) { throw { fluenterror: 3 }; } if( !src || !src.length ) { throw { fluenterror: 3 }; }
var sheets = ResumeFactory.load( src, _log, null, var sheets = ResumeFactory.load(src, _log, theme.render ? 'JRS' : 'FRESH', true);
theTheme.render ? 'JRS' : 'FRESH' );
// Merge input resumes... // Merge input resumes...
var msg = ''; var msg = '';
rez = _.reduceRight( sheets, function( a, b, idx ) { var rezRep = _.reduceRight( sheets, function( a, b, idx ) {
msg += ((idx == sheets.length - 2) ? msg += ((idx == sheets.length - 2) ?
'Merging '.gray+ a.imp.fileName : '') + ' onto '.gray + b.imp.fileName; 'Merging '.gray + a.rez.imp.fileName : '') + ' onto '.gray + b.rez.fileName;
return extend( true, b, a ); return extend( true, b.rez, a.rez );
}); });
rez = rezRep.rez;
msg && _log(msg); msg && _log(msg);
// Expand output resumes... // Expand output resumes...
var targets = expand( dst, theTheme ); var targets = expand( dst, theme );
// Run the transformation! // Run the transformation!
targets.forEach( function(t) { targets.forEach( function(t) {
t.final = single( t, theTheme, targets ); t.final = single( t, theme, targets );
}); });
// Don't send the client back empty-handed // Don't send the client back empty-handed

View File

@ -1,16 +1,23 @@
/**
Implementation of the 'validate' verb for HackMyResume.
@module validate.js
@license MIT. See LICENSE.md for details.
*/
(function() { (function() {
var FS = require('fs'); var FS = require('fs');
var ResumeFactory = require('../core/resume-factory'); var ResumeFactory = require('../core/resume-factory');
var SyntaxErrorEx = require('../utils/syntax-error-ex');
module.exports = module.exports =
/** /**
Validate 1 to N resumes in either FRESH or JSON Resume format. Validate 1 to N resumes in either FRESH or JSON Resume format.
*/ */
function validate( src, unused, opts, logger ) { function validate( sources, unused, opts, logger ) {
var _log = logger || console.log; var _log = logger || console.log;
if( !src || !src.length ) { throw { fluenterror: 6 }; } if( !sources || !sources.length ) { throw { fluenterror: 6 }; }
var isValid = true; var isValid = true;
var validator = require('is-my-json-valid'); var validator = require('is-my-json-valid');
@ -20,67 +27,51 @@
}; };
// Load input resumes... // Load input resumes...
var sheets = ResumeFactory.load(src, _log, function( res ) { sources.forEach(function( src ) {
try {
return {
file: res,
raw: FS.readFileSync( res, 'utf8' )
};
}
catch( ex ) {
throw ex;
}
});
sheets.forEach( function( rep ) { var result = ResumeFactory.loadOne( src, function(){}, null, false );
if( result.error ) {
var rez; _log( 'Validating '.info + src.infoBold + ' against '.info + 'AUTO'.infoBold + ' schema:'.info + ' BROKEN'.red.bold );
try {
rez = JSON.parse( rep.raw );
}
catch( ex ) { // Note [1]
_log('Validating '.info + rep.file.infoBold +
' against FRESH/JRS schema: '.info + 'ERROR!'.error.bold);
var ex = result.error; // alias
if ( ex instanceof SyntaxError) { if ( ex instanceof SyntaxError) {
// Invalid JSON var info = new SyntaxErrorEx( ex, result.raw );
_log( '--> '.bold.red + rep.file.toUpperCase().red + _log( ('--> '.warn.bold + src.toUpperCase() + ' contains invalid JSON on line ' +
' contains invalid JSON. Unable to validate.'.red ); info.line + ' column ' + info.col + '.').warn +
_log( (' INTERNAL: ' + ex).red ); ' Unable to validate.'.warn );
_log( (' INTERNAL: ' + ex).warn );
} }
else { else {
_log(('ERROR: ' + ex.toString()).warn.bold);
_log(('ERROR: ' + ex.toString()).red.bold);
} }
return; return;
} }
var json = result.json;
var isValid = false; var isValid = false;
var style = 'useful'; var style = 'useful';
var errors = []; var errors = [];
var fmt = rez.meta && var fmt = json.meta && (json.meta.format==='FRESH@0.1.0') ? 'fresh':'jars';
(rez.meta.format === 'FRESH@0.1.0') ? 'fresh':'jars';
try { try {
var validate = validator( schemas[ fmt ], { // Note [1] var validate = validator( schemas[ fmt ], { // Note [1]
formats: { formats: {
date: /^\d{4}(?:-(?:0[0-9]{1}|1[0-2]{1})(?:-[0-9]{2})?)?$/ date: /^\d{4}(?:-(?:0[0-9]{1}|1[0-2]{1})(?:-[0-9]{2})?)?$/
} }
}); });
isValid = validate( rez ); isValid = validate( json );
if( !isValid ) { if( !isValid ) {
style = 'warn'; style = 'warn';
errors = validate.errors; errors = validate.errors;
} }
} }
catch(ex) { catch(exc) {
return; return;
} }
_log( 'Validating '.info + rep.file.infoBold + ' against '.info + _log( 'Validating '.info + result.file.infoBold + ' against '.info +
fmt.replace('jars','JSON Resume').toUpperCase().infoBold + fmt.replace('jars','JSON Resume').toUpperCase().infoBold +
' schema: '.info + (isValid ? 'VALID!' : 'INVALID')[style].bold ); ' schema: '.info + (isValid ? 'VALID!' : 'INVALID')[style].bold );
@ -93,5 +84,4 @@
}); });
}; };
}()); }());

View File

@ -47,11 +47,14 @@ describe('Testing CLI interface', function () {
run( 'new', ['test/sandbox/new-jrs-resume.json'], [], opts2, ' (JRS format)' ); run( 'new', ['test/sandbox/new-jrs-resume.json'], [], opts2, ' (JRS format)' );
run( 'new', ['test/sandbox/new-1.json', 'test/sandbox/new-2.json', 'test/sandbox/new-3.json'], [], opts, ' (multiple FRESH resumes)' ); run( 'new', ['test/sandbox/new-1.json', 'test/sandbox/new-2.json', 'test/sandbox/new-3.json'], [], opts, ' (multiple FRESH resumes)' );
run( 'new', ['test/sandbox/new-jrs-1.json', 'test/sandbox/new-jrs-2.json', 'test/sandbox/new-jrs-3.json'], [], opts, ' (multiple JRS resumes)' ); run( 'new', ['test/sandbox/new-jrs-1.json', 'test/sandbox/new-jrs-2.json', 'test/sandbox/new-jrs-3.json'], [], opts, ' (multiple JRS resumes)' );
run( 'new', ['test/sandbox/new-jrs-resume.json'], [], opts2, ' (JRS format)' );
fail( 'new', [], [], opts, " (when a filename isn't specified)" ); fail( 'new', [], [], opts, " (when a filename isn't specified)" );
run( 'validate', ['node_modules/jane-q-fullstacker/resume/jane-resume.json'], [], opts, ' (FRESH format)' ); run( 'validate', ['node_modules/jane-q-fullstacker/resume/jane-resume.json'], [], opts, ' (jane-q-fullstacker|FRESH)' );
run( 'validate', ['test/sandbox/new-fresh-resume.json'], [], opts, ' (FRESH format)' ); run( 'validate', ['node_modules/johnny-trouble-resume/src/johnny-trouble.fresh.json'], [], opts, ' (johnny-trouble|FRESH)' );
run( 'validate', ['test/sandbox/new-fresh-resume.json'], [], opts, ' (new-fresh-resume|FRESH)' );
run( 'validate', ['test/sandbox/resumes/jrs-0.0.0/ruchard-hendriks.json'], [], opts2, ' (richard-hendriks.json|JRS)' );
run( 'validate', ['test/sandbox/resumes/jrs-0.0.0/jane-incomplete.json'], [], opts2, ' (jane-incomplete.json|JRS)' );
run( 'validate', ['test/sandbox/new-1.json','test/sandbox/new-jrs-resume.json','test/sandbox/new-1.json', 'test/sandbox/new-2.json', 'test/sandbox/new-3.json'], [], opts, ' (5|BOTH)' );
function run( verb, src, dst, opts, msg ) { function run( verb, src, dst, opts, msg ) {
msg = msg || '.'; msg = msg || '.';

View File

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

View File

@ -59,4 +59,4 @@ function testResume(opts) {
var sects = [ 'info', 'employment', 'service', 'skills', 'education', 'writing', 'recognition', 'references' ]; var sects = [ 'info', 'employment', 'service', 'skills', 'education', 'writing', 'recognition', 'references' ];
testResume({ title: 'jane-q-fullstacker', path: 'node_modules/jane-q-fullstacker/resume/jane-resume.json', duration: 7, sections: sects }); testResume({ title: 'jane-q-fullstacker', path: 'node_modules/jane-q-fullstacker/resume/jane-resume.json', duration: 7, sections: sects });
testResume({ title: 'johnny-trouble-resume', path: 'node_modules/johnny-trouble-resume/src/johnny-trouble.fresh.json', duration: 3, sections: sects }); testResume({ title: 'johnny-trouble-resume', path: 'node_modules/johnny-trouble-resume/src/johnny-trouble.fresh.json', duration: 4, sections: sects });