mirror of
https://github.com/JuanCanham/HackMyResume.git
synced 2024-11-05 09:56:22 +00:00
Introduce FluentLib sources.
This commit is contained in:
parent
21d9db2d99
commit
0aaa9ffff8
1
.gitignore
vendored
1
.gitignore
vendored
@ -1 +1,2 @@
|
|||||||
node_modules/
|
node_modules/
|
||||||
|
tests/sandbox/
|
||||||
|
29
Gruntfile.js
Normal file
29
Gruntfile.js
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
module.exports = function (grunt) {
|
||||||
|
|
||||||
|
var opts = {
|
||||||
|
|
||||||
|
pkg: grunt.file.readJSON('package.json'),
|
||||||
|
|
||||||
|
simplemocha: {
|
||||||
|
options: {
|
||||||
|
globals: ['expect', 'should'],
|
||||||
|
timeout: 3000,
|
||||||
|
ignoreLeaks: false,
|
||||||
|
ui: 'bdd',
|
||||||
|
reporter: 'spec'
|
||||||
|
},
|
||||||
|
all: { src: ['tests/*.js'] }
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
grunt.initConfig( opts );
|
||||||
|
grunt.loadNpmTasks('grunt-simple-mocha');
|
||||||
|
grunt.registerTask('test', 'Test the FluentLib library.', function( config ) {
|
||||||
|
grunt.task.run( ['simplemocha:all'] );
|
||||||
|
});
|
||||||
|
grunt.registerTask('default', [ 'test' ]);
|
||||||
|
|
||||||
|
};
|
25
package.json
25
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "fluentcmd",
|
"name": "fluentcmd",
|
||||||
"version": "0.5.0",
|
"version": "0.6.0",
|
||||||
"description": "Generate beautiful, targeted resumes from your command line or shell.",
|
"description": "Generate beautiful, targeted resumes from your command line or shell.",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@ -24,10 +24,29 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/fluentdesk/fluentcmd",
|
"homepage": "https://github.com/fluentdesk/fluentcmd",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fluentlib": "fluentdesk/fluentlib#v0.4.0",
|
|
||||||
"minimist": "^1.2.0",
|
"minimist": "^1.2.0",
|
||||||
"mkdirp": "^0.5.1",
|
"mkdirp": "^0.5.1",
|
||||||
"underscore": "^1.8.3",
|
"underscore": "^1.8.3",
|
||||||
"watermark": "fluentdesk/watermark#v0.3.1-alpha"
|
"watermark": "fluentdesk/watermark#v0.3.1-alpha",
|
||||||
|
"fs-extra": "^0.24.0",
|
||||||
|
"html": "0.0.10",
|
||||||
|
"is-my-json-valid": "^2.12.2",
|
||||||
|
"jst": "0.0.13",
|
||||||
|
"marked": "^0.3.5",
|
||||||
|
"minimist": "^1.2.0",
|
||||||
|
"moment": "^2.10.6",
|
||||||
|
"underscore": "^1.8.3",
|
||||||
|
"watermark": "fluentdesk/watermark#v0.3.1-alpha",
|
||||||
|
"wkhtmltopdf": "^0.1.5",
|
||||||
|
"xml-escape": "^1.0.0",
|
||||||
|
"yamljs": "^0.2.4"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"chai": "*",
|
||||||
|
"grunt": "*",
|
||||||
|
"grunt-simple-mocha": "*",
|
||||||
|
"is-my-json-valid": "^2.12.2",
|
||||||
|
"mocha": "*",
|
||||||
|
"resample": "fluentdesk/resample#v0.1.0-alpha"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
77
src/core/empty.json
Normal file
77
src/core/empty.json
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
{
|
||||||
|
"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": [""]
|
||||||
|
}]
|
||||||
|
}
|
80
src/core/fluent-date.js
Normal file
80
src/core/fluent-date.js
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
/**
|
||||||
|
The FluentCV date representation.
|
||||||
|
@license Copyright (c) 2015 by James M. Devlin. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var moment = require('moment');
|
||||||
|
|
||||||
|
/**
|
||||||
|
Create a FluentDate from a string or Moment date object. There are a few date
|
||||||
|
formats to be aware of here.
|
||||||
|
1. The words "Present" and "Now", referring to the current date
|
||||||
|
2. The default "YYYY-MM-DD" format used in JSON Resume ("2015-02-10")
|
||||||
|
3. Year-and-month only ("2015-04")
|
||||||
|
4. Year-only "YYYY" ("2015")
|
||||||
|
5. The friendly FluentCV "mmm YYYY" format ("Mar 2015" or "Dec 2008")
|
||||||
|
6. Empty dates ("", " ")
|
||||||
|
7. Any other date format that Moment.js can parse from
|
||||||
|
Note: Moment can transparently parse all or most of these, without requiring us
|
||||||
|
to specify a date format...but for maximum parsing safety and to avoid Moment
|
||||||
|
deprecation warnings, it's recommended to either a) explicitly specify the date
|
||||||
|
format or b) use an ISO format. For clarity, we handle these cases explicitly.
|
||||||
|
@class FluentDate
|
||||||
|
*/
|
||||||
|
function FluentDate( dt ) {
|
||||||
|
this.rep = this.fmt( dt );
|
||||||
|
}
|
||||||
|
|
||||||
|
FluentDate/*.prototype*/.fmt = function( dt ) {
|
||||||
|
if( (typeof dt === 'string' || dt instanceof String) ) {
|
||||||
|
dt = dt.toLowerCase().trim();
|
||||||
|
if( /^(present|now)$/.test(dt) ) { // "Present", "Now"
|
||||||
|
return moment();
|
||||||
|
}
|
||||||
|
else if( /^\D+\s+\d{4}$/.test(dt) ) { // "Mar 2015"
|
||||||
|
var parts = dt.split(' ');
|
||||||
|
var month = (months[parts[0]] || abbr[parts[0]]);
|
||||||
|
var dt = parts[1] + '-' + (month < 10 ? '0' + month : month.toString());
|
||||||
|
return moment( dt, 'YYYY-MM' );
|
||||||
|
}
|
||||||
|
else if( /^\d{4}-\d{1,2}$/.test(dt) ) { // "2015-03", "1998-4"
|
||||||
|
return moment( dt, 'YYYY-MM' );
|
||||||
|
}
|
||||||
|
else if( /^\s\d{4}$/.test(dt) ) { // "2015"
|
||||||
|
return moment( dt, 'YYYY' );
|
||||||
|
}
|
||||||
|
else if( /^\s*$/.test(dt) ) { // "", " "
|
||||||
|
var defTime = {
|
||||||
|
isNull: true,
|
||||||
|
isBefore: function( other ) {
|
||||||
|
return( other && !other.isNull ) ? true : false;
|
||||||
|
},
|
||||||
|
isAfter: function( other ) {
|
||||||
|
return( other && !other.isNull ) ? false : false;
|
||||||
|
},
|
||||||
|
unix: function() { return 0; },
|
||||||
|
format: function() { return ''; },
|
||||||
|
diff: function() { return 0; }
|
||||||
|
};
|
||||||
|
return defTime;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
var mt = moment( dt );
|
||||||
|
if(mt.isValid())
|
||||||
|
return mt;
|
||||||
|
throw 'Invalid date format encountered.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if( dt.isValid && dt.isValid() )
|
||||||
|
return dt;
|
||||||
|
throw 'Unknown date object encountered.';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var months = {}, abbr = {};
|
||||||
|
moment.months().forEach(function(m,idx){months[m.toLowerCase()]=idx+1;});
|
||||||
|
moment.monthsShort().forEach(function(m,idx){abbr[m.toLowerCase()]=idx+1;});
|
||||||
|
abbr.sept = 9;
|
||||||
|
|
||||||
|
module.exports = FluentDate;
|
380
src/core/resume.json
Normal file
380
src/core/resume.json
Normal file
@ -0,0 +1,380 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
|
"title": "Resume Schema",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"basics": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true,
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"label": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "e.g. Web Developer"
|
||||||
|
},
|
||||||
|
"picture": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "URL (as per RFC 3986) to a picture in JPEG or PNG format"
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "e.g. thomas@gmail.com",
|
||||||
|
"format": "email"
|
||||||
|
},
|
||||||
|
"phone": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Phone numbers are stored as strings so use any format you like, e.g. 712-117-2923"
|
||||||
|
},
|
||||||
|
"website": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "URL (as per RFC 3986) to your website, e.g. personal homepage",
|
||||||
|
"format": "uri"
|
||||||
|
},
|
||||||
|
"summary": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Write a short 2-3 sentence biography about yourself"
|
||||||
|
},
|
||||||
|
"location": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true,
|
||||||
|
"properties": {
|
||||||
|
"address": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "To add multiple address lines, use \n. For example, 1234 Glücklichkeit Straße\nHinterhaus 5. Etage li."
|
||||||
|
},
|
||||||
|
"postalCode": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"city": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"countryCode": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "code as per ISO-3166-1 ALPHA-2, e.g. US, AU, IN"
|
||||||
|
},
|
||||||
|
"region": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The general region where you live. Can be a US state, or a province, for instance."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"profiles": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "Specify any number of social networks that you participate in",
|
||||||
|
"additionalItems": false,
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true,
|
||||||
|
"properties": {
|
||||||
|
"network": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "e.g. Facebook or Twitter"
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "e.g. neutralthoughts"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "e.g. http://twitter.com/neutralthoughts"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"work": {
|
||||||
|
"type": "array",
|
||||||
|
"additionalItems": false,
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true,
|
||||||
|
"properties": {
|
||||||
|
"company": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "e.g. Facebook"
|
||||||
|
},
|
||||||
|
"position": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "e.g. Software Engineer"
|
||||||
|
},
|
||||||
|
"website": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "e.g. http://facebook.com",
|
||||||
|
"format": "uri"
|
||||||
|
},
|
||||||
|
"startDate": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "resume.json uses the ISO 8601 date standard e.g. 2014-06-29",
|
||||||
|
"format": "date"
|
||||||
|
},
|
||||||
|
"endDate": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "e.g. 2012-06-29",
|
||||||
|
"format": "date"
|
||||||
|
},
|
||||||
|
"summary": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Give an overview of your responsibilities at the company"
|
||||||
|
},
|
||||||
|
"highlights": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "Specify multiple accomplishments",
|
||||||
|
"additionalItems": false,
|
||||||
|
"items": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "e.g. Increased profits by 20% from 2011-2012 through viral advertising"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"volunteer": {
|
||||||
|
"type": "array",
|
||||||
|
"additionalItems": false,
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true,
|
||||||
|
"properties": {
|
||||||
|
"organization": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "e.g. Facebook"
|
||||||
|
},
|
||||||
|
"position": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "e.g. Software Engineer"
|
||||||
|
},
|
||||||
|
"website": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "e.g. http://facebook.com",
|
||||||
|
"format": "uri"
|
||||||
|
},
|
||||||
|
"startDate": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "resume.json uses the ISO 8601 date standard e.g. 2014-06-29",
|
||||||
|
"format": "date"
|
||||||
|
},
|
||||||
|
"endDate": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "e.g. 2012-06-29",
|
||||||
|
"format": "date"
|
||||||
|
},
|
||||||
|
"summary": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Give an overview of your responsibilities at the company"
|
||||||
|
},
|
||||||
|
"highlights": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "Specify multiple accomplishments",
|
||||||
|
"additionalItems": false,
|
||||||
|
"items": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "e.g. Increased profits by 20% from 2011-2012 through viral advertising"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"education": {
|
||||||
|
"type": "array",
|
||||||
|
"additionalItems": false,
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true,
|
||||||
|
"properties": {
|
||||||
|
"institution": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "e.g. Massachusetts Institute of Technology"
|
||||||
|
},
|
||||||
|
"area": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "e.g. Arts"
|
||||||
|
},
|
||||||
|
"studyType": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "e.g. Bachelor"
|
||||||
|
},
|
||||||
|
"startDate": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "e.g. 2014-06-29",
|
||||||
|
"format": "date"
|
||||||
|
},
|
||||||
|
"endDate": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "e.g. 2012-06-29",
|
||||||
|
"format": "date"
|
||||||
|
},
|
||||||
|
"gpa": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "grade point average, e.g. 3.67/4.0"
|
||||||
|
},
|
||||||
|
"courses": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "List notable courses/subjects",
|
||||||
|
"additionalItems": false,
|
||||||
|
"items": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "e.g. H1302 - Introduction to American history"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"awards": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "Specify any awards you have received throughout your professional career",
|
||||||
|
"additionalItems": false,
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true,
|
||||||
|
"properties": {
|
||||||
|
"title": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "e.g. One of the 100 greatest minds of the century"
|
||||||
|
},
|
||||||
|
"date": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "e.g. 1989-06-12",
|
||||||
|
"format": "date"
|
||||||
|
},
|
||||||
|
"awarder": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "e.g. Time Magazine"
|
||||||
|
},
|
||||||
|
"summary": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "e.g. Received for my work with Quantum Physics"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"publications": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "Specify your publications through your career",
|
||||||
|
"additionalItems": false,
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true,
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "e.g. The World Wide Web"
|
||||||
|
},
|
||||||
|
"publisher": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "e.g. IEEE, Computer Magazine"
|
||||||
|
},
|
||||||
|
"releaseDate": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "e.g. 1990-08-01"
|
||||||
|
},
|
||||||
|
"website": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "e.g. http://www.computer.org/csdl/mags/co/1996/10/rx069-abs.html"
|
||||||
|
},
|
||||||
|
"summary": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Short summary of publication. e.g. Discussion of the World Wide Web, HTTP, HTML."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"skills": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "List out your professional skill-set",
|
||||||
|
"additionalItems": false,
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true,
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "e.g. Web Development"
|
||||||
|
},
|
||||||
|
"level": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "e.g. Master"
|
||||||
|
},
|
||||||
|
"keywords": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "List some keywords pertaining to this skill",
|
||||||
|
"additionalItems": false,
|
||||||
|
"items": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "e.g. HTML"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"languages": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "List any other languages you speak",
|
||||||
|
"additionalItems": false,
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true,
|
||||||
|
"properties": {
|
||||||
|
"language": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "e.g. English, Spanish"
|
||||||
|
},
|
||||||
|
"fluency": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "e.g. Fluent, Beginner"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"interests": {
|
||||||
|
"type": "array",
|
||||||
|
"additionalItems": false,
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true,
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "e.g. Philosophy"
|
||||||
|
},
|
||||||
|
"keywords": {
|
||||||
|
"type": "array",
|
||||||
|
"additionalItems": false,
|
||||||
|
"items": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "e.g. Friedrich Nietzsche"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"references": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "List references you have received",
|
||||||
|
"additionalItems": false,
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true,
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "e.g. Timothy Cook"
|
||||||
|
},
|
||||||
|
"reference": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "e.g. Joe blogs was a great employee, who turned up to work at least once a week. He exceeded my expectations when it came to doing nothing."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
259
src/core/sheet.js
Normal file
259
src/core/sheet.js
Normal file
@ -0,0 +1,259 @@
|
|||||||
|
/**
|
||||||
|
Abstract character/resume sheet representation.
|
||||||
|
@license Copyright (c) 2015 by James M. Devlin. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
|
||||||
|
var FS = require('fs')
|
||||||
|
, extend = require('../utils/extend')
|
||||||
|
, validator = require('is-my-json-valid')
|
||||||
|
, _ = require('underscore')
|
||||||
|
, PATH = require('path')
|
||||||
|
, moment = require('moment');
|
||||||
|
|
||||||
|
/**
|
||||||
|
The Sheet class represent a specific JSON character sheet. When Sheet.open
|
||||||
|
is called, we merge the loaded JSON sheet properties onto the Sheet instance
|
||||||
|
via extend(), so a full-grown sheet object will have all of the methods here,
|
||||||
|
plus a complement of JSON properties from the backing JSON file. That allows
|
||||||
|
us to treat Sheet objects interchangeably with the loaded JSON model.
|
||||||
|
@class Sheet
|
||||||
|
*/
|
||||||
|
function Sheet() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Open and parse the specified JSON resume sheet. Merge the JSON object model
|
||||||
|
onto this Sheet instance with extend() and convert sheet dates to a safe &
|
||||||
|
consistent format. Then sort each section by startDate descending.
|
||||||
|
*/
|
||||||
|
Sheet.prototype.open = function( file, title ) {
|
||||||
|
this.meta = { fileName: file };
|
||||||
|
this.meta.raw = FS.readFileSync( file, 'utf8' );
|
||||||
|
return this.parse( this.meta.raw, title );
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
Save the sheet to disk (for environments that have disk access).
|
||||||
|
*/
|
||||||
|
Sheet.prototype.save = function( filename ) {
|
||||||
|
filename = filename || this.meta.fileName;
|
||||||
|
FS.writeFileSync( filename, this.stringify(), 'utf8' );
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
Convert this object to a JSON string, sanitizing meta-properties along the
|
||||||
|
way. Don't override .toString().
|
||||||
|
*/
|
||||||
|
Sheet.prototype.stringify = function() {
|
||||||
|
function replacer( key,value ) { // Exclude these keys from stringification
|
||||||
|
return _.some(['meta', 'warnings', 'computed', 'filt', 'ctrl', 'index',
|
||||||
|
'safeStartDate', 'safeEndDate', 'safeDate', 'safeReleaseDate', 'result',
|
||||||
|
'isModified', 'htmlPreview'],
|
||||||
|
function( val ) { return key.trim() === val; }
|
||||||
|
) ? undefined : value;
|
||||||
|
}
|
||||||
|
return JSON.stringify( this, replacer, 2 );
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
Open and parse the specified JSON resume sheet. Merge the JSON object model
|
||||||
|
onto this Sheet instance with extend() and convert sheet dates to a safe &
|
||||||
|
consistent format. Then sort each section by startDate descending.
|
||||||
|
*/
|
||||||
|
Sheet.prototype.parse = function( stringData, opts ) {
|
||||||
|
opts = opts || { };
|
||||||
|
var rep = JSON.parse( stringData );
|
||||||
|
extend( true, this, rep );
|
||||||
|
// Set up metadata
|
||||||
|
if( opts.meta === undefined || opts.meta ) {
|
||||||
|
this.meta = this.meta || { };
|
||||||
|
this.meta.title = (opts.title || this.meta.title) || this.basics.name;
|
||||||
|
}
|
||||||
|
// Parse dates, sort dates, and calculate computed values
|
||||||
|
(opts.date === undefined || opts.date) && _parseDates.call( this );
|
||||||
|
(opts.sort === undefined || opts.sort) && this.sort();
|
||||||
|
(opts.compute === undefined || opts.compute) && (this.computed = {
|
||||||
|
numYears: this.duration(),
|
||||||
|
keywords: this.keywords()
|
||||||
|
});
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
Return a unique list of all keywords across all skills.
|
||||||
|
*/
|
||||||
|
Sheet.prototype.keywords = function() {
|
||||||
|
var flatSkills = [];
|
||||||
|
if( this.skills && this.skills.length ) {
|
||||||
|
this.skills.forEach( function( s ) {
|
||||||
|
flatSkills = _.union( flatSkills, s.keywords );
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return flatSkills;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
Update the sheet's raw data. TODO: remove/refactor
|
||||||
|
*/
|
||||||
|
Sheet.prototype.updateData = function( str ) {
|
||||||
|
this.clear( false );
|
||||||
|
this.parse( str )
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
Reset the sheet to an empty state.
|
||||||
|
*/
|
||||||
|
Sheet.prototype.clear = function( clearMeta ) {
|
||||||
|
clearMeta = ((clearMeta === undefined) && true) || clearMeta;
|
||||||
|
clearMeta && (delete this.meta);
|
||||||
|
delete this.computed; // Don't use Object.keys() here
|
||||||
|
delete this.work;
|
||||||
|
delete this.volunteer;
|
||||||
|
delete this.education;
|
||||||
|
delete this.awards;
|
||||||
|
delete this.publications;
|
||||||
|
delete this.interests;
|
||||||
|
delete this.skills;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
Get the default (empty) sheet.
|
||||||
|
*/
|
||||||
|
Sheet.default = function() {
|
||||||
|
return new Sheet().open( PATH.join( __dirname, 'empty.json'), 'Empty' );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Add work experience to the sheet.
|
||||||
|
*/
|
||||||
|
Sheet.prototype.add = function( moniker ) {
|
||||||
|
var defSheet = Sheet.default();
|
||||||
|
var newObject = $.extend( true, {}, defSheet[ moniker ][0] );
|
||||||
|
this[ moniker ] = this[ moniker ] || [];
|
||||||
|
this[ moniker ].push( newObject );
|
||||||
|
return newObject;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
Determine if the sheet includes a specific social profile (eg, GitHub).
|
||||||
|
*/
|
||||||
|
Sheet.prototype.hasProfile = function( socialNetwork ) {
|
||||||
|
socialNetwork = socialNetwork.trim().toLowerCase();
|
||||||
|
return this.basics.profiles && _.some( this.basics.profiles, function(p) {
|
||||||
|
return p.network.trim().toLowerCase() === socialNetwork;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
Determine if the sheet includes a specific skill.
|
||||||
|
*/
|
||||||
|
Sheet.prototype.hasSkill = function( skill ) {
|
||||||
|
skill = skill.trim().toLowerCase();
|
||||||
|
return this.skills && _.some( this.skills, function(sk) {
|
||||||
|
return sk.keywords && _.some( sk.keywords, function(kw) {
|
||||||
|
return kw.trim().toLowerCase() === skill;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
Validate the sheet against the JSON Resume schema.
|
||||||
|
*/
|
||||||
|
Sheet.prototype.isValid = function( ) { // TODO: ↓ fix this path ↓
|
||||||
|
var schema = FS.readFileSync( PATH.join( __dirname, 'resume.json' ), 'utf8' );
|
||||||
|
var schemaObj = JSON.parse( schema );
|
||||||
|
var validator = require('is-my-json-valid')
|
||||||
|
var validate = validator( schemaObj );
|
||||||
|
return validate( this );
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
Calculate the total duration of the sheet. Assumes this.work has been sorted
|
||||||
|
by start date descending, perhaps via a call to Sheet.sort().
|
||||||
|
@returns The total duration of the sheet's work history, that is, the number
|
||||||
|
of years between the start date of the earliest job on the resume and the
|
||||||
|
*latest end date of all jobs in the work history*. This last condition is for
|
||||||
|
sheets that have overlapping jobs.
|
||||||
|
*/
|
||||||
|
Sheet.prototype.duration = function() {
|
||||||
|
if( this.work && this.work.length ) {
|
||||||
|
var careerStart = this.work[ this.work.length - 1].safeStartDate;
|
||||||
|
if ((typeof careerStart === 'string' || careerStart instanceof String) &&
|
||||||
|
!careerStart.trim())
|
||||||
|
return 0;
|
||||||
|
var careerLast = _.max( this.work, function( w ) {
|
||||||
|
return w.safeEndDate.unix();
|
||||||
|
}).safeEndDate;
|
||||||
|
return careerLast.diff( careerStart, 'years' );
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
Sort dated things on the sheet by start date descending. Assumes that dates
|
||||||
|
on the sheet have been processed with _parseDates().
|
||||||
|
*/
|
||||||
|
Sheet.prototype.sort = function( ) {
|
||||||
|
|
||||||
|
this.work && this.work.sort( byDateDesc );
|
||||||
|
this.education && this.education.sort( byDateDesc );
|
||||||
|
this.volunteer && this.volunteer.sort( byDateDesc );
|
||||||
|
|
||||||
|
this.awards && this.awards.sort( function(a, b) {
|
||||||
|
return( a.safeDate.isBefore(b.safeDate) ) ? 1
|
||||||
|
: ( a.safeDate.isAfter(b.safeDate) && -1 ) || 0;
|
||||||
|
});
|
||||||
|
this.publications && this.publications.sort( function(a, b) {
|
||||||
|
return( a.safeReleaseDate.isBefore(b.safeReleaseDate) ) ? 1
|
||||||
|
: ( a.safeReleaseDate.isAfter(b.safeReleaseDate) && -1 ) || 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
function byDateDesc(a,b) {
|
||||||
|
return( a.safeStartDate.isBefore(b.safeStartDate) ) ? 1
|
||||||
|
: ( a.safeStartDate.isAfter(b.safeStartDate) && -1 ) || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
Convert human-friendly dates into formal Moment.js dates for all collections.
|
||||||
|
We don't want to lose the raw textual date as entered by the user, so we store
|
||||||
|
the Moment-ified date as a separate property with a prefix of .safe. For ex:
|
||||||
|
job.startDate is the date as entered by the user. job.safeStartDate is the
|
||||||
|
parsed Moment.js date that we actually use in processing.
|
||||||
|
*/
|
||||||
|
function _parseDates() {
|
||||||
|
|
||||||
|
var _fmt = require('./fluent-date').fmt;
|
||||||
|
|
||||||
|
this.work && this.work.forEach( function(job) {
|
||||||
|
job.safeStartDate = _fmt( job.startDate );
|
||||||
|
job.safeEndDate = _fmt( job.endDate );
|
||||||
|
});
|
||||||
|
this.education && this.education.forEach( function(edu) {
|
||||||
|
edu.safeStartDate = _fmt( edu.startDate );
|
||||||
|
edu.safeEndDate = _fmt( edu.endDate );
|
||||||
|
});
|
||||||
|
this.volunteer && this.volunteer.forEach( function(vol) {
|
||||||
|
vol.safeStartDate = _fmt( vol.startDate );
|
||||||
|
vol.safeEndDate = _fmt( vol.endDate );
|
||||||
|
});
|
||||||
|
this.awards && this.awards.forEach( function(awd) {
|
||||||
|
awd.safeDate = _fmt( awd.date );
|
||||||
|
});
|
||||||
|
this.publications && this.publications.forEach( function(pub) {
|
||||||
|
pub.safeReleaseDate = _fmt( pub.releaseDate );
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Export the Sheet function/ctor.
|
||||||
|
*/
|
||||||
|
module.exports = Sheet;
|
||||||
|
|
||||||
|
}());
|
90
src/core/theme.js
Normal file
90
src/core/theme.js
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
/**
|
||||||
|
Abstract theme representation.
|
||||||
|
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
|
||||||
|
var FS = require('fs')
|
||||||
|
, extend = require('../utils/extend')
|
||||||
|
, validator = require('is-my-json-valid')
|
||||||
|
, _ = require('underscore')
|
||||||
|
, PATH = require('path')
|
||||||
|
, moment = require('moment');
|
||||||
|
|
||||||
|
/**
|
||||||
|
The Theme class represents a specific presentation of a resume.
|
||||||
|
@class Theme
|
||||||
|
*/
|
||||||
|
function Theme() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Open and parse the specified theme.
|
||||||
|
*/
|
||||||
|
Theme.prototype.open = function( themeFolder ) {
|
||||||
|
|
||||||
|
function friendlyName( val ) {
|
||||||
|
val = val.trim().toLowerCase();
|
||||||
|
var friendly = { yml: 'yaml', md: 'markdown', txt: 'text' };
|
||||||
|
return friendly[val] || val;
|
||||||
|
}
|
||||||
|
|
||||||
|
var tplFolder = PATH.join( themeFolder, 'templates' );
|
||||||
|
var fmts = FS.readdirSync( tplFolder ).map( function( file ) {
|
||||||
|
var absPath = PATH.join( tplFolder, file );
|
||||||
|
var pathInfo = PATH.parse(absPath);
|
||||||
|
var temp = [ pathInfo.name, {
|
||||||
|
title: friendlyName(pathInfo.name),
|
||||||
|
pre: pathInfo.name,
|
||||||
|
ext: pathInfo.ext.slice(1),
|
||||||
|
path: absPath,
|
||||||
|
data: FS.readFileSync( absPath, 'utf8' ),
|
||||||
|
css: null
|
||||||
|
}];
|
||||||
|
return temp;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Freebie formats every theme gets
|
||||||
|
fmts.push( [ 'json', { title: 'json', pre: 'json', ext: 'json', path: null, data: null } ] );
|
||||||
|
fmts.push( [ 'yml', { title: 'yaml', pre: 'yml', ext: 'yml', path: null, data: null } ] );
|
||||||
|
|
||||||
|
// Handle CSS files
|
||||||
|
var cssFiles = fmts.filter(function( fmt ){
|
||||||
|
return fmt[1].ext === 'css';
|
||||||
|
});
|
||||||
|
cssFiles.forEach(function( cssf ) {
|
||||||
|
// For each CSS file, get its corresponding HTML file
|
||||||
|
var idx = _.findIndex(fmts, function( fmt ) { return fmt[1].pre === cssf[1].pre && fmt[1].ext === 'html' });
|
||||||
|
fmts[ idx ][1].css = cssf[1].data;
|
||||||
|
fmts[ idx ][1].cssPath = cssf[1].path;
|
||||||
|
});
|
||||||
|
fmts = fmts.filter( function( fmt) {
|
||||||
|
return fmt[1].ext !== 'css';
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create a hash out of the formats for this theme
|
||||||
|
this.formats = _.object( fmts );
|
||||||
|
|
||||||
|
this.name = PATH.parse( themeFolder ).name;
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
Determine if the theme supports the specified output format.
|
||||||
|
*/
|
||||||
|
Theme.prototype.hasFormat = function( fmt ) {
|
||||||
|
return _.has( this.formats, fmt );
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
Determine if the theme supports the specified output format.
|
||||||
|
*/
|
||||||
|
Theme.prototype.getFormat = function( fmt ) {
|
||||||
|
return this.formats[ fmt ];
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = Theme;
|
||||||
|
|
||||||
|
}());
|
@ -11,7 +11,7 @@ module.exports = function () {
|
|||||||
, unused = require('./utils/string')
|
, unused = require('./utils/string')
|
||||||
, fs = require('fs')
|
, fs = require('fs')
|
||||||
, _ = require('underscore')
|
, _ = require('underscore')
|
||||||
, FLUENT = require('fluentlib')
|
, FLUENT = require('./fluentlib')
|
||||||
, PATH = require('path')
|
, PATH = require('path')
|
||||||
, MKDIRP = require('mkdirp')
|
, MKDIRP = require('mkdirp')
|
||||||
, rez, _log, _err;
|
, rez, _log, _err;
|
||||||
|
17
src/fluentlib.js
Normal file
17
src/fluentlib.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
Core resume generation module for FluentCV.
|
||||||
|
@license Copyright (c) 2015 by James M. Devlin. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
Sheet: require('./core/sheet'),
|
||||||
|
Theme: require('./core/theme'),
|
||||||
|
HtmlGenerator: require('./gen/html-generator'),
|
||||||
|
TextGenerator: require('./gen/text-generator'),
|
||||||
|
HtmlPdfGenerator: require('./gen/html-pdf-generator'),
|
||||||
|
WordGenerator: require('./gen/word-generator'),
|
||||||
|
MarkdownGenerator: require('./gen/markdown-generator'),
|
||||||
|
JsonGenerator: require('./gen/json-generator'),
|
||||||
|
YamlGenerator: require('./gen/yaml-generator'),
|
||||||
|
JsonYamlGenerator: require('./gen/json-yaml-generator')
|
||||||
|
};
|
43
src/gen/base-generator.js
Normal file
43
src/gen/base-generator.js
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
/**
|
||||||
|
Base resume generator for FluentCV.
|
||||||
|
@license Copyright (c) 2015 | James M. Devlin
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
|
||||||
|
// Use J. Resig's nifty class implementation
|
||||||
|
var Class = require( '../utils/class' );
|
||||||
|
|
||||||
|
/**
|
||||||
|
The BaseGenerator class is the root of the generator hierarchy. Functionality
|
||||||
|
common to ALL generators lives here.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var BaseGenerator = module.exports = Class.extend({
|
||||||
|
|
||||||
|
/**
|
||||||
|
Base-class initialize.
|
||||||
|
*/
|
||||||
|
init: function( outputFormat ) {
|
||||||
|
this.format = outputFormat;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
Status codes.
|
||||||
|
*/
|
||||||
|
codes: {
|
||||||
|
success: 0,
|
||||||
|
themeNotFound: 1,
|
||||||
|
copyCss: 2,
|
||||||
|
resumeNotFound: 3
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
Generator options.
|
||||||
|
*/
|
||||||
|
opts: {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}());
|
32
src/gen/html-generator.js
Normal file
32
src/gen/html-generator.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
HTML resume generator for FluentCV.
|
||||||
|
@license Copyright (c) 2015 by James M. Devlin. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var TemplateGenerator = require('./template-generator');
|
||||||
|
var FS = require('fs-extra');
|
||||||
|
var HTML = require( 'html' );
|
||||||
|
|
||||||
|
var HtmlGenerator = module.exports = TemplateGenerator.extend({
|
||||||
|
|
||||||
|
init: function() {
|
||||||
|
this._super( 'html' );
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
Generate an HTML resume with optional pretty printing.
|
||||||
|
*/
|
||||||
|
onBeforeSave: function( mk, theme, outputFile ) {
|
||||||
|
var themeFile = theme.getFormat('html').path;
|
||||||
|
var cssSrc = themeFile.replace( /.html$/g, '.css' );
|
||||||
|
var cssDst = outputFile.replace( /.html$/g, '.css' );
|
||||||
|
var that = this;
|
||||||
|
FS.copySync( cssSrc, cssDst, { clobber: true }, function( e ) {
|
||||||
|
throw { fluenterror: that.codes.copyCss, data: [cssSrc,cssDst] };
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.opts.prettify ?
|
||||||
|
HTML.prettyPrint( mk, this.opts.prettify ) : mk;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
74
src/gen/html-pdf-generator.js
Normal file
74
src/gen/html-pdf-generator.js
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
/**
|
||||||
|
HTML-based PDF resume generator for FluentCV.
|
||||||
|
@license Copyright (c) 2015 by James M. Devlin. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var TemplateGenerator = require('./template-generator');
|
||||||
|
var FS = require('fs-extra');
|
||||||
|
var HTML = require( 'html' );
|
||||||
|
|
||||||
|
var HtmlPdfGenerator = module.exports = TemplateGenerator.extend({
|
||||||
|
|
||||||
|
init: function() {
|
||||||
|
this._super( 'pdf', 'html' );
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
Generate an HTML resume with optional pretty printing.
|
||||||
|
TODO: Avoid copying the CSS file to dest if we don't need to.
|
||||||
|
*/
|
||||||
|
onBeforeSave: function( mk, themeFile, outputFile ) {
|
||||||
|
// var cssSrc = themeFile.replace( /pdf\.html$/gi, 'html.css' );
|
||||||
|
// var cssDst = outputFile.replace( /\.pdf$/gi, '.css' );
|
||||||
|
// var that = this;
|
||||||
|
// FS.copySync( cssSrc, cssDst, { clobber: true }, function( e ) {
|
||||||
|
// if( e ) that.err( "Couldn't copy CSS file to destination: " + e);
|
||||||
|
// });
|
||||||
|
// return true ?
|
||||||
|
// HTML.prettyPrint( mk, { indent_size: 2 } ) : mk;
|
||||||
|
|
||||||
|
pdf(mk, outputFile);
|
||||||
|
return mk;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
Generate a PDF from HTML.
|
||||||
|
*/
|
||||||
|
function pdf( markup, fOut ) {
|
||||||
|
|
||||||
|
var pdfCount = 0;
|
||||||
|
if( false ) { //( _opts.pdf === 'phantom' || _opts.pdf == 'all' ) {
|
||||||
|
pdfCount++;
|
||||||
|
require('phantom').create( function( ph ) {
|
||||||
|
ph.createPage( function( page ) {
|
||||||
|
page.setContent( markup );
|
||||||
|
page.set('paperSize', {
|
||||||
|
format: 'A4',
|
||||||
|
orientation: 'portrait',
|
||||||
|
margin: '1cm'
|
||||||
|
});
|
||||||
|
page.set("viewportSize", {
|
||||||
|
width: 1024, // TODO: option-ify
|
||||||
|
height: 768 // TODO: Use "A" sizes
|
||||||
|
});
|
||||||
|
page.set('onLoadFinished', function(success) {
|
||||||
|
page.render( fOut );
|
||||||
|
pdfCount++;
|
||||||
|
ph.exit();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{ dnodeOpts: { weak: false } } );
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if( true ) { // _opts.pdf === 'wkhtmltopdf' || _opts.pdf == 'all' ) {
|
||||||
|
var fOut2 = fOut;
|
||||||
|
if( pdfCount == 1 ) {
|
||||||
|
fOut2 = fOut2.replace(/\.pdf$/g, '.b.pdf');
|
||||||
|
}
|
||||||
|
require('wkhtmltopdf')( markup, { pageSize: 'letter' } )
|
||||||
|
.pipe( FS.createWriteStream( fOut2 ) );
|
||||||
|
pdfCount++;
|
||||||
|
}
|
||||||
|
}
|
35
src/gen/json-generator.js
Normal file
35
src/gen/json-generator.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
/**
|
||||||
|
Definition of the JsonGenerator class.
|
||||||
|
@license Copyright (c) 2015 | James M. Devlin
|
||||||
|
*/
|
||||||
|
|
||||||
|
var BaseGenerator = require('./base-generator');
|
||||||
|
var FS = require('fs');
|
||||||
|
var _ = require('underscore');
|
||||||
|
|
||||||
|
/**
|
||||||
|
The JsonGenerator generates a JSON resume directly.
|
||||||
|
*/
|
||||||
|
var JsonGenerator = module.exports = BaseGenerator.extend({
|
||||||
|
|
||||||
|
init: function(){
|
||||||
|
this._super( 'json' );
|
||||||
|
},
|
||||||
|
|
||||||
|
invoke: function( rez ) {
|
||||||
|
// TODO: merge with FCVD
|
||||||
|
function replacer( key,value ) { // Exclude these keys from stringification
|
||||||
|
return _.some(['meta', 'warnings', 'computed', 'filt', 'ctrl', 'index',
|
||||||
|
'safeStartDate', 'safeEndDate', 'safeDate', 'safeReleaseDate', 'result',
|
||||||
|
'isModified', 'htmlPreview'],
|
||||||
|
function( val ) { return key.trim() === val; }
|
||||||
|
) ? undefined : value;
|
||||||
|
}
|
||||||
|
return JSON.stringify( rez, replacer, 2 );
|
||||||
|
},
|
||||||
|
|
||||||
|
generate: function( rez, f ) {
|
||||||
|
FS.writeFileSync( f, this.invoke(rez), 'utf8' );
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
37
src/gen/json-yaml-generator.js
Normal file
37
src/gen/json-yaml-generator.js
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
A JSON-driven YAML resume generator for FluentLib.
|
||||||
|
@module json-yaml-generator.js
|
||||||
|
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
|
||||||
|
var BaseGenerator = require('./base-generator');
|
||||||
|
var FS = require('fs');
|
||||||
|
var YAML = require('yamljs');
|
||||||
|
|
||||||
|
/**
|
||||||
|
JsonYamlGenerator takes a JSON resume object and translates it directly to
|
||||||
|
JSON without a template, producing an equivalent YAML-formatted resume. See
|
||||||
|
also YamlGenerator (yaml-generator.js).
|
||||||
|
*/
|
||||||
|
|
||||||
|
var JsonYamlGenerator = module.exports = BaseGenerator.extend({
|
||||||
|
|
||||||
|
init: function(){
|
||||||
|
this._super( 'yml' );
|
||||||
|
},
|
||||||
|
|
||||||
|
invoke: function( rez, themeMarkup, cssInfo, opts ) {
|
||||||
|
return YAML.stringify( JSON.parse( rez.stringify() ), Infinity, 2 );
|
||||||
|
},
|
||||||
|
|
||||||
|
generate: function( rez, f, opts ) {
|
||||||
|
var data = YAML.stringify( JSON.parse( rez.stringify() ), Infinity, 2 );
|
||||||
|
FS.writeFileSync( f, data, 'utf8' );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}());
|
17
src/gen/markdown-generator.js
Normal file
17
src/gen/markdown-generator.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
Markdown resume generator for FluentCV.
|
||||||
|
@license Copyright (c) 2015 by James M. Devlin. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var TemplateGenerator = require('./template-generator');
|
||||||
|
|
||||||
|
/**
|
||||||
|
MarkdownGenerator generates a Markdown-formatted resume via TemplateGenerator.
|
||||||
|
*/
|
||||||
|
var MarkdownGenerator = module.exports = TemplateGenerator.extend({
|
||||||
|
|
||||||
|
init: function(){
|
||||||
|
this._super( 'md', 'txt' );
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
173
src/gen/template-generator.js
Normal file
173
src/gen/template-generator.js
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
/**
|
||||||
|
Template-based resume generator base for FluentCV.
|
||||||
|
@license Copyright (c) 2015 | James M. Devlin
|
||||||
|
*/
|
||||||
|
|
||||||
|
var FS = require( 'fs' )
|
||||||
|
, _ = require( 'underscore' )
|
||||||
|
, MD = require( 'marked' )
|
||||||
|
, XML = require( 'xml-escape' )
|
||||||
|
, PATH = require('path')
|
||||||
|
, BaseGenerator = require( './base-generator' )
|
||||||
|
, EXTEND = require('../utils/extend')
|
||||||
|
, Theme = require('../core/theme');
|
||||||
|
|
||||||
|
// Default options.
|
||||||
|
var _defaultOpts = {
|
||||||
|
themeRelative: '../../node_modules/watermark/themes',
|
||||||
|
keepBreaks: true,
|
||||||
|
freezeBreaks: true,
|
||||||
|
nSym: '&newl;', // newline entity
|
||||||
|
rSym: '&retn;', // return entity
|
||||||
|
template: {
|
||||||
|
interpolate: /\{\{(.+?)\}\}/g,
|
||||||
|
escape: /\{\{\=(.+?)\}\}/g,
|
||||||
|
evaluate: /\{\%(.+?)\%\}/g,
|
||||||
|
comment: /\{\#(.+?)\#\}/g
|
||||||
|
},
|
||||||
|
filters: {
|
||||||
|
out: function( txt ) { return txt; },
|
||||||
|
raw: function( txt ) { return txt; },
|
||||||
|
xml: function( txt ) { return XML(txt); },
|
||||||
|
md: function( txt ) { return MD(txt); },
|
||||||
|
mdin: function( txt ) { return MD(txt).replace(/^\s*\<p\>|\<\/p\>\s*$/gi, ''); },
|
||||||
|
lower: function( txt ) { return txt.toLowerCase(); }
|
||||||
|
},
|
||||||
|
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
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
TemplateGenerator performs resume generation via Underscore-style template
|
||||||
|
expansion and is appropriate for text-based formats like HTML, plain text,
|
||||||
|
and XML versions of Microsoft Word, Excel, and OpenOffice.
|
||||||
|
*/
|
||||||
|
var TemplateGenerator = module.exports = BaseGenerator.extend({
|
||||||
|
|
||||||
|
/** outputFormat: html, txt, pdf, doc
|
||||||
|
templateFormat: html or txt
|
||||||
|
**/
|
||||||
|
init: function( outputFormat, templateFormat, cssFile ){
|
||||||
|
this._super( outputFormat );
|
||||||
|
this.tplFormat = templateFormat || outputFormat;
|
||||||
|
},
|
||||||
|
|
||||||
|
/** Default generation method for template-based generators. */
|
||||||
|
invoke: function( rez, themeMarkup, cssInfo, opts ) {
|
||||||
|
|
||||||
|
// Compile and invoke the template!
|
||||||
|
this.opts = EXTEND( true, {}, _defaultOpts, opts );
|
||||||
|
mk = this.single( rez, themeMarkup, this.format, cssInfo, { } );
|
||||||
|
this.onBeforeSave && (mk = this.onBeforeSave( mk, themeFile, f ));
|
||||||
|
return mk;
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
/** Default generation method for template-based generators. */
|
||||||
|
generate: function( rez, f, opts ) {
|
||||||
|
|
||||||
|
// Carry over options
|
||||||
|
this.opts = EXTEND( true, { }, _defaultOpts, opts );
|
||||||
|
|
||||||
|
// Verify the specified theme name/path
|
||||||
|
var tFolder = PATH.resolve( __dirname, this.opts.themeRelative, this.opts.theme );
|
||||||
|
var exists = require('../utils/file-exists');
|
||||||
|
if (!exists( tFolder )) {
|
||||||
|
tFolder = PATH.resolve( this.opts.theme );
|
||||||
|
if (!exists( tFolder )) {
|
||||||
|
throw { fluenterror: this.codes.themeNotFound, data: this.opts.theme };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the theme
|
||||||
|
var theme = opts.themeObj || new Theme().open( tFolder );
|
||||||
|
|
||||||
|
// Load theme and CSS data
|
||||||
|
var tplFolder = PATH.join( tFolder, 'templates' );
|
||||||
|
var curFmt = theme.getFormat( this.format );
|
||||||
|
var ctx = { file: curFmt.css ? curFmt.cssPath : null, data: curFmt.css || null };
|
||||||
|
|
||||||
|
// Compile and invoke the template!
|
||||||
|
var mk = this.single( rez, curFmt.data, this.format, ctx, opts );
|
||||||
|
this.onBeforeSave && (mk = this.onBeforeSave( mk, theme, f ));
|
||||||
|
FS.writeFileSync( f, mk, { encoding: 'utf8', flags: 'w' } );
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
Perform a single resume JSON-to-DEST resume transformation. Exists as a
|
||||||
|
separate function in order to expose string-based transformations to clients
|
||||||
|
who don't have access to filesystem resources (in-browser, etc.).
|
||||||
|
*/
|
||||||
|
single: function( json, jst, format, cssInfo, opts ) {
|
||||||
|
|
||||||
|
// Freeze whitespace in the template.
|
||||||
|
this.opts.freezeBreaks && ( jst = freeze(jst) );
|
||||||
|
|
||||||
|
// Tweak underscore's default template delimeters
|
||||||
|
_.templateSettings = this.opts.template;
|
||||||
|
|
||||||
|
// Convert {{ someVar }} to {% print(filt.out(someVar) %}
|
||||||
|
// Convert {{ someVar|someFilter }} to {% print(filt.someFilter(someVar) %}
|
||||||
|
jst = jst.replace( _.templateSettings.interpolate, function replace(m, p1) {
|
||||||
|
if( p1.indexOf('|') > -1 ) {
|
||||||
|
var terms = p1.split('|');
|
||||||
|
return '{% print( filt.' + terms[1] + '( ' + terms[0] + ' )) %}';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return '{% print( filt.out(' + p1 + ') ) %}';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Strip {# comments #}
|
||||||
|
jst = jst.replace( _.templateSettings.comment, '');
|
||||||
|
json.display_progress_bar = true;
|
||||||
|
|
||||||
|
// Compile and run the template. TODO: avoid unnecessary recompiles.
|
||||||
|
jst = _.template(jst)({ r: json, filt: this.opts.filters, cssInfo: cssInfo, headFragment: this.opts.headFragment || '' });
|
||||||
|
|
||||||
|
// Unfreeze whitespace
|
||||||
|
this.opts.freezeBreaks && ( jst = unfreeze(jst) );
|
||||||
|
|
||||||
|
return jst;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
Export the TemplateGenerator function/ctor.
|
||||||
|
*/
|
||||||
|
module.exports = TemplateGenerator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Freeze newlines for protection against errant JST parsers.
|
||||||
|
*/
|
||||||
|
function freeze( markup ) {
|
||||||
|
return markup
|
||||||
|
.replace( _reg.regN, _defaultOpts.nSym )
|
||||||
|
.replace( _reg.regR, _defaultOpts.rSym );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Unfreeze newlines when the coast is clear.
|
||||||
|
*/
|
||||||
|
function unfreeze( markup ) {
|
||||||
|
return markup
|
||||||
|
.replace( _reg.regSymR, '\r' )
|
||||||
|
.replace( _reg.regSymN, '\n' );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Regexes for linebreak preservation.
|
||||||
|
*/
|
||||||
|
var _reg = {
|
||||||
|
regN: new RegExp( '\n', 'g' ),
|
||||||
|
regR: new RegExp( '\r', 'g' ),
|
||||||
|
regSymN: new RegExp( _defaultOpts.nSym, 'g' ),
|
||||||
|
regSymR: new RegExp( _defaultOpts.rSym, 'g' )
|
||||||
|
};
|
19
src/gen/text-generator.js
Normal file
19
src/gen/text-generator.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
/**
|
||||||
|
Plain text resume generator for FluentCV.
|
||||||
|
@license Copyright (c) 2015 by James M. Devlin. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var TemplateGenerator = require('./template-generator');
|
||||||
|
|
||||||
|
/**
|
||||||
|
The TextGenerator generates a plain-text resume via the TemplateGenerator.
|
||||||
|
*/
|
||||||
|
var TextGenerator = TemplateGenerator.extend({
|
||||||
|
|
||||||
|
init: function(){
|
||||||
|
this._super( 'txt' );
|
||||||
|
},
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = TextGenerator;
|
13
src/gen/word-generator.js
Normal file
13
src/gen/word-generator.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
MS Word resume generator for FluentCV.
|
||||||
|
@license Copyright (c) 2015 by James M. Devlin. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var TemplateGenerator = require('./template-generator');
|
||||||
|
var WordGenerator = module.exports = TemplateGenerator.extend({
|
||||||
|
|
||||||
|
init: function(){
|
||||||
|
this._super( 'doc', 'xml' );
|
||||||
|
},
|
||||||
|
|
||||||
|
});
|
17
src/gen/xml-generator.js
Normal file
17
src/gen/xml-generator.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
XML resume generator for FluentCV.
|
||||||
|
@license Copyright (c) 2015 | James M. Devlin
|
||||||
|
*/
|
||||||
|
|
||||||
|
var BaseGenerator = require('./base-generator');
|
||||||
|
|
||||||
|
/**
|
||||||
|
The XmlGenerator generates an XML resume via the TemplateGenerator.
|
||||||
|
*/
|
||||||
|
var XmlGenerator = module.exports = BaseGenerator.extend({
|
||||||
|
|
||||||
|
init: function(){
|
||||||
|
this._super( 'xml' );
|
||||||
|
},
|
||||||
|
|
||||||
|
});
|
24
src/gen/yaml-generator.js
Normal file
24
src/gen/yaml-generator.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
A YAML resume generator for FluentLib.
|
||||||
|
@module yaml-generator.js
|
||||||
|
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
|
||||||
|
var TemplateGenerator = require('./template-generator');
|
||||||
|
|
||||||
|
/**
|
||||||
|
YamlGenerator generates a YAML-formatted resume via TemplateGenerator.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var YamlGenerator = module.exports = TemplateGenerator.extend({
|
||||||
|
|
||||||
|
init: function(){
|
||||||
|
this._super( 'yml', 'yml' );
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}());
|
67
src/utils/class.js
Normal file
67
src/utils/class.js
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
/* Simple JavaScript Inheritance
|
||||||
|
* By John Resig http://ejohn.org/
|
||||||
|
* MIT Licensed.
|
||||||
|
* http://ejohn.org/blog/simple-javascript-inheritance/
|
||||||
|
*/
|
||||||
|
// Inspired by base2 and Prototype
|
||||||
|
(function(){
|
||||||
|
var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
|
||||||
|
|
||||||
|
// The base Class implementation (does nothing)
|
||||||
|
this.Class = function(){};
|
||||||
|
module.exports = Class;
|
||||||
|
|
||||||
|
// Create a new Class that inherits from this class
|
||||||
|
Class.extend = function(prop) {
|
||||||
|
var _super = this.prototype;
|
||||||
|
|
||||||
|
// Instantiate a base class (but only create the instance,
|
||||||
|
// don't run the init constructor)
|
||||||
|
initializing = true;
|
||||||
|
var prototype = new this();
|
||||||
|
initializing = false;
|
||||||
|
|
||||||
|
// Copy the properties over onto the new prototype
|
||||||
|
for (var name in prop) {
|
||||||
|
// Check if we're overwriting an existing function
|
||||||
|
prototype[name] = typeof prop[name] == "function" &&
|
||||||
|
typeof _super[name] == "function" && fnTest.test(prop[name]) ?
|
||||||
|
(function(name, fn){
|
||||||
|
return function() {
|
||||||
|
var tmp = this._super;
|
||||||
|
|
||||||
|
// Add a new ._super() method that is the same method
|
||||||
|
// but on the super-class
|
||||||
|
this._super = _super[name];
|
||||||
|
|
||||||
|
// The method only need to be bound temporarily, so we
|
||||||
|
// remove it when we're done executing
|
||||||
|
var ret = fn.apply(this, arguments);
|
||||||
|
this._super = tmp;
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
})(name, prop[name]) :
|
||||||
|
prop[name];
|
||||||
|
}
|
||||||
|
|
||||||
|
// The dummy class constructor
|
||||||
|
function Class() {
|
||||||
|
// All construction is actually done in the init method
|
||||||
|
if ( !initializing && this.init )
|
||||||
|
this.init.apply(this, arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate our constructed prototype object
|
||||||
|
Class.prototype = prototype;
|
||||||
|
|
||||||
|
// Enforce the constructor to be what we expect
|
||||||
|
Class.prototype.constructor = Class;
|
||||||
|
|
||||||
|
// And make this class extendable
|
||||||
|
Class.extend = arguments.callee;
|
||||||
|
|
||||||
|
return Class;
|
||||||
|
};
|
||||||
|
|
||||||
|
})();
|
@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
Plain JavaScript replacement of jQuery .extend based on jQuery sources.
|
Plain JavaScript replacement of jQuery .extend based on jQuery sources.
|
||||||
@license Copyright (c) 2015 | James M. Devlin
|
@license Copyright (c) 2015 by James M. Devlin. All rights reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function _extend() {
|
function _extend() {
|
||||||
|
67
tests/test-sheet.js
Normal file
67
tests/test-sheet.js
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
|
||||||
|
var chai = require('chai')
|
||||||
|
, expect = chai.expect
|
||||||
|
, should = chai.should()
|
||||||
|
, path = require('path')
|
||||||
|
, _ = require('underscore')
|
||||||
|
, Sheet = require('../src/core/sheet')
|
||||||
|
, validator = require('is-my-json-valid');
|
||||||
|
|
||||||
|
chai.config.includeStack = false;
|
||||||
|
|
||||||
|
describe('fullstack.json', function () {
|
||||||
|
|
||||||
|
var _sheet;
|
||||||
|
|
||||||
|
it('should open without throwing an exception', function () {
|
||||||
|
function tryOpen() {
|
||||||
|
_sheet = new Sheet().open( 'node_modules/resample/fullstack/in/resume.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 );
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have a work duration of 11 years', function() {
|
||||||
|
_sheet.computed.numYears.should.equal( 11 );
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should save without throwing an exception', function(){
|
||||||
|
function trySave() {
|
||||||
|
_sheet.save( 'tests/sandbox/fullstack.json' );
|
||||||
|
}
|
||||||
|
trySave.should.not.Throw();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not be modified after saving', function() {
|
||||||
|
var savedSheet = new Sheet().open( 'tests/sandbox/fullstack.json' );
|
||||||
|
_sheet.stringify().should.equal( savedSheet.stringify() )
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should validate against the JSON Resume schema', function() {
|
||||||
|
var schemaJson = require('../src/core/resume.json');
|
||||||
|
var validate = validator( schemaJson, { verbose: true } );
|
||||||
|
var result = validate( JSON.parse( _sheet.meta.raw ) );
|
||||||
|
result || console.log("\n\nOops, resume didn't validate. " +
|
||||||
|
"Validation errors:\n\n", validate.errors, "\n\n");
|
||||||
|
result.should.equal( true );
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
// describe('subtract', function () {
|
||||||
|
// it('should return -1 when passed the params (1, 2)', function () {
|
||||||
|
// expect(math.subtract(1, 2)).to.equal(-1);
|
||||||
|
// });
|
||||||
|
// });
|
Loading…
Reference in New Issue
Block a user