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

Compare commits

...

54 Commits

Author SHA1 Message Date
36f8010ebc Merge pull request #24 from fluentdesk/feat-notices
feat-notices
2015-12-17 10:27:58 -05:00
e80d8fb5c8 Bump version. 2015-12-17 10:29:22 -05:00
eabab26eef Update file headers. 2015-12-17 10:15:59 -05:00
18dbb23168 Merge pull request #23 from fluentdesk/v0.10.2
v0.10.2
2015-12-17 07:52:21 -05:00
9ad2a1e92e Add simple-html-tokenizer dependency. 2015-12-16 23:28:57 -05:00
5475b081b1 Support basic Markdown in MS Word docs. 2015-12-16 23:26:53 -05:00
ae9c295ce1 Better Handlebars support. 2015-12-16 20:13:27 -05:00
e0ef774692 Add missing semicolon. 2015-12-16 12:13:50 -05:00
0c1364593a Support post-save callback. 2015-12-16 11:26:30 -05:00
1603a4bc73 Prosecute FCVD updates. 2015-12-16 11:25:50 -05:00
8361cf9960 Remove extraneous comma.
Go away, extraneous comma.
2015-12-16 11:25:04 -05:00
8273e7d150 ... 2015-12-15 06:20:06 -05:00
5c49a8297f Fix: FCVD error. 2015-12-14 07:32:41 -05:00
ee1e4bf699 Scrub. 2015-12-12 11:13:47 -05:00
f5a8e36e50 Refactor theme tests. 2015-12-12 11:13:37 -05:00
b38a7c1da2 Improve conversions and tests. 2015-12-12 10:48:26 -05:00
fe2247329e Bump version to 0.10.2. 2015-12-12 04:43:29 -05:00
9d459370ce Update FRESH<-->JRS converter. 2015-12-12 04:42:56 -05:00
201d96fe22 Merge pull request #22 from fluentdesk/v0.10.1
v0.10.1
2015-12-11 22:29:58 -05:00
8747429bc6 Update NPM registry description. 2015-12-11 22:35:26 -05:00
95540efe29 Tweak FRESCA and theme dependency versions. 2015-12-11 04:02:05 -05:00
0474dc7dbe Bump version to 0.10.1. 2015-12-11 03:04:34 -05:00
e5af6c38e0 Merge remote-tracking branch 'refs/remotes/origin/master' into v0.10.1 2015-12-11 03:01:31 -05:00
00e6407347 Kludge theme loading issue for FCVD.
Resolve in v0.11.0.
2015-12-10 10:28:19 -05:00
3805a36271 Fix folder generation wrinkle. 2015-12-09 23:30:53 -05:00
a3cefa1c42 Merge pull request #20 from fluentdesk/v0.10.0
v0.10.0
2015-12-09 23:28:25 -05:00
81276cf2cc Update README. 2015-12-09 23:09:33 -05:00
541198321e Fix JSHint warnings. 2015-12-09 21:44:35 -05:00
91aba39050 Add file LINTing through JSHint. 2015-12-09 21:44:18 -05:00
f7a3da0a4d Add generator tests for all themes. 2015-12-09 05:41:04 -05:00
0395792359 Restore canonical output filename. 2015-12-09 05:08:10 -05:00
2abfe4426c Refactor. 2015-12-09 04:32:48 -05:00
3dcf3c3974 Tweak Markdownification. 2015-12-09 04:32:39 -05:00
857de65750 More MEGADESK. 2015-12-09 00:13:58 -05:00
f3c9f92263 Add baseline Markdownification. 2015-12-08 22:22:33 -05:00
e8704e1374 Fix file generation glitch. 2015-12-08 22:22:14 -05:00
87c03b437c Generate safe date times; don't hard-code. 2015-12-08 22:21:42 -05:00
1a757e8a87 Bump FRESCA version to 0.2.0. 2015-12-08 21:12:19 -05:00
7c58f0ea96 Add symlink support. 2015-12-08 10:13:04 -05:00
fcaeb381fe Gather. 2015-12-07 21:24:14 -05:00
5a716dff16 Add basic multiplexing support. 2015-12-07 16:39:59 -05:00
8ee2716245 Scrub theme.js. 2015-12-07 10:16:38 -05:00
5f19f0a7df Add baseline support for multifile themes. #rough 2015-12-07 09:51:00 -05:00
cf25621679 Introduce placeholder LaTeX generator. 2015-12-06 18:29:16 -05:00
228f14d06c Support recursive theme template loading. 2015-12-06 18:19:33 -05:00
307c37dc44 Use "src" subfolder instead of "templates". 2015-12-06 18:18:36 -05:00
3b8d100f39 Add baseline Handlebars support. 2015-12-06 16:19:55 -05:00
fb783cdbc6 Add Handlebars dependency. 2015-12-06 14:57:20 -05:00
e4d098a3ce Add safety for implicit Markdown. 2015-12-06 05:51:03 -05:00
263f224e1b Bump fluent-themes version to 0.6.0-beta. 2015-12-06 05:50:14 -05:00
92ca11f23c Adjust output. 2015-12-02 15:10:38 -05:00
5b3a25c461 Support NEW command. 2015-12-02 14:56:36 -05:00
2431ae4d89 Bump version to 0.10.0. 2015-12-02 14:56:06 -05:00
2a8f0196b4 Update LICENSE author. 2015-12-01 14:33:49 -05:00
40 changed files with 2018 additions and 788 deletions

View File

@ -1,7 +1,7 @@
'use strict';
module.exports = function (grunt) {
'use strict';
var opts = {
pkg: grunt.file.readJSON('package.json'),
@ -29,19 +29,28 @@ module.exports = function (grunt) {
outdir: 'docs/'
}
}
},
jshint: {
options: {
laxcomma: true,
expr: true
},
all: ['Gruntfile.js', 'src/**/*.js', 'test/**/*.js']
}
};
grunt.initConfig( opts );
grunt.loadNpmTasks('grunt-simple-mocha');
grunt.loadNpmTasks('grunt-contrib-yuidoc');
grunt.registerTask('test', 'Test the FluentCV library.', function( config ) {
grunt.task.run( ['simplemocha:all'] );
});
grunt.registerTask('document', 'Generate FluentCV library documentation.', function( config ) {
grunt.task.run( ['yuidoc'] );
});
grunt.registerTask('default', [ 'test', 'yuidoc' ]);
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.registerTask('test', 'Test the FluentCV library.',
function( config ) { grunt.task.run( ['simplemocha:all'] ); });
grunt.registerTask('document', 'Generate FluentCV library documentation.',
function( config ) { grunt.task.run( ['yuidoc'] ); });
grunt.registerTask('default', [ 'jshint', 'test', 'yuidoc' ]);
};

View File

@ -1,7 +1,7 @@
The MIT License
===============
Copyright (c) 2015 James M. Devlin (https://github.com/devlinjd)
Copyright (c) 2015 James M. Devlin (https://github.com/hacksalot)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -1,47 +1,51 @@
fluentCV
========
*Generate beautiful, targeted resumes from your command line or shell.*
*Create polished technical résumés and CVs in multiple formats from your command
line or shell. See [FluentCV Desktop][7] for the desktop version. OS X ~ Windows
~ Linux.*
![](assets/fluentcv_cli_ubuntu.png)
FluentCV is a Swiss Army knife for resumes and CVs. Use it to:
FluentCV is a dev-friendly Swiss Army knife for resumes and CVs. Use it to:
1. **Generate** polished multiformat resumes in HTML, Word, Markdown, PDF, plain
text, JSON, and YAML formats&mdash;without violating DRY.
1. **Generate** HTML, Markdown, LaTeX, MS Word, PDF, plain text, JSON, XML,
YAML, print, smoke signal, carrier pigeon, and other arbitrary-format resumes
and CVs, from a single source of truth&mdash;without violating DRY.
2. **Convert** resumes between [FRESH][fresca] and [JSON Resume][6] formats.
3. **Validate** resumes against either format.
Install it with NPM:
FluentCV supports both the [FRESH][fresca] and [JSON Resume][6] source formats.
## Features
- OS X, Linux, and Windows.
- Store your resume data as a durable, versionable JSON or YAML document.
- Generate polished resumes in multiple formats without violating [DRY][dry].
- Output to HTML, Markdown, LaTeX, PDF, MS Word, JSON, YAML, plain text, or XML.
- Compatible with [FRESH][fresh], [JSON Resume][6], [FRESCA][fresca], and
[FCV Desktop][7].
- Validate resumes against the FRESH or JSON Resume schema.
- Support for multiple input and output resumes.
- Use from your command line or [desktop][7].
- Free and open-source through the MIT license.
## Install
Install FluentCV with NPM:
```bash
[sudo] npm install fluentcv -g
```
Note: for PDF generation you'll need to install a copy of [wkhtmltopdf][3] for
your platform.
## Features
- Runs on OS X, Linux, and Windows.
- Store your resume data as a durable, versionable JSON or YAML document.
- Generate polished resumes in multiple formats without violating [DRY][dry].
- Output to HTML, PDF, Markdown, MS Word, JSON, YAML, plain text, or XML.
- Compatible with [FRESH][fresh], [JSON Resume][6], [FRESCA][fresca], and
[FCV Desktop][7].
- Validate resumes against the FRESH or JSON Resume schema.
- Support for multiple input and output resumes.
- Free and open-source through the MIT license.
- Forthcoming: StackOverflow and LinkedIn support.
- Forthcoming: More commands and themes.
Looking for a desktop GUI version for Windows / OS X / Linux? Check out
[FluentCV Desktop][7].
your platform. For LaTeX generation you'll need a valid LaTeX environment with
access to `xelatex` and similar.
## Getting Started
To use FluentCV you'll need to create a valid resume in either [FRESH][fresca]
or [JSON Resume][6] format. Then you can start using the command line tool.
There are three commands you should be aware of:
There are four basic commands you should be aware of:
- `build` generates resumes in HTML, Word, Markdown, PDF, and other formats. Use
it when you need to submit, upload, print, or email resumes in specific formats.
@ -52,6 +56,15 @@ it when you need to submit, upload, print, or email resumes in specific formats.
fluentcv BUILD r1.json r2.json TO out/rez.html out/rez.md foo/rez.all
```
- `new` creates a new resume in FRESH or JSON Resume format.
```bash
# fluentcv NEW <OUTPUTS> [-f <FORMAT>]
fluentcv NEW resume.json
fluentcv NEW resume.json -f fresh
fluentcv NEW r1.json r2.json -f jrs
```
- `convert` converts your source resume between FRESH and JSON Resume formats.
Use it to convert between the two formats to take advantage of tools and services.
@ -78,6 +91,7 @@ Output Format | Ext | Notes
------------- | --- | -----
HTML | .html | A standard HTML 5 + CSS resume format that can be viewed in a browser, deployed to a website, etc.
Markdown | .md | A structured Markdown document that can be used as-is or used to generate HTML.
LaTeX | .tex | A structured LaTeX document (or collection of documents).
MS Word | .doc | A Microsoft Word office document.
Adobe Acrobat (PDF) | .pdf | A binary PDF document driven by an HTML theme.
plain text | .txt | A formatted plain text document appropriate for emails or copy-paste.
@ -92,6 +106,7 @@ image | .png, .bmp | Forthcoming.
FluentCV requires a recent version of [Node.js][4] and [NPM][5]. Then:
1. Install the latest official [wkhtmltopdf][3] binary for your platform.
2. Optionally install an updated LaTeX environment (LaTeX resumes only).
2. Install **fluentCV** with `[sudo] npm install fluentcv -g`.
3. You're ready to go.

View File

@ -1,7 +1,7 @@
{
"name": "fluentcv",
"version": "0.9.1",
"description": "Generate beautiful, targeted resumes from your command line, shell, or in Javascript with Node.js.",
"version": "0.10.3",
"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",
"url": "https://github.com/fluentdesk/fluentcv.git"
@ -32,10 +32,11 @@
},
"homepage": "https://github.com/fluentdesk/fluentcv",
"dependencies": {
"FRESCA": "fluentdesk/FRESCA#v0.1.0",
"colors": "^1.1.2",
"fluent-themes": "0.5.1-beta",
"fluent-themes": "~0.6.3-beta",
"fresca": "~0.2.1",
"fs-extra": "^0.24.0",
"handlebars": "^4.0.5",
"html": "0.0.10",
"is-my-json-valid": "^2.12.2",
"jst": "0.0.13",
@ -43,6 +44,8 @@
"minimist": "^1.2.0",
"mkdirp": "^0.5.1",
"moment": "^2.10.6",
"recursive-readdir-sync": "^1.0.6",
"simple-html-tokenizer": "^0.2.0",
"underscore": "^1.8.3",
"wkhtmltopdf": "^0.1.5",
"xml-escape": "^1.0.0",
@ -51,6 +54,7 @@
"devDependencies": {
"chai": "*",
"grunt": "*",
"grunt-contrib-jshint": "^0.11.3",
"grunt-contrib-yuidoc": "^0.10.0",
"grunt-simple-mocha": "*",
"is-my-json-valid": "^2.12.2",

View File

@ -1,6 +1,7 @@
/**
FRESH to JSON Resume conversion routiens.
@license MIT. Copyright (c) 2015 James M. Devlin / FluentDesk
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
@module convert.js
*/
(function(){
@ -14,6 +15,7 @@ FRESH to JSON Resume conversion routiens.
/**
Convert from JSON Resume format to FRESH.
@method toFresh
*/
toFRESH: function( src, foreign ) {
@ -47,87 +49,15 @@ FRESH to JSON Resume conversion routiens.
address: src.basics.location.address
},
employment: {
history: src.work.map( function( job ) {
return {
position: job.position,
employer: job.company,
summary: job.summary,
current: (!job.endDate || !job.endDate.trim() || job.endDate.trim().toLowerCase() === 'current') || undefined,
start: job.startDate,
end: job.endDate,
url: job.website,
keywords: "",
highlights: job.highlights
};
})
},
education: {
history: src.education.map(function(edu){
return {
institution: edu.institution,
start: edu.startDate,
end: edu.endDate,
grade: edu.gpa,
curriculum: edu.courses,
url: edu.website || edu.url || null,
summary: null,
area: edu.area,
studyType: edu.studyType
};
})
},
service: {
history: src.volunteer.map(function(vol) {
return {
type: 'volunteer',
position: vol.position,
organization: vol.organization,
start: vol.startDate,
end: vol.endDate,
url: vol.website,
summary: vol.summary,
highlights: vol.highlights
};
})
},
employment: employment( src.work, true ),
education: education( src.education, true),
service: service( src.volunteer, true),
skills: skillsToFRESH( src.skills ),
writing: src.publications.map(function(pub){
return {
title: pub.name,
flavor: undefined,
publisher: pub.publisher,
url: pub.website,
date: pub.releaseDate,
summary: pub.summary
};
}),
recognition: src.awards.map(function(awd){
return {
title: awd.title,
date: awd.date,
summary: awd.summary,
from: awd.awarder,
url: null
};
}),
social: src.basics.profiles.map(function(pro){
return {
label: pro.network,
network: pro.network,
url: pro.url,
user: pro.username
};
}),
writing: writing( src.publications, true),
recognition: recognition( src.awards, true, foreign ),
social: social( src.basics.profiles, true ),
interests: src.interests,
references: src.references,
testimonials: references( src.references, true ),
languages: src.languages,
disposition: src.disposition // <--> round-trip
};
@ -160,77 +90,17 @@ FRESH to JSON Resume conversion routiens.
countryCode: src.location.country,
region: src.location.region
},
profiles: src.social.map(function(soc){
return {
network: soc.network,
username: soc.user,
url: soc.url
};
})
profiles: social( src.social, false )
},
work: src.employment.history.map(function(emp){
return {
company: emp.employer,
website: emp.url,
position: emp.position,
startDate: emp.start,
endDate: emp.end,
summary: emp.summary,
highlights: emp.highlights
};
}),
education: src.education.history.map(function(edu){
return {
institution: edu.institution,
gpa: edu.grade,
courses: edu.curriculum,
startDate: edu.start,
endDate: edu.end,
area: edu.area,
studyType: edu.studyType
};
}),
skills: skillsToJRS( src.skills ),
volunteer: src.service.history.map(function(srv){
return {
flavor: foreign ? srv.flavor : undefined,
organization: srv.organization,
position: srv.position,
startDate: srv.start,
endDate: srv.end,
website: srv.url,
summary: srv.summary,
highlights: srv.highlights
};
}),
awards: src.recognition.map(function(awd){
return {
flavor: foreign ? awd.flavor : undefined,
url: foreign ? awd.url: undefined,
title: awd.title,
date: awd.date,
awarder: awd.from,
summary: awd.summary
};
}),
publications: src.writing.map(function(pub){
return {
name: pub.title,
publisher: pub.publisher,
releaseDate: pub.date,
website: pub.url,
summary: pub.summary
};
}),
work: employment( src.employment, false ),
education: education( src.education, false ),
skills: skillsToJRS( src.skills, false ),
volunteer: service( src.service, false ),
awards: recognition( src.recognition, false, foreign ),
publications: writing( src.writing, false ),
interests: src.interests,
references: src.references,
references: references( src.testimonials, false ),
samples: foreign ? src.samples : undefined,
disposition: foreign ? src.disposition : undefined,
languages: src.languages
@ -250,6 +120,207 @@ FRESH to JSON Resume conversion routiens.
return obj;
}
function employment( obj, direction ) {
if( !direction ) {
return obj && obj.history ?
obj.history.map(function(emp){
return {
company: emp.employer,
website: emp.url,
position: emp.position,
startDate: emp.start,
endDate: emp.end,
summary: emp.summary,
highlights: emp.highlights
};
}) : undefined;
}
else {
return {
history: obj && obj.length ?
obj.map( function( job ) {
return {
position: job.position,
employer: job.company,
summary: job.summary,
current: (!job.endDate || !job.endDate.trim() || job.endDate.trim().toLowerCase() === 'current') || undefined,
start: job.startDate,
end: job.endDate,
url: job.website,
keywords: "",
highlights: job.highlights
};
}) : undefined
};
}
}
function education( obj, direction ) {
if( direction ) {
return obj && obj.length ? {
history: obj.map(function(edu){
return {
institution: edu.institution,
start: edu.startDate,
end: edu.endDate,
grade: edu.gpa,
curriculum: edu.courses,
url: edu.website || edu.url || null,
summary: null,
area: edu.area,
studyType: edu.studyType
};
})
} : undefined;
}
else {
return obj && obj.history ?
obj.history.map(function(edu){
return {
institution: edu.institution,
gpa: edu.grade,
courses: edu.curriculum,
startDate: edu.start,
endDate: edu.end,
area: edu.area,
studyType: edu.studyType
};
}) : undefined;
}
}
function service( obj, direction, foreign ) {
if( direction ) {
return {
history: obj && obj.length ? obj.map(function(vol) {
return {
type: 'volunteer',
position: vol.position,
organization: vol.organization,
start: vol.startDate,
end: vol.endDate,
url: vol.website,
summary: vol.summary,
highlights: vol.highlights
};
}) : undefined
};
}
else {
return obj && obj.history ?
obj.history.map(function(srv){
return {
flavor: foreign ? srv.flavor : undefined,
organization: srv.organization,
position: srv.position,
startDate: srv.start,
endDate: srv.end,
website: srv.url,
summary: srv.summary,
highlights: srv.highlights
};
}) : undefined;
}
}
function social( obj, direction ) {
if( direction ) {
return obj.map(function(pro){
return {
label: pro.network,
network: pro.network,
url: pro.url,
user: pro.username
};
});
}
else {
return obj.map( function( soc ) {
return {
network: soc.network,
username: soc.user,
url: soc.url
};
});
}
}
function recognition( obj, direction, foreign ) {
if( direction ) {
return obj && obj.length ? obj.map(
function(awd){
return {
flavor: foreign ? awd.flavor : undefined,
url: foreign ? awd.url: undefined,
title: awd.title,
date: awd.date,
from: awd.awarder,
summary: awd.summary
};
}) : undefined;
}
else {
return obj && obj.length ? obj.map(function(awd){
return {
flavor: foreign ? awd.flavor : undefined,
url: foreign ? awd.url: undefined,
title: awd.title,
date: awd.date,
awarder: awd.from,
summary: awd.summary
};
}) : undefined;
}
}
function references( obj, direction ) {
if( direction ) {
return obj && obj.length && obj.map(function(ref){
return {
name: ref.name,
flavor: 'professional',
quote: ref.reference,
private: false
};
});
}
else {
return obj && obj.length && obj.map(function(ref){
return {
name: ref.name,
reference: ref.quote
};
});
}
}
function writing( obj, direction ) {
if( direction ) {
return obj.map(function( pub ) {
return {
title: pub.name,
flavor: undefined,
publisher: pub.publisher,
url: pub.website,
date: pub.releaseDate,
summary: pub.summary
};
});
}
else {
return obj && obj.length ? obj.map(function(pub){
return {
name: pub.title,
publisher: pub.publisher && pub.publisher.name ? pub.publisher.name : pub.publisher,
releaseDate: pub.date,
website: pub.url,
summary: pub.summary
};
}) : undefined;
}
}
function skillsToFRESH( skills ) {
return {

184
src/core/empty-fresh.json Normal file
View File

@ -0,0 +1,184 @@
{
"name": "",
"meta": {
"format": "FRESH@0.1.0",
"version": "0.1.0"
},
"info": {
"label": "",
"characterClass": "",
"brief": "",
"image": ""
},
"contact": {
"website": "",
"phone": "",
"email": "",
"other": []
},
"location": {
"address": "",
"city": "",
"region": "",
"code": "",
"country": ""
},
"social": [
{
"label": "",
"network": "",
"user": "",
"url": ""
}
],
"employment": {
"summary": "",
"history": [
{
"employer": "",
"url": "",
"position": "",
"summary": "",
"start": "",
"end": "",
"keywords": [],
"highlights": []
}
]
},
"education": {
"summary": "",
"level": "",
"degree": "",
"history": [
{
"institution": "",
"url": "",
"start": "",
"end": "",
"grade": "",
"summary": "",
"curriculum": []
}
]
},
"service": {
"summary": "",
"history": [
{
"flavor": "",
"position": "",
"organization": "",
"url": "",
"start": "",
"end": "",
"summary": "",
"highlights": []
}
]
},
"skills": {
"sets": [
{
"name": "",
"level": "",
"skills": []
}
],
"list": [ ]
},
"samples": [
{
"title": "",
"summary": "",
"url": "",
"date": ""
}
],
"writing": [
{
"title": "",
"flavor": "",
"date": "",
"publisher": {
"name": "",
"url": ""
},
"url": ""
}
],
"reading": [
{
"title": "",
"flavor": "",
"url": "",
"author": ""
}
],
"recognition": [
{
"flavor": "",
"from": "",
"title": "",
"event": "",
"date": "",
"summary": ""
}
],
"references": [
{
"name": "",
"flavor": "",
"private": true,
"contact": [
{
"label": "",
"flavor": "",
"value": ""
}
]
}
],
"testimonials": [
{
"name": "",
"flavor": "",
"quote": ""
}
],
"languages": [
{
"language": "",
"level": "",
"years": 0
}
],
"interests": [
{
"name": "",
"summary": "",
"keywords": []
}
]
}

View File

@ -1,6 +1,7 @@
/**
The FluentCV date representation.
@license Copyright (c) 2015 by James M. Devlin. All rights reserved.
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
@module fluent-date.js
*/
var moment = require('moment');
@ -34,8 +35,8 @@ FluentDate/*.prototype*/.fmt = function( dt ) {
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' );
var temp = parts[1] + '-' + (month < 10 ? '0' + month : month.toString());
return moment( temp, 'YYYY-MM' );
}
else if( /^\d{4}-\d{1,2}$/.test(dt) ) { // "2015-03", "1998-4"
return moment( dt, 'YYYY-MM' );
@ -66,7 +67,10 @@ FluentDate/*.prototype*/.fmt = function( dt ) {
}
}
else {
if( dt.isValid && dt.isValid() )
if( !dt ) {
return moment();
}
else if( dt.isValid && dt.isValid() )
return dt;
throw 'Unknown date object encountered.';
}

View File

@ -1,6 +1,7 @@
/**
Definition of the FRESHResume class.
@license MIT. Copyright (c) 2015 James M. Devlin / FluentDesk
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
@module fresh-resume.js
*/
(function() {
@ -11,7 +12,9 @@ Definition of the FRESHResume class.
, _ = require('underscore')
, PATH = require('path')
, moment = require('moment')
, CONVERTER = require('./convert');
, MD = require('marked')
, CONVERTER = require('./convert')
, JRSResume = require('./jrs-resume');
/**
A FRESH-style resume in JSON or YAML.
@ -45,16 +48,23 @@ Definition of the FRESHResume class.
Save the sheet to disk in a specific format, either FRESH or JSON Resume.
*/
FreshResume.prototype.saveAs = function( filename, format ) {
this.imp.fileName = filename || this.imp.fileName;
if( format !== 'JRS' ) {
this.imp.fileName = filename || this.imp.fileName;
FS.writeFileSync( this.imp.fileName, this.stringify(), 'utf8' );
}
else {
var newRep = CONVERTER.toJRS( this );
FS.writeFileSync( this.imp.fileName, FreshResume.stringify( newRep ), 'utf8' );
FS.writeFileSync( filename, JRSResume.stringify( newRep ), 'utf8' );
}
return this;
}
};
FreshResume.prototype.dupe = function() {
var rnew = new FreshResume();
rnew.parse( this.stringify(), { } );
return rnew;
};
/**
Convert the supplied object to a JSON string, sanitizing meta-properties along
@ -68,7 +78,60 @@ Definition of the FRESHResume class.
) ? undefined : value;
}
return JSON.stringify( obj, replacer, 2 );
},
};
/**
Create a copy of this resume in which all fields have been interpreted as
Markdown.
*/
FreshResume.prototype.markdownify = function() {
var that = this;
var ret = this.dupe();
function MDIN(txt){
return MD(txt || '' ).replace(/^\s*<p>|<\/p>\s*$/gi, '');
}
// TODO: refactor recursion
function markdownifyStringsInObject( obj, inline ) {
if( !obj ) return;
inline = inline === undefined || inline;
if( Object.prototype.toString.call( obj ) === '[object Array]' ) {
obj.forEach(function(elem, idx, ar){
if( typeof elem === 'string' || elem instanceof String )
ar[idx] = inline ? MDIN(elem) : MD( elem );
else
markdownifyStringsInObject( elem );
});
}
else if (typeof obj === 'object') {
Object.keys( obj ).forEach(function(key) {
var sub = obj[key];
if( typeof sub === 'string' || sub instanceof String ) {
if( _.contains(['skills','url','start','end','date'], key) )
return;
if( key === 'summary' )
obj[key] = MD( obj[key] );
else
obj[key] = inline ? MDIN( obj[key] ) : MD( obj[key] );
}
else
markdownifyStringsInObject( sub );
});
}
}
Object.keys( ret ).forEach(function(member){
markdownifyStringsInObject( ret[ member ] );
});
return ret;
};
/**
Convert this object to a JSON string, sanitizing meta-properties along the
@ -129,7 +192,7 @@ Definition of the FRESHResume class.
*/
FreshResume.prototype.updateData = function( str ) {
this.clear( false );
this.parse( str )
this.parse( str );
return this;
};
@ -143,28 +206,49 @@ Definition of the FRESHResume class.
delete this.employment;
delete this.service;
delete this.education;
//delete this.awards;
delete this.publications;
//delete this.interests;
delete this.recognition;
delete this.reading;
delete this.writing;
delete this.interests;
delete this.skills;
delete this.social;
};
/**
Get a safe count of the number of things in a section.
*/
FreshResume.prototype.count = function( obj ) {
if( !obj ) return 0;
if( obj.history ) return obj.history.length;
if( obj.sets ) return obj.sets.length;
return obj.length || 0;
};
/**
Get the default (empty) sheet.
*/
FreshResume.default = function() {
return new FreshResume().open( PATH.join( __dirname, 'empty.json'), 'Empty' );
}
return new FreshResume().open(
PATH.join( __dirname, 'empty-fresh.json'), 'Empty' );
};
/**
Add work experience to the sheet.
*/
FreshResume.prototype.add = function( moniker ) {
var defSheet = FreshResume.default();
var newObject = $.extend( true, {}, defSheet[ moniker ][0] );
var newObject = defSheet[moniker].history ?
$.extend( true, {}, defSheet[ moniker ].history[0] ) :
(moniker === 'skills' ?
$.extend( true, {}, defSheet.skills.sets[0] ) :
$.extend( true, {}, defSheet[ moniker ][0] ));
this[ moniker ] = this[ moniker ] || [];
this[ moniker ].push( newObject );
if( this[ moniker ].history )
this[ moniker ].history.push( newObject );
else if( moniker === 'skills' )
this.skills.sets.push( newObject );
else
this[ moniker ].push( newObject );
return newObject;
};
@ -178,6 +262,27 @@ Definition of the FRESHResume class.
});
};
/**
Return the specified network profile.
*/
FreshResume.prototype.getProfile = function( socialNetwork ) {
socialNetwork = socialNetwork.trim().toLowerCase();
return this.social && _.find( this.social, function(sn) {
return sn.network.trim().toLowerCase() === socialNetwork;
});
};
/**
Return an array of profiles for the specified network, for when the user
has multiple eg. GitHub accounts.
*/
FreshResume.prototype.getProfiles = function( socialNetwork ) {
socialNetwork = socialNetwork.trim().toLowerCase();
return this.social && _.filter( this.social, function(sn){
return sn.network.trim().toLowerCase() === socialNetwork;
});
};
/**
Determine if the sheet includes a specific skill.
*/
@ -195,7 +300,7 @@ Definition of the FRESHResume class.
*/
FreshResume.prototype.isValid = function( info ) {
var schemaObj = require('FRESCA');
var validator = require('is-my-json-valid')
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})?)?$/ }
});
@ -217,14 +322,15 @@ Definition of the FRESHResume class.
*/
FreshResume.prototype.duration = function() {
if( this.employment.history && this.employment.history.length ) {
var careerStart = this.employment.history[ this.employment.history.length - 1].safe.start;
var firstJob = _.last( this.employment.history );
var careerStart = firstJob.start ? firstJob.safe.start : '';
if ((typeof careerStart === 'string' || careerStart instanceof String) &&
!careerStart.trim())
return 0;
var careerLast = _.max( this.employment.history, function( w ) {
return w.safe.end.unix();
}).safe.end;
return careerLast.diff( careerStart, 'years' );
return( w.safe && w.safe.end ) ? w.safe.end.unix() : moment().unix();
});
return careerLast.safe.end.diff( careerStart, 'years' );
}
return 0;
};
@ -243,7 +349,7 @@ Definition of the FRESHResume class.
// return( a.safeDate.isBefore(b.safeDate) ) ? 1
// : ( a.safeDate.isAfter(b.safeDate) && -1 ) || 0;
// });
this.publications && this.publications.sort( function(a, b) {
this.writing && this.writing.sort( function(a, b) {
return( a.safe.date.isBefore(b.safe.date) ) ? 1
: ( a.safe.date.isAfter(b.safe.date) && -1 ) || 0;
});
@ -265,35 +371,39 @@ Definition of the FRESHResume class.
function _parseDates() {
var _fmt = require('./fluent-date').fmt;
var that = this;
this.employment.history && this.employment.history.forEach( function(job) {
job.safe = {
start: _fmt( job.start ),
end: _fmt( job.end || 'current' )
};
});
this.education.history && this.education.history.forEach( function(edu) {
edu.safe = {
start: _fmt( edu.start ),
end: _fmt( edu.end || 'current' )
};
});
this.service.history && this.service.history.forEach( function(vol) {
vol.safe = {
start: _fmt( vol.start ),
end: _fmt( vol.end || 'current' )
};
});
this.recognition && this.recognition.forEach( function(rec) {
rec.safe = {
date: _fmt( rec.date )
};
});
this.writing && this.writing.forEach( function(pub) {
pub.safe = {
date: _fmt( pub.date )
};
// TODO: refactor recursion
function replaceDatesInObject( obj ) {
if( !obj ) return;
if( Object.prototype.toString.call( obj ) === '[object Array]' ) {
obj.forEach(function(elem){
replaceDatesInObject( elem );
});
}
else if (typeof obj === 'object') {
if( obj._isAMomentObject || obj.safe )
return;
Object.keys( obj ).forEach(function(key) {
replaceDatesInObject( obj[key] );
});
['start','end','date'].forEach( function(val) {
if( (obj[val] !== undefined) && (!obj.safe || !obj.safe[val] )) {
obj.safe = obj.safe || { };
obj.safe[ val ] = _fmt( obj[val] );
if( obj[val] && (val === 'start') && !obj.end ) {
obj.safe.end = _fmt('current');
}
}
});
}
}
Object.keys( this ).forEach(function(member){
replaceDatesInObject( that[ member ] );
});
}
/**

View File

@ -1,6 +1,7 @@
/**
Definition of the JRSResume class.
@license MIT. Copyright (c) 2015 James M. Devlin / FluentDesk
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
@module jrs-resume.js
*/
(function() {
@ -30,17 +31,22 @@ Definition of the JRSResume class.
consistent format. Then sort each section by startDate descending.
*/
JRSResume.prototype.open = function( file, title ) {
this.imp = { fileName: file };
this.imp.raw = FS.readFileSync( file, 'utf8' );
return this.parse( this.imp.raw, title );
//this.imp = { fileName: file }; <-- schema violation, tuck it into .basics instead
this.basics = {
imp: {
fileName: file,
raw: FS.readFileSync( file, 'utf8' )
}
};
return this.parse( this.basics.imp.raw, title );
};
/**
Save the sheet to disk (for environments that have disk access).
*/
JRSResume.prototype.save = function( filename ) {
this.imp.fileName = filename || this.imp.fileName;
FS.writeFileSync( this.imp.fileName, this.stringify(), 'utf8' );
this.basics.imp.fileName = filename || this.basics.imp.fileName;
FS.writeFileSync( this.basics.imp.fileName, this.stringify( this ), 'utf8' );
return this;
};
@ -48,15 +54,19 @@ Definition of the JRSResume class.
Convert this object to a JSON string, sanitizing meta-properties along the
way. Don't override .toString().
*/
JRSResume.prototype.stringify = function() {
JRSResume.stringify = function( obj ) {
function replacer( key,value ) { // Exclude these keys from stringification
return _.some(['meta', 'warnings', 'computed', 'filt', 'ctrl', 'index',
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;
}
return JSON.stringify( this, replacer, 2 );
return JSON.stringify( obj, replacer, 2 );
};
JRSResume.prototype.stringify = function() {
return JRSResume.stringify( this );
};
/**
@ -67,16 +77,17 @@ Definition of the JRSResume class.
JRSResume.prototype.parse = function( stringData, opts ) {
opts = opts || { };
var rep = JSON.parse( stringData );
extend( true, this, rep );
// Set up metadata
if( opts.imp === undefined || opts.imp ) {
this.imp = this.imp || { };
this.imp.title = (opts.title || this.imp.title) || this.basics.name;
this.basics.imp = this.basics.imp || { };
this.basics.imp.title = (opts.title || this.basics.imp.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 = {
(opts.compute === undefined || opts.compute) && (this.basics.computed = {
numYears: this.duration(),
keywords: this.keywords()
});
@ -94,14 +105,14 @@ 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 )
this.parse( str );
return this;
};
@ -111,7 +122,7 @@ Definition of the JRSResume class.
JRSResume.prototype.clear = function( clearMeta ) {
clearMeta = ((clearMeta === undefined) && true) || clearMeta;
clearMeta && (delete this.imp);
delete this.computed; // Don't use Object.keys() here
delete this.basics.computed; // Don't use Object.keys() here
delete this.work;
delete this.volunteer;
delete this.education;
@ -126,8 +137,8 @@ Definition of the JRSResume class.
Get the default (empty) sheet.
*/
JRSResume.default = function() {
return new JRSResume().open( PATH.join( __dirname, 'empty.json'), 'Empty' );
}
return new JRSResume().open( PATH.join( __dirname, 'empty-jrs.json'), 'Empty' );
};
/**
Add work experience to the sheet.
@ -168,9 +179,16 @@ Definition of the JRSResume class.
JRSResume.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 );
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})?)?$/ }
});
var ret = validate( this );
if( !ret ) {
this.basics.imp = this.basics.imp || { };
this.basics.imp.validationErrors = validate.errors;
}
return ret;
};
/**

View File

@ -1,6 +1,7 @@
/**
Abstract theme representation.
Definition of the Theme class.
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
@module theme.js
*/
(function() {
@ -10,10 +11,12 @@ Abstract theme representation.
, validator = require('is-my-json-valid')
, _ = require('underscore')
, PATH = require('path')
, moment = require('moment');
, EXTEND = require('../utils/extend')
, moment = require('moment')
, RECURSIVE_READ_DIR = require('recursive-readdir-sync');
/**
The Theme class represents a specific presentation of a resume.
The Theme class is a representation of a FluentCV theme asset.
@class Theme
*/
function Theme() {
@ -25,59 +28,38 @@ Abstract theme representation.
*/
Theme.prototype.open = function( themeFolder ) {
function friendlyName( val ) {
val = val.trim().toLowerCase();
var friendly = { yml: 'yaml', md: 'markdown', txt: 'text' };
return friendly[val] || val;
// 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 themeInfo = JSON.parse( FS.readFileSync( themeFile, 'utf8' ) );
var that = this;
// Move properties from the theme JSON file to the theme object
EXTEND( true, this, themeInfo );
// Set up a formats has for the theme
var formatsHash = { };
// Check for an explicit "formats" entry in the theme JSON. If it has one,
// then this theme declares its files explicitly.
if( !!this.formats ) {
formatsHash = loadExplicit.call( this );
this.explicit = true;
}
else {
formatsHash = loadImplicit.call( this );
}
// Remember the theme folder; might be custom
this.folder = themeFolder;
// Iterate over all files in the theme folder, producing an array, fmts,
// containing info for each file.
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;
});
// Add 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 } ] );
formatsHash.json = { title: 'json', outFormat: 'json', pre: 'json', ext: 'json', path: null, data: null };
formatsHash.yml = { title: 'yaml', outFormat: 'yml', pre: 'yml', ext: 'yml', path: null, data: null };
// Now, get all the CSS files...
this.cssFiles = fmts.filter(function( fmt ){ return fmt[1].ext === 'css'; });
// ...and assemble information on them
this.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;
});
// Remove CSS files from the formats array
fmts = fmts.filter( function( fmt) {
return fmt[1].ext !== 'css';
});
// Create a hash out of the formats for this theme
this.formats = _.object( fmts );
// Cache
this.formats = formatsHash;
// Set the official theme name
this.name = PATH.parse( themeFolder ).name;
this.name = PATH.parse( this.folder ).name;
return this;
};
@ -96,6 +78,196 @@ Abstract theme representation.
return this.formats[ fmt ];
};
function loadImplicit() {
// Set up a hash of formats supported by this theme.
var formatsHash = { };
var that = this;
var major = false;
// Establish the base theme folder
var tplFolder = PATH.join( this.folder, 'src' );
// Iterate over all files in the theme folder, producing an array, fmts,
// containing info for each file. While we're doing that, also build up
// the formatsHash object.
var fmts = RECURSIVE_READ_DIR( tplFolder ).map( function( absPath ) {
// 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 outFmt = '', isMajor = false;
var portion = pathInfo.dir.replace(tplFolder,'');
if( portion && portion.trim() ) {
var reg = /^(?:\/|\\)(html|latex|doc|pdf|partials)(?:\/|\\)?/ig;
var res = reg.exec( portion );
if( res ) {
if( res[1] !== 'partials' ) {
outFmt = res[1];
}
else {
that.partials = that.partials || [];
that.partials.push( { name: pathInfo.name, path: absPath } );
return null;
}
}
}
// Otherwise, the output format is inferred from the filename, as in
// compact-[outputformat].[extension], for ex, compact-pdf.html.
if( !outFmt ) {
var idx = pathInfo.name.lastIndexOf('-');
outFmt = ( idx === -1 ) ? pathInfo.name : pathInfo.name.substr( idx + 1 );
isMajor = true;
}
// We should have a valid output format now.
formatsHash[ outFmt ] = formatsHash[outFmt] || {
outFormat: outFmt,
files: []
};
// Create the file representation object.
var obj = {
action: 'transform',
path: absPath,
major: isMajor,
orgPath: PATH.relative(that.folder, absPath),
ext: pathInfo.ext.slice(1),
title: friendlyName( outFmt ),
pre: outFmt,
// outFormat: outFmt || pathInfo.name,
data: FS.readFileSync( absPath, 'utf8' ),
css: null
};
// Add this file to the list of files for this format type.
formatsHash[ outFmt ].files.push( obj );
return obj;
});
// Now, get all the CSS files...
(this.cssFiles = fmts.filter(function( fmt ){ return fmt && (fmt.ext === 'css'); }))
.forEach(function( cssf ) {
// For each CSS file, get its corresponding HTML file
var idx = _.findIndex(fmts, function( fmt ) {
return fmt.pre === cssf.pre && fmt.ext === 'html';
});
cssf.action = null;
fmts[ idx ].css = cssf.data;
fmts[ idx ].cssPath = cssf.path;
});
// Remove CSS files from the formats array
fmts = fmts.filter( function( fmt) {
return fmt && (fmt.ext !== 'css');
});
return formatsHash;
}
function loadExplicit() {
var that = this;
// Set up a hash of formats supported by this theme.
var formatsHash = { };
// Establish the base theme folder
var tplFolder = PATH.join( this.folder, 'src' );
var act = null;
// Iterate over all files in the theme folder, producing an array, fmts,
// containing info for each file. While we're doing that, also build up
// the formatsHash object.
var fmts = RECURSIVE_READ_DIR( tplFolder ).map( function( absPath ) {
act = null;
// If this file is mentioned in the theme's JSON file under "transforms"
var pathInfo = PATH.parse(absPath);
var absPathSafe = absPath.trim().toLowerCase();
var outFmt = _.find( Object.keys( that.formats ), function( fmtKey ) {
var fmtVal = that.formats[ fmtKey ];
return _.some( fmtVal.transform, function( fpath ) {
var absPathB = PATH.join( that.folder, fpath ).trim().toLowerCase();
return absPathB === absPathSafe;
});
});
if( outFmt ) {
act = 'transform';
}
// 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.
if( !outFmt ) {
var portion = pathInfo.dir.replace(tplFolder,'');
if( portion && portion.trim() ) {
var reg = /^(?:\/|\\)(html|latex|doc|pdf)(?:\/|\\)?/ig;
var res = reg.exec( portion );
res && (outFmt = res[1]);
}
}
// Otherwise, the output format is inferred from the filename, as in
// compact-[outputformat].[extension], for ex, compact-pdf.html.
if( !outFmt ) {
var idx = pathInfo.name.lastIndexOf('-');
outFmt = ( idx === -1 ) ? pathInfo.name : pathInfo.name.substr( idx + 1 );
}
// We should have a valid output format now.
formatsHash[ outFmt ] =
formatsHash[ outFmt ] || {
outFormat: outFmt,
files: [],
symLinks: that.formats[ outFmt ].symLinks
};
// Create the file representation object.
var obj = {
action: act,
orgPath: PATH.relative(that.folder, absPath),
path: absPath,
ext: pathInfo.ext.slice(1),
title: friendlyName( outFmt ),
pre: outFmt,
// outFormat: outFmt || pathInfo.name,
data: FS.readFileSync( absPath, 'utf8' ),
css: null
};
// Add this file to the list of files for this format type.
formatsHash[ outFmt ].files.push( obj );
return obj;
});
// Now, get all the CSS files...
(this.cssFiles = fmts.filter(function( fmt ){ return fmt.ext === 'css'; }))
.forEach(function( cssf ) {
// For each CSS file, get its corresponding HTML file
var idx = _.findIndex(fmts, function( fmt ) {
return fmt.pre === cssf.pre && fmt.ext === 'html';
});
fmts[ idx ].css = cssf.data;
fmts[ idx ].cssPath = cssf.path;
});
// Remove CSS files from the formats array
fmts = fmts.filter( function( fmt) {
return fmt.ext !== 'css';
});
return formatsHash;
}
function friendlyName( val ) {
val = val.trim().toLowerCase();
var friendly = { yml: 'yaml', md: 'markdown', txt: 'text' };
return friendly[val] || val;
}
module.exports = Theme;
}());

View File

@ -0,0 +1,103 @@
/**
Definition of the HandlebarsGenerator class.
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
@module handlebars-generator.js
*/
(function() {
var _ = require('underscore')
, HANDLEBARS = require('handlebars')
, FS = require('fs')
, moment = require('moment')
, MD = require('marked')
, H2W = require('../utils/html-to-wpml');
/**
Perform template-based resume generation using Handlebars.js.
@method generate
*/
module.exports = function( json, jst, format, cssInfo, opts, theme ) {
// Pre-compile any partials present in the theme.
_.each( theme.partials, function( el ) {
var tplData = FS.readFileSync( el.path, 'utf8' );
var compiledTemplate = HANDLEBARS.compile( tplData );
HANDLEBARS.registerPartial( el.name, compiledTemplate );
});
// Register necessary helpers.
registerHelpers();
// Compile and run the Handlebars template.
var template = HANDLEBARS.compile(jst);
return template({
r: json,
filt: opts.filters,
cssInfo: cssInfo,
headFragment: opts.headFragment || ''
});
};
/**
Register useful Handlebars helpers.
@method registerHelpers
*/
function registerHelpers() {
// Set up a date formatting helper so we can do:
// {{#formatDate val 'YYYY-MM'}}
HANDLEBARS.registerHelper("formatDate", function(datetime, format) {
if( moment ) {
return moment( datetime ).format( format );
}
else {
return datetime;
}
});
// Set up a Markdown-to-WordProcessingML helper so we can do:
// {{#wmpl val [true|false]}}
HANDLEBARS.registerHelper("wpml", function( txt, inline ) {
inline = (inline && !inline.hash) || false;
txt = inline ?
MD(txt.trim()).replace(/^\s*<p>|<\/p>\s*$/gi, '') :
MD(txt.trim());
txt = H2W( txt.trim() );
return txt;
});
// Set up a generic conditional helper so we can do:
// {{#compare val otherVal operator="<"}}
// http://doginthehat.com.au/2012/02/comparison-block-helper-for-handlebars-templates/
HANDLEBARS.registerHelper('compare', function(lvalue, rvalue, options) {
if (arguments.length < 3)
throw new Error("Handlerbars Helper 'compare' needs 2 parameters");
var operator = options.hash.operator || "==";
var operators = {
'==': function(l,r) { return l == r; },
'===': function(l,r) { return l === r; },
'!=': function(l,r) { return l != r; },
'<': function(l,r) { return l < r; },
'>': function(l,r) { return l > r; },
'<=': function(l,r) { return l <= r; },
'>=': function(l,r) { return l >= r; },
'typeof': function(l,r) { return typeof l == r; }
};
if (!operators[operator])
throw new Error("Handlerbars Helper 'compare' doesn't know the operator "+operator);
var result = operators[operator](lvalue,rvalue);
return result ? options.fn(this) : options.inverse(this);
});
}
}());

View File

@ -0,0 +1,37 @@
/**
Definition of the UnderscoreGenerator class.
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
*/
(function() {
var _ = require('underscore');
module.exports = function( json, jst, format, cssInfo, opts, theme ) {
// Tweak underscore's default template delimeters
var delims = (opts.themeObj && opts.themeObj.delimeters) || opts.template;
if( opts.themeObj && opts.themeObj.delimeters ) {
delims = _.mapObject( delims, function(val,key) {
return new RegExp( val, "ig");
});
}
_.templateSettings = delims;
// Strip {# comments #}
jst = jst.replace( delims.comment, '');
// Compile and run the template. TODO: avoid unnecessary recompiles.
var compiled = _.template(jst);
var ret = compiled({
r: format === 'html' || format === 'pdf' ? json.markdownify() : json,
filt: opts.filters,
XML: require('xml-escape'),
RAW: json,
cssInfo: cssInfo,
headFragment: opts.headFragment || ''
});
return ret;
};
}());

View File

@ -1,288 +1,348 @@
/**
Internal resume generation logic for FluentCV.
@license MIT. Copyright (c) 2015 James M. Devlin / FluentDesk
@license MIT. Copyright (c) 2015 James M. Devlin / FluentDesk.
@module fluentcmd.js
*/
module.exports = function () {
(function() {
module.exports = function () {
// We don't mind pseudo-globals here
var path = require( 'path' )
, extend = require( './utils/extend' )
, unused = require('./utils/string')
, FS = require('fs')
, _ = require('underscore')
, FLUENT = require('./fluentlib')
, PATH = require('path')
, MKDIRP = require('mkdirp')
//, COLORS = require('colors')
, rez, _log, _err;
var path = require( 'path' )
, extend = require( './utils/extend' )
, unused = require('./utils/string')
, FS = require('fs')
, _ = require('underscore')
, FLUENT = require('./fluentlib')
, PATH = require('path')
, MKDIRP = require('mkdirp')
//, COLORS = require('colors')
, rez, _log, _err;
/**
Given a source JSON resume, a destination resume path, and a theme file,
generate 0..N resumes in the desired formats.
@param src Path to the source JSON resume file: "rez/resume.json".
@param dst An array of paths to the target resume file(s).
@param theme Friendly name of the resume theme. Defaults to "modern".
@param logger Optional logging override.
*/
function generate( src, dst, opts, logger, errHandler ) {
/**
Given a source JSON resume, a destination resume path, and a theme file,
generate 0..N resumes in the desired formats.
@param src Path to the source JSON resume file: "rez/resume.json".
@param dst An array of paths to the target resume file(s).
@param theme Friendly name of the resume theme. Defaults to "modern".
@param logger Optional logging override.
*/
function generate( src, dst, opts, logger, errHandler ) {
_log = logger || console.log;
_err = errHandler || error;
_log = logger || console.log;
_err = errHandler || error;
//_opts = extend( true, _opts, opts );
_opts.theme = (opts.theme && opts.theme.toLowerCase().trim()) || 'modern';
_opts.prettify = opts.prettify === true ? _opts.prettify : false;
//_opts = extend( true, _opts, opts );
_opts.theme = (opts.theme && opts.theme.toLowerCase().trim())|| 'modern';
_opts.prettify = opts.prettify === true ? _opts.prettify : false;
// Load input resumes...
if(!src || !src.length) { throw { fluenterror: 3 }; }
var sheets = loadSourceResumes( src );
// Load input resumes...
if(!src || !src.length) { throw { fluenterror: 3 }; }
var sheets = loadSourceResumes( src );
// Merge input resumes...
var msg = '';
rez = _.reduceRight( sheets, function( a, b, idx ) {
msg += ((idx == sheets.length - 2) ? 'Merging '.gray + a.imp.fileName : '')
+ ' onto '.gray + b.imp.fileName;
return extend( true, b, a );
});
msg && _log(msg);
// Merge input resumes...
var msg = '';
rez = _.reduceRight( sheets, function( a, b, idx ) {
msg += ((idx == sheets.length - 2) ?
'Merging '.gray+ a.imp.fileName : '') + ' onto '.gray + b.imp.fileName;
return extend( true, b, a );
});
msg && _log(msg);
// 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');
if (!exists( tFolder )) {
tFolder = PATH.resolve( _opts.theme );
// 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');
if (!exists( tFolder )) {
throw { fluenterror: 1, data: _opts.theme };
}
}
// Load the theme
var theTheme = new FLUENT.Theme().open( tFolder );
_opts.themeObj = theTheme;
_log( 'Applying '.info + theTheme.name.toUpperCase().infoBold + (' theme (' +
Object.keys(theTheme.formats).length + ' formats)').info );
// Expand output resumes... (can't use map() here)
var targets = [], that = this;
( (dst && dst.length && dst) || ['resume.all'] ).forEach( function(t) {
var to = path.resolve(t),
pa = path.parse(to),
fmat = pa.ext || '.all';
targets.push.apply(targets, fmat === '.all' ?
Object.keys( theTheme.formats ).map(function(k){
var z = theTheme.formats[k];
return { file: to.replace(/all$/g,z.pre), fmt: z }
}) : [{ file: to, fmt: theTheme.getFormat( fmat.slice(1) ) }]);
});
// Run the transformation!
var finished = targets.map( function(t) { return single(t, theTheme); } );
// Don't send the client back empty-handed
return { sheet: rez, targets: targets, processed: finished };
}
/**
Generate a single resume of a specific format.
@param f Full path to the destination resume to generate, for example,
"/foo/bar/resume.pdf" or "c:\foo\bar\resume.txt".
*/
function single( fi, theme ) {
try {
var f = fi.file, fType = fi.fmt.ext, fName = path.basename(f,'.'+fType);
var fObj = _.property( fi.fmt.pre )( theme.formats );
var fOut = path.join( f.substring( 0, f.lastIndexOf('.')+1 ) + fObj.pre);
_log( 'Generating '.useful + fi.fmt.title.toUpperCase().useful.bold + ' resume: '.useful +
path.relative(process.cwd(), f ).useful.bold );
var theFormat = _fmts.filter(
function( fmt ) { return fmt.name === fi.fmt.pre; })[0];
MKDIRP.sync( path.dirname(fOut) ); // Ensure dest folder exists;
theFormat.gen.generate( rez, fOut, _opts );
}
catch( ex ) {
_err( ex );
}
}
/**
Handle an exception.
*/
function error( ex ) {
throw ex;
}
/**
Validate 1 to N resumes in either FRESH or JSON Resume format.
*/
function validate( src, unused, opts, logger ) {
_log = logger || console.log;
if( !src || !src.length ) { throw { fluenterror: 6 }; }
var isValid = true;
var validator = require('is-my-json-valid');
var schemas = {
fresh: require('FRESCA'),
jars: require('./core/resume.json')
};
// Load input resumes...
var sheets = loadSourceResumes(src, function( res ) {
try {
return {
file: res,
raw: FS.readFileSync( res, 'utf8' )
};
}
catch( ex ) {
throw ex;
}
});
sheets.forEach( function( rep ) {
try {
var rez = JSON.parse( rep.raw );
}
catch( ex ) {
_log('Validating '.info + rep.file.infoBold + ' against FRESH/JRS schema: '.info + 'ERROR!'.error.bold);
if (ex instanceof SyntaxError) {
// Invalid JSON
_log( '--> '.bold.red + rep.file.toUpperCase().red + ' contains invalid JSON. Unable to validate.'.red );
_log( (' INTERNAL: ' + ex).red );
tFolder = PATH.resolve( _opts.theme );
if (!exists( tFolder )) {
throw { fluenterror: 1, data: _opts.theme };
}
else {
_log(('ERROR: ' + ex.toString()).red.bold);
}
return;
}
var isValid = false;
var style = 'useful';
var errors = [];
// Load the theme
var theTheme = new FLUENT.Theme().open( tFolder );
_opts.themeObj = theTheme;
_log( 'Applying '.info + theTheme.name.toUpperCase().infoBold +
(' theme (' +Object.keys(theTheme.formats).length + ' formats)').info);
try {
// Expand output resumes... (can't use map() here)
var targets = [], that = this;
( (dst && dst.length && dst) || ['resume.all'] ).forEach( function(t) {
var to = path.resolve(t),
pa = path.parse(to),
fmat = pa.ext || '.all';
targets.push.apply(targets, fmat === '.all' ?
Object.keys( theTheme.formats ).map(function(k){
var z = theTheme.formats[k];
return { file: to.replace(/all$/g,z.outFormat), fmt: z };
}) : [{ file: to, fmt: theTheme.getFormat( fmat.slice(1) ) }]);
var fmt = rez.meta && rez.meta.format === 'FRESH@0.1.0' ? 'fresh':'jars';
var validate = validator( schemas[ fmt ], { // Note [1]
formats: { date: /^\d{4}(?:-(?:0[0-9]{1}|1[0-2]{1})(?:-[0-9]{2})?)?$/ }
});
isValid = validate( rez );
if( !isValid ) {
style = 'warn';
errors = validate.errors;
}
}
catch(ex) {
}
_log( 'Validating '.info + rep.file.infoBold + ' against '.info +
fmt.replace('jars','JSON Resume').toUpperCase().infoBold + ' schema: '.info + (isValid ? 'VALID!' : 'INVALID')[style].bold );
errors.forEach(function(err,idx){
_log( '--> '.bold.yellow + ( err.field.replace('data.','resume.').toUpperCase()
+ ' ' + err.message).yellow );
});
// Run the transformation!
var finished = targets.map( function(t) { return single(t, theTheme); });
});
}
/**
Convert between FRESH and JRS formats.
*/
function convert( src, dst, opts, logger ) {
_log = logger || console.log;
if( !src || !src.length ) { throw { fluenterror: 6 }; }
if( !dst || !dst.length ) {
if( src.length === 1 ) { throw { fluenterror: 5 }; }
else if( src.length === 2 ) { dst = [ src[1] ]; src = [ src[0] ]; }
else { throw { fluenterror: 5 }; }
// Don't send the client back empty-handed
return { sheet: rez, targets: targets, processed: finished };
}
if( src && dst && src.length && dst.length && src.length !== dst.length ) {
throw { fluenterror: 7 };
/**
Generate a single resume of a specific format.
@param f Full path to the destination resume to generate, for example,
"/foo/bar/resume.pdf" or "c:\foo\bar\resume.txt".
*/
function single( targInfo, theme ) {
try {
var f = targInfo.file
, fType = targInfo.fmt.outFormat
, fName = path.basename(f, '.' + fType)
, theFormat;
// If targInfo.fmt.files exists, this theme has an explicit "files"
// section in its theme.json file.
if( targInfo.fmt.files && targInfo.fmt.files.length ) {
_log( 'Generating '.useful +
targInfo.fmt.outFormat.toUpperCase().useful.bold +
' resume: '.useful + path.relative(process.cwd(), f ).useful.bold);
theFormat = _fmts.filter(
function(fmt) { return fmt.name === targInfo.fmt.outFormat; })[0];
MKDIRP.sync( path.dirname( f ) ); // Ensure dest folder exists;
theFormat.gen.generate( rez, f, _opts );
// targInfo.fmt.files.forEach( function( form ) {
//
// if( form.action === 'transform' ) {
// var theFormat = _fmts.filter( function( fmt ) {
// return fmt.name === targInfo.fmt.outFormat;
// })[0];
// MKDIRP.sync( path.dirname( f ) ); // Ensure dest folder exists;
// theFormat.gen.generate( rez, f, _opts );
// }
// else if( form.action === null ) {
// // Copy the file
// }
//
// });
}
// Otherwise the theme has no files section
else {
_log( 'Generating '.useful +
targInfo.fmt.outFormat.toUpperCase().useful.bold +
' resume: '.useful + path.relative(process.cwd(), f ).useful.bold);
theFormat = _fmts.filter(
function(fmt) { return fmt.name === targInfo.fmt.outFormat; })[0];
MKDIRP.sync( path.dirname( f ) ); // Ensure dest folder exists;
theFormat.gen.generate( rez, f, _opts );
}
}
catch( ex ) {
_err( ex );
}
}
var sheets = loadSourceResumes( src );
sheets.forEach(function(sheet, idx){
var sourceFormat = sheet.imp.orgFormat === 'JRS' ? 'JRS' : 'FRESH';
var targetFormat = sourceFormat === 'JRS' ? 'FRESH' : 'JRS';
_log( 'Converting '.useful + sheet.imp.fileName.useful.bold + (' (' + sourceFormat + ') to ').useful + dst[0].useful.bold +
(' (' + targetFormat + ').').useful );
sheet.saveAs( dst[idx], targetFormat );
});
}
/**
Display help documentation.
*/
function help() {
console.log( FS.readFileSync( PATH.join(__dirname, 'use.txt'), 'utf8' ).useful.bold );
}
function loadSourceResumes( src, fn ) {
return src.map( function( res ) {
_log( 'Reading '.info + 'SOURCE'.infoBold + ' resume: '.info + res.cyan.bold );
return (fn && fn(res)) || (new FLUENT.FRESHResume()).open( res );
});
}
/**
Supported resume formats.
*/
var _fmts = [
{ name: 'html', ext: 'html', gen: new FLUENT.HtmlGenerator() },
{ name: 'txt', ext: 'txt', gen: new FLUENT.TextGenerator() },
{ name: 'doc', ext: 'doc', fmt: 'xml', gen: new FLUENT.WordGenerator() },
{ name: 'pdf', ext: 'pdf', fmt: 'html', is: false, gen: new FLUENT.HtmlPdfGenerator() },
{ name: 'md', ext: 'md', fmt: 'txt', gen: new FLUENT.MarkdownGenerator() },
{ name: 'json', ext: 'json', gen: new FLUENT.JsonGenerator() },
{ name: 'yml', ext: 'yml', fmt: 'yml', gen: new FLUENT.JsonYamlGenerator() }
];
/**
Default FluentCV options.
*/
var _opts = {
theme: 'modern',
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
/**
Handle an exception.
*/
function error( ex ) {
throw ex;
}
};
/**
Internal module interface. Used by FCV Desktop and HMR.
*/
return {
verbs: {
build: generate,
validate: validate,
convert: convert,
help: help
},
lib: require('./fluentlib'),
options: _opts,
formats: _fmts
};
/**
Validate 1 to N resumes in either FRESH or JSON Resume format.
*/
function validate( src, unused, opts, logger ) {
_log = logger || console.log;
if( !src || !src.length ) { throw { fluenterror: 6 }; }
var isValid = true;
}();
var validator = require('is-my-json-valid');
var schemas = {
fresh: require('FRESCA'),
jars: require('./core/resume.json')
};
// Load input resumes...
var sheets = loadSourceResumes(src, function( res ) {
try {
return {
file: res,
raw: FS.readFileSync( res, 'utf8' )
};
}
catch( ex ) {
throw ex;
}
});
sheets.forEach( function( rep ) {
var rez;
try {
rez = JSON.parse( rep.raw );
}
catch( ex ) {
_log('Validating '.info + rep.file.infoBold +
' against FRESH/JRS schema: '.info + 'ERROR!'.error.bold);
if (ex instanceof SyntaxError) {
// Invalid JSON
_log( '--> '.bold.red + rep.file.toUpperCase().red +
' contains invalid JSON. Unable to validate.'.red );
_log( (' INTERNAL: ' + ex).red );
}
else {
_log(('ERROR: ' + ex.toString()).red.bold);
}
return;
}
var isValid = false;
var style = 'useful';
var errors = [];
var fmt = rez.meta &&
(rez.meta.format === 'FRESH@0.1.0') ? 'fresh':'jars';
try {
var validate = validator( schemas[ fmt ], { // Note [1]
formats: {
date: /^\d{4}(?:-(?:0[0-9]{1}|1[0-2]{1})(?:-[0-9]{2})?)?$/
}
});
isValid = validate( rez );
if( !isValid ) {
style = 'warn';
errors = validate.errors;
}
}
catch(ex) {
}
_log( 'Validating '.info + rep.file.infoBold + ' against '.info +
fmt.replace('jars','JSON Resume').toUpperCase().infoBold +
' schema: '.info + (isValid ? 'VALID!' : 'INVALID')[style].bold );
errors.forEach(function(err,idx) {
_log( '--> '.bold.yellow +
(err.field.replace('data.','resume.').toUpperCase() + ' ' +
err.message).yellow );
});
});
}
/**
Convert between FRESH and JRS formats.
*/
function convert( src, dst, opts, logger ) {
_log = logger || console.log;
if( !src || !src.length ) { throw { fluenterror: 6 }; }
if( !dst || !dst.length ) {
if( src.length === 1 ) { throw { fluenterror: 5 }; }
else if( src.length === 2 ) { dst = [ src[1] ]; src = [ src[0] ]; }
else { throw { fluenterror: 5 }; }
}
if( src && dst && src.length && dst.length && src.length !== dst.length ) {
throw { fluenterror: 7 };
}
var sheets = loadSourceResumes( src );
sheets.forEach(function(sheet, idx){
var sourceFormat = sheet.imp.orgFormat === 'JRS' ? 'JRS' : 'FRESH';
var targetFormat = sourceFormat === 'JRS' ? 'FRESH' : 'JRS';
_log( 'Converting '.useful + sheet.imp.fileName.useful.bold + (' (' +
sourceFormat + ') to ').useful + dst[0].useful.bold +
(' (' + targetFormat + ').').useful );
sheet.saveAs( dst[idx], targetFormat );
});
}
/**
Create a new empty resume in either FRESH or JRS format.
*/
function create( src, dst, opts, logger ) {
_log = logger || console.log;
dst = src || ['resume.json'];
dst.forEach( function( t ) {
var safeFormat = opts.format.toUpperCase();
_log('Creating new '.useful +safeFormat.useful.bold +
' resume: '.useful + t.useful.bold);
MKDIRP.sync( path.dirname( t ) ); // Ensure dest folder exists;
FLUENT[ safeFormat + 'Resume' ].default().save( t );
});
}
/**
Display help documentation.
*/
function help() {
console.log( FS.readFileSync( PATH.join(__dirname, 'use.txt'), 'utf8' )
.useful.bold );
}
function loadSourceResumes( src, fn ) {
return src.map( function( res ) {
_log( 'Reading '.info + 'SOURCE'.infoBold + ' resume: '.info +
res.cyan.bold );
return (fn && fn(res)) || (new FLUENT.FRESHResume()).open( res );
});
}
/**
Supported resume formats.
*/
var _fmts = [
{ name: 'html', ext: 'html', gen: new FLUENT.HtmlGenerator() },
{ name: 'txt', ext: 'txt', gen: new FLUENT.TextGenerator() },
{ name: 'doc', ext: 'doc', fmt: 'xml', gen: new FLUENT.WordGenerator() },
{ name: 'pdf', ext: 'pdf', fmt: 'html', is: false, gen: new FLUENT.HtmlPdfGenerator() },
{ name: 'md', ext: 'md', fmt: 'txt', gen: new FLUENT.MarkdownGenerator() },
{ name: 'json', ext: 'json', gen: new FLUENT.JsonGenerator() },
{ name: 'yml', ext: 'yml', fmt: 'yml', gen: new FLUENT.JsonYamlGenerator() },
{ name: 'latex', ext: 'tex', fmt: 'latex', gen: new FLUENT.LaTeXGenerator() }
];
/**
Default FluentCV options.
*/
var _opts = {
theme: 'modern',
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
}
};
/**
Internal module interface. Used by FCV Desktop and HMR.
*/
return {
verbs: {
build: generate,
validate: validate,
convert: convert,
new: create,
help: help
},
lib: require('./fluentlib'),
options: _opts,
formats: _fmts
};
}();
}());
// [1]: JSON.parse throws SyntaxError on invalid JSON. See:
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse

View File

@ -1,6 +1,7 @@
/**
External API surface for FluentCV:CLI.
@license MIT. Copyright (c) 2015 James M. Devlin / FluentDesk
@license MIT. Copyright (c) 2015 James M. Devlin / FluentDesk.
@module fluentlib.js
*/
module.exports = {
@ -16,5 +17,6 @@ module.exports = {
MarkdownGenerator: require('./gen/markdown-generator'),
JsonGenerator: require('./gen/json-generator'),
YamlGenerator: require('./gen/yaml-generator'),
JsonYamlGenerator: require('./gen/json-yaml-generator')
JsonYamlGenerator: require('./gen/json-yaml-generator'),
LaTeXGenerator: require('./gen/latex-generator')
};

View File

@ -1,6 +1,7 @@
/**
Base resume generator for FluentCV.
@license Copyright (c) 2015 | James M. Devlin
Definition of the BaseGenerator class.
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
@module base-generator.js
*/
(function() {

View File

@ -1,6 +1,7 @@
/**
HTML resume generator for FluentCV.
@license Copyright (c) 2015 James M. Devlin / FluentDesk
Definition of the HTMLGenerator class.
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
@module html-generator.js
*/
(function() {
@ -21,12 +22,12 @@ HTML resume generator for FluentCV.
the HTML resume prior to saving.
*/
onBeforeSave: function( info ) {
var cssSrc = PATH.join( info.theme.folder, 'templates', '*.css' )
var cssSrc = PATH.join( info.theme.folder, 'src', '*.css' )
, outFolder = PATH.parse( info.outputFile ).dir, that = this;
info.theme.cssFiles.forEach( function( f ) {
var fi = PATH.parse( f[1].path );
FS.copySync( f[1].path, PATH.join( outFolder, fi.base ), { clobber: true }, function( e ) {
var fi = PATH.parse( f.path );
FS.copySync( f.path, PATH.join( outFolder, fi.base ), { clobber: true }, function( e ) {
throw { fluenterror: that.codes.copyCss, data: [cssSrc,cssDst] };
});
});

View File

@ -1,6 +1,7 @@
/**
Definition of the HtmlPdfGenerator class.
@license Copyright (c) 2015 James M. Devlin / FluentDesk
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
@module html-pdf-generator.js
*/
(function() {

View File

@ -1,6 +1,7 @@
/**
Definition of the JsonGenerator class.
@license Copyright (c) 2015 | James M. Devlin
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
@module json-generator.js
*/
var BaseGenerator = require('./base-generator');

View File

@ -1,7 +1,7 @@
/**
A JSON-driven YAML resume generator for FluentLib.
Definition of the JsonYamlGenerator class.
@module json-yaml-generator.js
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
*/
(function() {

View File

@ -0,0 +1,18 @@
/**
Definition of the LaTeXGenerator class.
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
@module latex-generator.js
*/
var TemplateGenerator = require('./template-generator');
/**
LaTeXGenerator generates a LaTeX resume via TemplateGenerator.
*/
var LaTeXGenerator = module.exports = TemplateGenerator.extend({
init: function(){
this._super( 'latex', 'tex' );
}
});

View File

@ -1,6 +1,7 @@
/**
Markdown resume generator for FluentCV.
@license Copyright (c) 2015 by James M. Devlin. All rights reserved.
Definition of the MarkdownGenerator class.
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
@module markdown-generator.js
*/
var TemplateGenerator = require('./template-generator');

View File

@ -1,176 +1,246 @@
/**
Template-based resume generator base for FluentCV.
@license Copyright (c) 2015 | James M. Devlin
Definition of the TemplateGenerator class.
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
@module template-generator.js
*/
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');
(function() {
// Default options.
var _defaultOpts = {
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(); },
link: function( name, url ) { return url ?
'<a href="' + url + '">' + name + '</a>' : name }
},
prettify: { // ← See https://github.com/beautify-web/js-beautify#options
indent_size: 2,
unformatted: ['em','strong','a'],
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;
},
var FS = require( 'fs-extra' )
, _ = require( 'underscore' )
, MD = require( 'marked' )
, XML = require( 'xml-escape' )
, PATH = require('path')
, MKDIRP = require('mkdirp')
, BaseGenerator = require( './base-generator' )
, EXTEND = require('../utils/extend')
, Theme = require('../core/theme');
/** 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 options.
var _defaultOpts = {
engine: 'underscore',
keepBreaks: true,
freezeBreaks: false,
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(); },
link: function( name, url ) { return url ?
'<a href="' + url + '">' + name + '</a>' : name; }
},
prettify: { // ← See https://github.com/beautify-web/js-beautify#options
indent_size: 2,
unformatted: ['em','strong','a'],
max_char: 80, // ← See lib/html.js in above-linked repo
//wrap_line_length: 120, <-- Don't use this
}
};
/** Default generation method for template-based generators. */
generate: function( rez, f, opts ) {
// Carry over options
this.opts = EXTEND( true, { }, _defaultOpts, opts );
/**
TemplateGenerator performs resume generation via local Handlebar or Underscore
style template expansion and is appropriate for text-based formats like HTML,
plain text, and XML versions of Microsoft Word, Excel, and OpenOffice.
@class TemplateGenerator
*/
var TemplateGenerator = module.exports = BaseGenerator.extend({
init: function( outputFormat, templateFormat, cssFile ){
this._super( outputFormat );
this.tplFormat = templateFormat || outputFormat;
},
invoke: function( rez, themeMarkup, cssInfo, opts ) {
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.
@method generate
@param rez A FreshResume object.
@param f Full path to the output resume file to generate.
@param opts Generator options.
*/
generate: function( rez, f, opts ) {
// Carry over options
this.opts = EXTEND( true, { }, _defaultOpts, opts );
// Load the theme
var themeInfo = themeFromMoniker.call( this );
var theme = themeInfo.theme;
var tFolder = themeInfo.folder;
var tplFolder = PATH.join( tFolder, 'src' );
var outFolder = PATH.parse(f).dir;
var curFmt = theme.getFormat( this.format );
var that = this;
// "Generate": process individual files within the theme
curFmt.files.forEach(function(tplInfo){
if( tplInfo.action === 'transform' ) {
transform.call( that, rez, f, tplInfo, theme, outFolder );
}
else if( tplInfo.action === null && theme.explicit ) {
var thisFilePath = PATH.join(outFolder, tplInfo.orgPath);
try {
MKDIRP.sync( PATH.dirname(thisFilePath) );
FS.copySync( tplInfo.path, thisFilePath );
}
catch(ex) {
console.log(ex);
}
}
});
// Some themes require a symlink structure. If so, create it.
if( curFmt.symLinks ) {
Object.keys( curFmt.symLinks ).forEach( function(loc) {
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';
FS.symlinkSync( absTarg, absLoc, type);
});
}
},
/**
Perform a single resume JSON-to-DEST resume transformation.
@param json A FRESH or JRS resume object.
@param jst The stringified template data
@param format The format name, such as "html" or "latex"
@param cssInfo Needs to be refactored.
@param opts Options and passthrough data.
*/
single: function( json, jst, format, cssInfo, opts, theme ) {
this.opts.freezeBreaks && ( jst = freeze(jst) );
var eng = require( '../eng/' + ((opts.themeObj && opts.themeObj.engine) ||
opts.engine) + '-generator' );
var result = eng( json, jst, format, cssInfo, opts, theme );
this.opts.freezeBreaks && ( result = unfreeze(result) );
return result;
}
});
/**
Export the TemplateGenerator function/ctor.
*/
module.exports = TemplateGenerator;
/**
Given a theme title, load the corresponding theme.
*/
function themeFromMoniker() {
// Verify the specified theme name/path
var tFolder = PATH.join(
PATH.parse( require.resolve('fluent-themes') ).dir,
this.opts.theme
);
var exists = require('../utils/file-exists');
if (!exists( tFolder )) {
if( !exists( tFolder ) ) {
tFolder = PATH.resolve( this.opts.theme );
if (!exists( tFolder )) {
throw { fluenterror: this.codes.themeNotFound, data: 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 );
var t = this.opts.themeObj || new Theme().open( tFolder );
// Load theme and CSS data
var tplFolder = PATH.join( tFolder, 'templates' );
var curFmt = theme.getFormat( this.format );
var cssInfo = { file: curFmt.css ? curFmt.cssPath : null, data: curFmt.css || null };
// Compile and invoke the template!
var mk = this.single( rez, curFmt.data, this.format, cssInfo, opts );
this.onBeforeSave && (mk = this.onBeforeSave( { mk: mk, theme: theme, outputFile: 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, '');
// 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;
// Load the theme and format
return {
theme: t,
folder: tFolder
};
}
});
/**
Export the TemplateGenerator function/ctor.
*/
module.exports = TemplateGenerator;
/**
Transform a single subfile.
*/
function transform( rez, f, tplInfo, theme, outFolder ) {
var cssInfo = { file: tplInfo.css ? tplInfo.cssPath : null, data: tplInfo.css || null };
var mk = this.single( rez, tplInfo.data, this.format, cssInfo, this.opts, theme );
this.onBeforeSave && (mk = this.onBeforeSave( { mk: mk, theme: theme, outputFile: f } ));
var thisFilePath = PATH.join( outFolder, tplInfo.orgPath );
try {
MKDIRP.sync( PATH.dirname( tplInfo.major ? f : thisFilePath) );
FS.writeFileSync( tplInfo.major ? f : thisFilePath, mk, { encoding: 'utf8', flags: 'w' } );
this.onAfterSave && (mk = this.onAfterSave( { outputFile: (tplInfo.major ? f : thisFilePath), mk: mk } ));
}
catch(ex) {
console.log(ex);
}
}
/**
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' )
};
/**
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' )
};
}());

View File

@ -1,6 +1,7 @@
/**
Plain text resume generator for FluentCV.
@license Copyright (c) 2015 by James M. Devlin. All rights reserved.
Definition of the TextGenerator class.
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
@module text-generator.js
*/
var TemplateGenerator = require('./template-generator');

View File

@ -1,13 +1,19 @@
/**
MS Word resume generator for FluentCV.
@license Copyright (c) 2015 by James M. Devlin. All rights reserved.
Definition of the WordGenerator class.
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
@module word-generator.js
*/
var TemplateGenerator = require('./template-generator');
var WordGenerator = module.exports = TemplateGenerator.extend({
(function() {
init: function(){
this._super( 'doc', 'xml' );
},
var TemplateGenerator = require('./template-generator');
var WordGenerator = module.exports = TemplateGenerator.extend({
});
init: function(){
this._super( 'doc', 'xml' );
}
});
}());

View File

@ -1,6 +1,7 @@
/**
XML resume generator for FluentCV.
@license Copyright (c) 2015 | James M. Devlin
Definition of the XMLGenerator class.
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
@module xml-generator.js
*/
var BaseGenerator = require('./base-generator');
@ -8,7 +9,7 @@ var BaseGenerator = require('./base-generator');
/**
The XmlGenerator generates an XML resume via the TemplateGenerator.
*/
var XmlGenerator = module.exports = BaseGenerator.extend({
var XMLGenerator = module.exports = BaseGenerator.extend({
init: function(){
this._super( 'xml' );

View File

@ -1,7 +1,7 @@
/**
A YAML resume generator for FluentLib.
Definition of the YAMLGenerator class.
@module yaml-generator.js
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
*/
@ -13,7 +13,7 @@ A YAML resume generator for FluentLib.
YamlGenerator generates a YAML-formatted resume via TemplateGenerator.
*/
var YamlGenerator = module.exports = TemplateGenerator.extend({
var YAMLGenerator = module.exports = TemplateGenerator.extend({
init: function(){
this._super( 'yml', 'yml' );

View File

@ -3,6 +3,7 @@
/**
Command-line interface (CLI) for FluentCV:CLI.
@license MIT. Copyright (c) 2015 James M. Devlin / FluentDesk.
@module index.js
*/
var ARGS = require( 'minimist' )
@ -59,17 +60,14 @@ function main() {
var splitAt = _.indexOf( params, 'to' );
if( splitAt === a._.length - 1 ) {
// 'TO' cannot be the last argument
logMsg('Please '.warn + 'specify an output file'.warnBold +
' for this operation or '.warn + 'omit the TO keyword'.warnBold + '.'.warn );
logMsg('Please '.warn + 'specify an output file'.warn.bold +
' for this operation or '.warn + 'omit the TO keyword'.warn.bold +
'.'.warn );
return;
}
var src = a._.slice(1, splitAt === -1 ? undefined : splitAt );
var dst = splitAt === -1 ? [] : a._.slice( splitAt + 1 );
// Preload our params array
//var dst = (a.o && ((typeof a.o === 'string' && [ a.o ]) || a.o)) || [];
//dst = (dst === true) ? [] : dst; // Handle -o with missing output file
var parms = [ src, dst, opts, logMsg ];
// Invoke the action
@ -82,10 +80,11 @@ function logMsg( msg ) {
}
function getOpts( args ) {
var noPretty = args['nopretty'] || args.n;
var noPretty = args.nopretty || args.n;
noPretty = noPretty && (noPretty === true || noPretty === 'true');
return {
theme: args.t || 'modern',
format: args.f || 'FRESH',
prettify: !noPretty,
silent: args.s || args.silent
};
@ -103,15 +102,15 @@ function handleError( ex ) {
case 3: msg = 'Please '.guide + 'specify a valid input resume'.guide.bold + ' in FRESH or JSON Resume format.'.guide; break;
case 4: msg = title + "\nPlease ".guide + "specify a command".guide.bold + " (".guide +
Object.keys( FCMD.verbs ).map( function(v, idx, ar) {
return (idx === ar.length - 1 ? 'or '.guide : '')
+ v.toUpperCase().guide;
}).join(', '.guide) + ") to get started.\n\n".guide + FS.readFileSync( PATH.join(__dirname, 'use.txt'), 'utf8' ).info.bold;
return (idx === ar.length - 1 ? 'or '.guide : '') +
v.toUpperCase().guide;
}).join(', '.guide) + ").\n\n".guide + FS.readFileSync( PATH.join(__dirname, 'use.txt'), 'utf8' ).info.bold;
break;
//case 4: msg = title + '\n' + ; break;
case 5: msg = 'Please '.guide + 'specify the output resume file'.guide.bold + ' that should be created in the new format.'.guide; break;
case 6: msg = 'Please '.guide + 'specify a valid input resume'.guide.bold + ' in either FRESH or JSON Resume format.'.guide; break;
case 7: msg = 'Please '.guide + 'specify an output file name'.guide.bold + ' for every input file you wish to convert.'.guide; break;
};
}
exitCode = ex.fluenterror;
}

View File

@ -1,22 +1,25 @@
Usage:
fluentcv <COMMAND> <SOURCES> [TO <TARGETS>] [-t <THEME>]
fluentcv <COMMAND> <SOURCES> [TO <TARGETS>] [-t <THEME>] [-f <FORMAT>]
<COMMAND> should be BUILD, CONVERT, VALIDATE, or HELP. <SOURCES> should
<COMMAND> should be BUILD, NEW, CONVERT, VALIDATE, or HELP. <SOURCES> should
be the path to one or more FRESH or JSON Resume format resumes. <TARGETS>
should be the name of the destination resume to be created, if any. The
<THEME> parameter should be the name of a predefined theme (for example:
COMPACT, MINIMIST, MODERN, or HELLO-WORLD) or the relative path to a
custom theme.
COMPACT, MINIMIST, MODERN, or HELLO-WORLD) or the relative path to a custom
theme. <FORMAT> should be either FRESH (for a FRESH-format resume) or JRS
(for a JSON Resume-format resume).
fluentcv BUILD resume.json TO out/resume.all
fluentcv NEW resume.json
fluentcv CONVERT resume.json TO resume-jrs.json
fluentcv VALIDATE resume.json
Both SOURCES and TARGETS can accept multiple files:
fluentCV BUILD r1.json r2.json TO out/resume.all out2/resume.html
fluentCV NEW r1.json r2.json r3.json
fluentCV VALIDATE resume.json resume2.json resume3.json
See https://github.com/fluentdesk/fluentCV/blob/master/README.md
for more information.
See https://github.com/fluentdesk/fluentCV/blob/master/README.md for more
information.

View File

@ -1,3 +1,8 @@
/**
Definition of John Resig's `Class` class.
@module class.js
*/
/* Simple JavaScript Inheritance
* By John Resig http://ejohn.org/
* MIT Licensed.
@ -41,7 +46,7 @@
return ret;
};
})(name, prop[name]) :
})(name, prop[name]) : // jshint ignore:line
prop[name];
}

View File

@ -1,6 +1,7 @@
/**
Plain JavaScript replacement of jQuery .extend based on jQuery sources.
@license Copyright (c) 2015 by James M. Devlin. All rights reserved.
Definition of the `extend` method.
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
@module extend.js
*/
function _extend() {

View File

@ -1,6 +1,7 @@
/**
File-exists checker for Node.js.
@license Copyright (c) 2015 | James M. Devlin
Definition of the `fileExists` method.
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
@module file-exists.js
*/
var FS = require('fs');

64
src/utils/html-to-wpml.js Normal file
View File

@ -0,0 +1,64 @@
/**
Definition of the Markdown to WordProcessingML conversion routine.
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
@module html-to-wpml.js
*/
(function(){
var _ = require('underscore');
var HTML5Tokenizer = require('simple-html-tokenizer');
module.exports = function( html ) {
var final = '';
var is_bold = false, is_italic = false, is_link = false;
var depth = 0;
var tokens = HTML5Tokenizer.tokenize( html );
_.each( tokens, function( tok ) {
switch( tok.type ) {
case 'StartTag':
switch( tok.tagName ) {
case 'p':
final += '<w:p>';
break;
case 'strong':
is_bold = true;
break;
case 'em':
is_italic = true;
break;
case 'a':
is_link = true;
break;
}
break;
case 'EndTag':
switch( tok.tagName ) {
case 'p':
final += '</w:p>';
break;
case 'strong':
is_bold = false;
break;
case 'em':
is_italic = false;
break;
case 'a':
is_link = false;
break;
}
break;
case 'Chars':
var style = is_bold ? '<w:b/>' : '';
style += is_italic ? '<w:i/>': '';
final += '<w:r><w:rPr>' + style + '</w:rPr><w:t>' + tok.chars + '</w:t></w:r>';
break;
}
});
return final;
};
}());

View File

@ -1,6 +1,7 @@
/**
String utility functions.
@license Copyright (c) 2015 | James M. Devlin
Definitions of string utility functions.
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
@module string.js
*/
/**

View File

@ -0,0 +1,224 @@
{
"basics": {
"name": "Jane Doe",
"label": "Senior Developer / Code Ninja",
"summary": "**Full-stack software developer with 6+ years industry experience** specializing in scalable cloud architectures for this, that, and the other. A native of southern CA, Jane enjoys hiking, mystery novels, and the company of Rufus, her two-year-old beagle.",
"website": "http://jane-doe.me",
"phone": "1-650-999-7777",
"email": "jdoe@onecoolstartup.io",
"picture": "jane_doe.png",
"location": {
"address": "Jane Doe\n123 Somewhere Rd.\nMountain View, CA 94035",
"postalCode": "94035",
"city": "Mountain View",
"countryCode": "US",
"region": "CA"
},
"profiles": [
{
"network": "GitHub",
"username": "jane-doe-was-here",
"url": "https://github.com/jane-doe-was-here"
},
{
"network": "Twitter",
"username": "jane-doe-was-here",
"url": "https://twitter.com/jane-doe-was-here"
}
]
},
"work": [
{
"company": "One Cool Startup",
"website": "https://onecool.io/does-not-exist",
"position": "Head Code Ninja",
"startDate": "2013-09",
"summary": "Development team manager for OneCoolApp and OneCoolWebsite, a free social network tiddlywink generator and lifestyle portal with over 200,000 users.",
"highlights": [
"Managed a 5-person development team",
"Accomplishment 2",
"Etc."
]
},
{
"company": "Veridian Dynamics",
"website": "https://en.wikipedia.org/wiki/Better_Off_Ted#Plot",
"position": "Principal Developer",
"startDate": "2011-07",
"endDate": "2013-08",
"summary": "Developer on numerous projects culminating in technical lead role for the [Jabberwocky project](http://betteroffted.wikia.com/wiki/Jabberwocky) and promotion to principal developer.",
"highlights": [
"Managed a 5-person development team",
"Accomplishment 2",
"Etc."
]
},
{
"company": "Stark Industries",
"position": "IT Administrator",
"startDate": "2008-10",
"endDate": "2011-06",
"summary": "Junior programmer with heavy code responsibilities. Promoted to intermediate role after 6 months.",
"highlights": [
"Promoted to intermediate developer after 6 months",
"Accomplishment 2",
"Etc."
]
},
{
"company": "Dunder Mifflin",
"position": "Intern",
"startDate": "2008-06",
"endDate": "2008-09",
"summary": "Performed IT administration and deployments for Dunder Mifflin.",
"highlights": [
"Supervised roll-out of Dunder Mifflin Infinity website.",
"Performed mission-critical system backups and ",
"Etc."
]
}
],
"education": [
{
"institution": "Cornell University",
"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": "Programming",
"keywords": [
"C++",
"Ruby",
"Xcode"
]
},
{
"name": "Project Management",
"keywords": [
"Agile"
]
}
],
"volunteer": [
{
"organization": "Technology for Tots",
"position": "Technical Consultant",
"startDate": "2003-11",
"endDate": "2005-06",
"website": "http://technology-for-tots.org",
"summary": "Summary of this volunteer stint.",
"highlights": [
"Accomplishment 1",
"Accomplishment 2",
"etc"
]
},
{
"organization": "US Army Reserves",
"position": "NCO",
"startDate": "1999-11",
"endDate": "2003-06",
"website": "http://www.usar.army.mil/",
"summary": "Summary of this military stint.",
"highlights": [
"Accomplishment 1",
"Accomplishment 2",
"etc"
]
}
],
"awards": [
{
"title": "Honorable Mention",
"date": "2012",
"awarder": "Google"
},
{
"title": "Summa cum laude",
"date": "2012",
"awarder": "Cornell University"
}
],
"publications": [
{
"name": "Building User Interfaces with Electron and Atom",
"publisher": "Code Project",
"releaseDate": "2011",
"website": "http://codeproject.com/build-ui-electron-atom.aspx"
},
{
"name": "Jane Doe Unplugged",
"publisher": "self",
"releaseDate": "2011",
"website": "http://jane-doe.me"
},
{
"name": "Teach Yourself GORFF in 21 Days",
"publisher": "Amazon",
"releaseDate": "2008",
"website": "http://url.to.publication.com/blah",
"summary": "A primer on the programming language of GORFF, whose for loops are coterminous with all of time and space."
}
],
"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

@ -17,8 +17,8 @@ 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, 'jrs-exemplar/richard-hendriks.json' );
var fileB = path.join( __dirname, 'sandbox/richard-hendriks.json' );
var fileA = path.join( __dirname, 'resumes/jrs/richard-hendriks.json' );
var fileB = path.join( __dirname, 'sandbox/richard-hendriks.json' );
_sheet = new FRESHResume().open( fileA );
_sheet.saveAs( fileB, 'JRS' );

View File

@ -65,9 +65,3 @@ describe('jane-doe.json (FRESH)', function () {
});
// describe('subtract', function () {
// it('should return -1 when passed the params (1, 2)', function () {
// expect(math.subtract(1, 2)).to.equal(-1);
// });
// });

View File

@ -9,13 +9,14 @@ var chai = require('chai')
chai.config.includeStack = false;
describe('fullstack.json (JRS)', function () {
describe('jane-doe.json (JRS)', function () {
var _sheet;
it('should open without throwing an exception', function () {
function tryOpen() {
_sheet = new JRSResume().open( 'node_modules/resample/fullstack/in/resume.json' );
_sheet = new JRSResume().open(
path.join( __dirname, 'resumes/jrs/jane-doe.json' ) );
}
tryOpen.should.not.Throw();
});
@ -32,36 +33,31 @@ describe('fullstack.json (JRS)', function () {
).to.equal( true );
});
it('should have a work duration of 11 years', function() {
_sheet.computed.numYears.should.equal( 11 );
it('should have a work duration of 7 years', function() {
_sheet.basics.computed.numYears.should.equal( 7 );
});
it('should save without throwing an exception', function(){
function trySave() {
_sheet.save( 'tests/sandbox/fullstack.json' );
_sheet.save( 'tests/sandbox/jane-doe.json' );
}
trySave.should.not.Throw();
});
it('should not be modified after saving', function() {
var savedSheet = new JRSResume().open( 'tests/sandbox/fullstack.json' );
var savedSheet = new JRSResume().open( 'tests/sandbox/jane-doe.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.imp.raw ) );
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", validate.errors, "\n\n");
"Validation errors:\n\n", _sheet.basics.imp.validationErrors, "\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);
// });
// });

52
tests/test-themes.js Normal file
View File

@ -0,0 +1,52 @@
var chai = require('chai')
, expect = chai.expect
, should = chai.should()
, path = require('path')
, _ = require('underscore')
, FRESHResume = require('../src/core/fresh-resume')
, FCMD = require( '../src/fluentcmd')
, validator = require('is-my-json-valid')
, COLORS = require('colors');
chai.config.includeStack = false;
describe('Testing themes', function () {
var _sheet;
COLORS.setTheme({
title: ['white','bold'],
info: process.platform === 'win32' ? 'gray' : ['white','dim'],
infoBold: ['white','dim'],
warn: 'yellow',
error: 'red',
guide: 'yellow',
status: 'gray',//['white','dim'],
useful: 'green',
});
function genTheme( themeName ) {
it( themeName.toUpperCase() + ' theme should generate without throwing an exception', function () {
function tryOpen() {
var src = ['node_modules/FRESCA/exemplar/jane-doe.json'];
var dst = ['tests/sandbox/hello-world/resume.all'];
var opts = {
theme: themeName,
format: 'FRESH',
prettify: true,
silent: false
};
FCMD.verbs.build( src, dst, opts );
}
tryOpen.should.not.Throw();
});
}
genTheme('hello-world');
genTheme('compact');
genTheme('modern');
genTheme('minimist');
genTheme('awesome');
});