mirror of
https://github.com/JuanCanham/HackMyResume.git
synced 2024-11-05 09:56:22 +00:00
commit
6f37ccdee3
58
CONTRIBUTING.md
Normal file
58
CONTRIBUTING.md
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
Contributing
|
||||||
|
============
|
||||||
|
|
||||||
|
*Note: HackMyResume is also available as [FluentCV][fcv]. Contributors are
|
||||||
|
credited in both.*
|
||||||
|
|
||||||
|
HackMyResume needs your help! Our contribution workflow is based on [GitHub
|
||||||
|
Flow][flow] and we respond to all pull requests and issues, usually within 24
|
||||||
|
hours. HackMyResume has no corporate affiliation and no commercial basis, which
|
||||||
|
allows the project to maintain a strict user-first policy, rapid development
|
||||||
|
velocity, and a liberal stance on contributions and exotic functionality in
|
||||||
|
keeping with the spirit (and name) of the tool.
|
||||||
|
|
||||||
|
In short, your code is welcome here.
|
||||||
|
|
||||||
|
## How To Contribute
|
||||||
|
|
||||||
|
1. Optional: [**open an issue**][iss] identifying the feature or bug you'd like
|
||||||
|
to implement or fix. This step isn't required — you can start hacking away on
|
||||||
|
HackMyResume without clearing it with us — but helps avoid duplication of work
|
||||||
|
and ensures that your changes will be accepted once submitted.
|
||||||
|
2. **Fork and clone** the HackMyResume project.
|
||||||
|
3. Ideally, **create a new feature branch** (eg, `feat/new-awesome-feature` or
|
||||||
|
similar; call it whatever you like) to perform your work in.
|
||||||
|
4. **Install dependencies** by running `npm install` in the top-level
|
||||||
|
HackMyResume folder.
|
||||||
|
5. Make your **commits** as usual.
|
||||||
|
6. **Verify** your changes locally with `npm test`.
|
||||||
|
7. **Push** your commits.
|
||||||
|
7. **Submit a pull request** from your feature branch to the HackMyResume `dev`
|
||||||
|
branch.
|
||||||
|
8. We'll typically **respond** within 24 hours.
|
||||||
|
9. Your awesome changes will be **merged** after verification.
|
||||||
|
|
||||||
|
## Project Maintainers
|
||||||
|
|
||||||
|
HackMyResume is currently maintained by [hacksalot][ha] with assistance from
|
||||||
|
[tomheon][th] and our awesome [contributors][awesome]. Please direct all official
|
||||||
|
or internal inquiries to:
|
||||||
|
|
||||||
|
```
|
||||||
|
admin@hackmyresume.com
|
||||||
|
```
|
||||||
|
|
||||||
|
You can reach hacksalot directly at:
|
||||||
|
|
||||||
|
```
|
||||||
|
hacksalot@indevious.com
|
||||||
|
```
|
||||||
|
|
||||||
|
Thanks! See you out there in the trenches.
|
||||||
|
|
||||||
|
[fcv]: https://github.com/fluentdesk/fluentcv
|
||||||
|
[flow]: https://guides.github.com/introduction/flow/
|
||||||
|
[iss]: https://github.com/hacksalot/HackMyResume/issues
|
||||||
|
[ha]: https://github.com/hacksalot
|
||||||
|
[th]: https://github.com/tomheon
|
||||||
|
[awesome]: https://github.com/hacksalot/HackMyResume/graphs/contributors
|
10
Gruntfile.js
10
Gruntfile.js
@ -14,10 +14,10 @@ module.exports = function (grunt) {
|
|||||||
ui: 'bdd',
|
ui: 'bdd',
|
||||||
reporter: 'spec'
|
reporter: 'spec'
|
||||||
},
|
},
|
||||||
all: { src: ['tests/*.js'] }
|
all: { src: ['test/*.js'] }
|
||||||
},
|
},
|
||||||
|
|
||||||
clean: ['tests/sandbox'],
|
clean: ['test/sandbox'],
|
||||||
|
|
||||||
yuidoc: {
|
yuidoc: {
|
||||||
compile: {
|
compile: {
|
||||||
@ -38,7 +38,7 @@ module.exports = function (grunt) {
|
|||||||
laxcomma: true,
|
laxcomma: true,
|
||||||
expr: true
|
expr: true
|
||||||
},
|
},
|
||||||
all: ['Gruntfile.js', 'src/**/*.js', 'test/**/*.js']
|
all: ['Gruntfile.js', 'src/**/*.js', 'test/*.js']
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
@ -51,9 +51,9 @@ module.exports = function (grunt) {
|
|||||||
grunt.loadNpmTasks('grunt-contrib-clean');
|
grunt.loadNpmTasks('grunt-contrib-clean');
|
||||||
|
|
||||||
grunt.registerTask('test', 'Test the HackMyResume library.',
|
grunt.registerTask('test', 'Test the HackMyResume library.',
|
||||||
function( config ) { grunt.task.run( ['clean','simplemocha:all'] ); });
|
function( config ) { grunt.task.run( ['clean','jshint','simplemocha:all'] ); });
|
||||||
grunt.registerTask('document', 'Generate HackMyResume library documentation.',
|
grunt.registerTask('document', 'Generate HackMyResume library documentation.',
|
||||||
function( config ) { grunt.task.run( ['yuidoc'] ); });
|
function( config ) { grunt.task.run( ['yuidoc'] ); });
|
||||||
grunt.registerTask('default', [ 'jshint', 'test', 'yuidoc' ]);
|
grunt.registerTask('default', [ 'test', 'yuidoc' ]);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
102
README.md
102
README.md
@ -32,6 +32,7 @@ or Windows.
|
|||||||
- Support for multiple input and output resumes.
|
- Support for multiple input and output resumes.
|
||||||
- Use from your command line or [desktop][7].
|
- Use from your command line or [desktop][7].
|
||||||
- Free and open-source through the MIT license.
|
- Free and open-source through the MIT license.
|
||||||
|
- Updated daily.
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
@ -42,8 +43,36 @@ Install HackMyResume with NPM:
|
|||||||
```
|
```
|
||||||
|
|
||||||
Note: for PDF generation you'll need to install a copy of [wkhtmltopdf][3] for
|
Note: for PDF generation you'll need to install a copy of [wkhtmltopdf][3] for
|
||||||
your platform. For LaTeX generation you'll need a valid LaTeX environment with
|
your platform.
|
||||||
access to `xelatex` and similar.
|
|
||||||
|
## Installing Themes
|
||||||
|
|
||||||
|
HackMyResume supports both [FRESH][fresh-themes] and [JSON Resume][jrst]-style
|
||||||
|
résumé themes.
|
||||||
|
|
||||||
|
- FRESH themes currently come preinstalled with HackMyResume.
|
||||||
|
- JSON Resume themes can be installed from NPM, GitHub, or manually.
|
||||||
|
|
||||||
|
To install a JSON Resume theme, just `cd` to the folder where you want to store
|
||||||
|
your themes and run one of:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install with NPM
|
||||||
|
npm install jsonresume-theme-[theme-name]
|
||||||
|
|
||||||
|
# Install with GitHub
|
||||||
|
git clone https://github.com/[user-or-org]/[repo-name]
|
||||||
|
```
|
||||||
|
|
||||||
|
Then when you're ready to generate your resume, just reference the location of
|
||||||
|
the theme folder as you installed it:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
hackmyresume BUILD resume.json TO out/resume.all -t node_modules/jsonresume-theme-classy
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: You can use install themes anywhere on your file system. You don't need a
|
||||||
|
package.json or other NPM/Node infrastructure.
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
@ -98,9 +127,9 @@ 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.
|
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.
|
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).
|
LaTeX | .tex | A structured LaTeX document (or collection of documents) that can be processed with pdflatex, xelatex, and similar tools.
|
||||||
MS Word | .doc | A Microsoft Word office document.
|
MS Word | .doc | A Microsoft Word office document (XML-driven; WordProcessingML).
|
||||||
Adobe Acrobat (PDF) | .pdf | A binary PDF document driven by an HTML theme.
|
Adobe Acrobat (PDF) | .pdf | A binary PDF document driven by an HTML theme (through wkhtmltopdf).
|
||||||
plain text | .txt | A formatted plain text document appropriate for emails or copy-paste.
|
plain text | .txt | A formatted plain text document appropriate for emails or copy-paste.
|
||||||
JSON | .json | A JSON representation of the resume.
|
JSON | .json | A JSON representation of the resume.
|
||||||
YAML | .yml | A YAML representation of the resume.
|
YAML | .yml | A YAML representation of the resume.
|
||||||
@ -108,15 +137,6 @@ RTF | .rtf | Forthcoming.
|
|||||||
Textile | .textile | Forthcoming.
|
Textile | .textile | Forthcoming.
|
||||||
image | .png, .bmp | Forthcoming.
|
image | .png, .bmp | Forthcoming.
|
||||||
|
|
||||||
## Install
|
|
||||||
|
|
||||||
HackMyResume 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 **HackMyResume** with `[sudo] npm install hackmyresume -g`.
|
|
||||||
3. You're ready to go.
|
|
||||||
|
|
||||||
## Use
|
## Use
|
||||||
|
|
||||||
Assuming you've got a JSON-formatted resume handy, generating resumes in
|
Assuming you've got a JSON-formatted resume handy, generating resumes in
|
||||||
@ -132,19 +152,19 @@ theme (default to Modern). For example:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Generate all resume formats (HTML, PDF, DOC, TXT, YML, etc.)
|
# Generate all resume formats (HTML, PDF, DOC, TXT, YML, etc.)
|
||||||
hackmyresume build resume.json -o out/resume.all -t modern
|
hackmyresume BUILD resume.json TO out/resume.all -t modern
|
||||||
|
|
||||||
# Generate a specific resume format
|
# Generate a specific resume format
|
||||||
hackmyresume build resume.json TO out/resume.html
|
hackmyresume BUILD resume.json TO out/resume.html
|
||||||
hackmyresume build resume.json TO out/resume.pdf
|
hackmyresume BUILD resume.json TO out/resume.pdf
|
||||||
hackmyresume build resume.json TO out/resume.md
|
hackmyresume BUILD resume.json TO out/resume.md
|
||||||
hackmyresume build resume.json TO out/resume.doc
|
hackmyresume BUILD resume.json TO out/resume.doc
|
||||||
hackmyresume build resume.json TO out/resume.json
|
hackmyresume BUILD resume.json TO out/resume.json
|
||||||
hackmyresume build resume.json TO out/resume.txt
|
hackmyresume BUILD resume.json TO out/resume.txt
|
||||||
hackmyresume build resume.json TO out/resume.yml
|
hackmyresume BUILD resume.json TO out/resume.yml
|
||||||
|
|
||||||
# Specify 2 inputs and 3 outputs
|
# Specify 2 inputs and 3 outputs
|
||||||
hackmyresume build in1.json in2.json TO out.html out.doc out.pdf
|
hackmyresume BUILD in1.json in2.json TO out.html out.doc out.pdf
|
||||||
```
|
```
|
||||||
|
|
||||||
You should see something to the effect of:
|
You should see something to the effect of:
|
||||||
@ -171,8 +191,8 @@ For a predefined theme, include the theme name. For a custom theme, include the
|
|||||||
path to the custom theme's folder.
|
path to the custom theme's folder.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
hackmyresume build resume.json -t modern
|
hackmyresume BUILD resume.json TO out/rez.all -t modern
|
||||||
hackmyresume build resume.json -t ~/foo/bar/my-custom-theme/
|
hackmyresume BUILD resume.json TO OUT.rez.all -t ~/foo/bar/my-custom-theme/
|
||||||
```
|
```
|
||||||
|
|
||||||
As of v1.0.0, available predefined themes are `positive`, `modern`, `compact`,
|
As of v1.0.0, available predefined themes are `positive`, `modern`, `compact`,
|
||||||
@ -185,7 +205,7 @@ most generic to most specific:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Merge specific.json onto base.json and generate all formats
|
# Merge specific.json onto base.json and generate all formats
|
||||||
hackmyresume build base.json specific.json -o resume.all
|
hackmyresume BUILD base.json specific.json TO resume.all
|
||||||
```
|
```
|
||||||
|
|
||||||
This can be useful for overriding a base (generic) resume with information from
|
This can be useful for overriding a base (generic) resume with information from
|
||||||
@ -196,7 +216,7 @@ resume. Merging follows conventional [extend()][9]-style behavior and there's
|
|||||||
no arbitrary limit to how many resumes you can merge:
|
no arbitrary limit to how many resumes you can merge:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
hackmyresume build in1.json in2.json in3.json in4.json TO out.html out.doc
|
hackmyresume BUILD in1.json in2.json in3.json in4.json TO out.html out.doc
|
||||||
Reading JSON resume: in1.json
|
Reading JSON resume: in1.json
|
||||||
Reading JSON resume: in2.json
|
Reading JSON resume: in2.json
|
||||||
Reading JSON resume: in3.json
|
Reading JSON resume: in3.json
|
||||||
@ -212,14 +232,7 @@ You can specify **multiple output targets** and HackMyResume will build them:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Generate out1.doc, out1.pdf, and foo.txt from me.json.
|
# Generate out1.doc, out1.pdf, and foo.txt from me.json.
|
||||||
hackmyresume build me.json -o out1.doc -o out1.pdf -o foo.txt
|
hackmyresume BUILD me.json TO out1.doc out1.pdf foo.txt
|
||||||
```
|
|
||||||
|
|
||||||
You can also omit the output file(s) and/or theme completely:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Equivalent to "hackmyresume resume.json resume.all -t modern"
|
|
||||||
hackmyresume build resume.json
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Using .all
|
### Using .all
|
||||||
@ -229,7 +242,7 @@ formats for the given resume. For example, this...
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Generate all resume formats (HTML, PDF, DOC, TXT, etc.)
|
# Generate all resume formats (HTML, PDF, DOC, TXT, etc.)
|
||||||
hackmyresume build me.json -o out/resume.all
|
hackmyresume BUILD me.json TO out/resume.all
|
||||||
```
|
```
|
||||||
|
|
||||||
..tells HackMyResume to read `me.json` and generate `out/resume.md`,
|
..tells HackMyResume to read `me.json` and generate `out/resume.md`,
|
||||||
@ -244,7 +257,7 @@ resumes, use the `validate` command:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Validate myresume.json against either the FRESH or JSON Resume schema.
|
# Validate myresume.json against either the FRESH or JSON Resume schema.
|
||||||
hackmyresume validate resumeA.json resumeB.json
|
hackmyresume VALIDATE resumeA.json resumeB.json
|
||||||
```
|
```
|
||||||
|
|
||||||
HackMyResume will validate each specified resume in turn:
|
HackMyResume will validate each specified resume in turn:
|
||||||
@ -276,7 +289,7 @@ HTML-formatted resumes. To disable prettification, the `--nopretty` or `-n` flag
|
|||||||
can be used:
|
can be used:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
hackmyresume generate resume.json out.all --nopretty
|
hackmyresume BUILD resume.json out.all --nopretty
|
||||||
```
|
```
|
||||||
|
|
||||||
### Silent Mode
|
### Silent Mode
|
||||||
@ -284,10 +297,16 @@ hackmyresume generate resume.json out.all --nopretty
|
|||||||
Use `-s` or `--silent` to run in silent mode:
|
Use `-s` or `--silent` to run in silent mode:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
hackmyresume generate resume.json -o someFile.all -s
|
hackmyresume BUILD resume.json -o someFile.all -s
|
||||||
hackmyresume generate resume.json -o someFile.all --silent
|
hackmyresume BUILD resume.json -o someFile.all --silent
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
HackMyResume is a community-driven free and open source project under the MIT
|
||||||
|
License. Contributions are encouraged and we respond to all PRs and issues,
|
||||||
|
usually within 24 hours. See [CONTRIBUTING.md][contribute] for details.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
MIT. Go crazy. See [LICENSE.md][1] for details.
|
MIT. Go crazy. See [LICENSE.md][1] for details.
|
||||||
@ -307,3 +326,6 @@ MIT. Go crazy. See [LICENSE.md][1] for details.
|
|||||||
[dry]: https://en.wikipedia.org/wiki/Don%27t_repeat_yourself
|
[dry]: https://en.wikipedia.org/wiki/Don%27t_repeat_yourself
|
||||||
[travis-image]: https://img.shields.io/travis/palomajs/paloma.svg?style=flat-square
|
[travis-image]: https://img.shields.io/travis/palomajs/paloma.svg?style=flat-square
|
||||||
[travis-url]: https://travis-ci.org/hacksalot/HackMyResume
|
[travis-url]: https://travis-ci.org/hacksalot/HackMyResume
|
||||||
|
[contribute]: CONTRIBUTING.md
|
||||||
|
[fresh-themes]: https://github.com/fluentdesk/fresh-themes
|
||||||
|
[jrst]: https://www.npmjs.com/search?q=jsonresume-theme
|
||||||
|
18
package.json
18
package.json
@ -1,13 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "hackmyresume",
|
"name": "hackmyresume",
|
||||||
"version": "1.2.2",
|
"version": "1.3.0-beta",
|
||||||
"description": "Generate polished résumés and CVs in HTML, Markdown, LaTeX, MS Word, PDF, plain text, JSON, XML, YAML, smoke signal, and carrier pigeon.",
|
"description": "Generate polished résumés and CVs in HTML, Markdown, LaTeX, MS Word, PDF, plain text, JSON, XML, YAML, smoke signal, and carrier pigeon.",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/hacksalot/HackMyResume.git"
|
"url": "https://github.com/hacksalot/HackMyResume.git"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "mocha && grunt",
|
"test": "grunt clean && mocha",
|
||||||
"grunt": "grunt"
|
"grunt": "grunt"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
@ -29,6 +29,11 @@
|
|||||||
"template"
|
"template"
|
||||||
],
|
],
|
||||||
"author": "hacksalot <hacksalot@indevious.com> (https://github.com/hacksalot)",
|
"author": "hacksalot <hacksalot@indevious.com> (https://github.com/hacksalot)",
|
||||||
|
"contributors": [
|
||||||
|
"Edmund Jorgensen (https://github.com/tomheon)",
|
||||||
|
"Ya Zhuang (https://github.com/zhuangya)",
|
||||||
|
"hacksalot <hacksalot@indevious.com> (https://github.com/hacksalot)"
|
||||||
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"preferGlobal": "true",
|
"preferGlobal": "true",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
@ -41,7 +46,8 @@
|
|||||||
"homepage": "https://github.com/hacksalot/HackMyResume",
|
"homepage": "https://github.com/hacksalot/HackMyResume",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"colors": "^1.1.2",
|
"colors": "^1.1.2",
|
||||||
"fluent-themes": "~0.7.1-beta",
|
"copy": "^0.1.3",
|
||||||
|
"fresh-themes": "~0.9.3-beta",
|
||||||
"fresca": "~0.2.2",
|
"fresca": "~0.2.2",
|
||||||
"fs-extra": "^0.24.0",
|
"fs-extra": "^0.24.0",
|
||||||
"handlebars": "^4.0.5",
|
"handlebars": "^4.0.5",
|
||||||
@ -57,6 +63,7 @@
|
|||||||
"path-exists": "^2.1.0",
|
"path-exists": "^2.1.0",
|
||||||
"recursive-readdir-sync": "^1.0.6",
|
"recursive-readdir-sync": "^1.0.6",
|
||||||
"simple-html-tokenizer": "^0.2.0",
|
"simple-html-tokenizer": "^0.2.0",
|
||||||
|
"string.prototype.startswith": "^0.2.0",
|
||||||
"underscore": "^1.8.3",
|
"underscore": "^1.8.3",
|
||||||
"webshot": "^0.16.0",
|
"webshot": "^0.16.0",
|
||||||
"wkhtmltopdf": "^0.1.5",
|
"wkhtmltopdf": "^0.1.5",
|
||||||
@ -72,6 +79,11 @@
|
|||||||
"grunt-contrib-yuidoc": "^0.10.0",
|
"grunt-contrib-yuidoc": "^0.10.0",
|
||||||
"grunt-simple-mocha": "*",
|
"grunt-simple-mocha": "*",
|
||||||
"jane-q-fullstacker": "fluentdesk/jane-q-fullstacker",
|
"jane-q-fullstacker": "fluentdesk/jane-q-fullstacker",
|
||||||
|
"johnny-trouble-resume": "fluentdesk/johnny-trouble-resume",
|
||||||
|
"jsonresume-theme-boilerplate": "^0.1.2",
|
||||||
|
"jsonresume-theme-classy": "^1.0.9",
|
||||||
|
"jsonresume-theme-modern": "0.0.18",
|
||||||
|
"jsonresume-theme-sceptile": "^1.0.5",
|
||||||
"mocha": "*",
|
"mocha": "*",
|
||||||
"resample": "fluentdesk/resample"
|
"resample": "fluentdesk/resample"
|
||||||
}
|
}
|
||||||
|
100
src/core/error-handler.js
Normal file
100
src/core/error-handler.js
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
/**
|
||||||
|
@module error-handler.js
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
|
||||||
|
var HACKMYSTATUS = require('./status-codes')
|
||||||
|
, PKG = require('../../package.json')
|
||||||
|
, FS = require('fs')
|
||||||
|
, FCMD = require('../hackmycmd')
|
||||||
|
, PATH = require('path')
|
||||||
|
, title = ('\n*** HackMyResume v' + PKG.version + ' ***').bold.white;
|
||||||
|
|
||||||
|
var ErrorHandler = module.exports = {
|
||||||
|
|
||||||
|
|
||||||
|
err: function( ex, shouldExit ) {
|
||||||
|
var msg = '', exitCode;
|
||||||
|
|
||||||
|
if( ex.fluenterror ){
|
||||||
|
switch( ex.fluenterror ) { // TODO: Remove magic numbers
|
||||||
|
|
||||||
|
case HACKMYSTATUS.themeNotFound:
|
||||||
|
msg = "The specified theme couldn't be found: " + ex.data;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HACKMYSTATUS.copyCSS:
|
||||||
|
msg = "Couldn't copy CSS file to destination folder";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HACKMYSTATUS.resumeNotFound:
|
||||||
|
msg = 'Please '.guide + 'feed me a resume'.guide.bold +
|
||||||
|
' in FRESH or JSON Resume format.'.guide;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HACKMYSTATUS.missingCommand:
|
||||||
|
msg = title + "\nPlease ".guide + "give me a command".guide.bold +
|
||||||
|
" (".guide;
|
||||||
|
|
||||||
|
msg += Object.keys( FCMD.verbs ).map( function(v, idx, ar) {
|
||||||
|
return (idx === ar.length - 1 ? 'or '.guide : '') +
|
||||||
|
v.toUpperCase().guide;
|
||||||
|
}).join(', '.guide) + ").\n\n".guide;
|
||||||
|
|
||||||
|
msg += FS.readFileSync(
|
||||||
|
PATH.resolve(__dirname, '../use.txt'), 'utf8' ).info.bold;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HACKMYSTATUS.invalidCommand:
|
||||||
|
msg = 'Please '.guide + 'specify the output resume file'.guide.bold +
|
||||||
|
' that should be created.'.guide;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HACKMYSTATUS.resumeNotFoundAlt:
|
||||||
|
msg = 'Please '.guide + 'feed me a resume'.guide.bold +
|
||||||
|
' in either FRESH or JSON Resume format.'.guide;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HACKMYSTATUS.inputOutputParity:
|
||||||
|
msg = 'Please '.guide + 'specify an output file name'.guide.bold +
|
||||||
|
' for every input file you wish to convert.'.guide;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HACKMYSTATUS.createNameMissing:
|
||||||
|
msg = 'Please '.guide + 'specify the filename of the resume'.guide.bold +
|
||||||
|
' to create.'.guide;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HACKMYSTATUS.wkhtmltopdf:
|
||||||
|
msg = 'ERROR: PDF generation failed. '.red.bold + ('Make sure wkhtmltopdf is ' +
|
||||||
|
'installed and accessible from your path.').red;
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
exitCode = ex.fluenterror;
|
||||||
|
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
msg = ex.toString();
|
||||||
|
exitCode = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
var idx = msg.indexOf('Error: ');
|
||||||
|
var trimmed = idx === -1 ? msg : msg.substring( idx + 7 );
|
||||||
|
if( !ex.fluenterror || ex.fluenterror < 3 ) { // TODO: magic #s
|
||||||
|
console.log( ('ERROR: ' + trimmed.toString()).red.bold );
|
||||||
|
console.log( ex.stack.gray);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log( trimmed.toString() );
|
||||||
|
}
|
||||||
|
|
||||||
|
if( shouldExit )
|
||||||
|
process.exit( exitCode );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}());
|
@ -143,15 +143,12 @@ Definition of the FRESHResume class.
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Open and parse the specified JSON resume sheet. Merge the JSON object model
|
Initialize the FreshResume from JSON data.
|
||||||
onto this Sheet instance with extend() and convert sheet dates to a safe &
|
Open and parse the specified FRESH resume. Merge the JSON object model onto
|
||||||
|
this Sheet instance with extend() and convert sheet dates to a safe &
|
||||||
consistent format. Then sort each section by startDate descending.
|
consistent format. Then sort each section by startDate descending.
|
||||||
*/
|
*/
|
||||||
FreshResume.prototype.parse = function( stringData, opts ) {
|
FreshResume.prototype.parseJSON = function( rep, opts ) {
|
||||||
|
|
||||||
// Parse the incoming JSON representation
|
|
||||||
var rep = JSON.parse( stringData );
|
|
||||||
|
|
||||||
// Convert JSON Resume to FRESH if necessary
|
// Convert JSON Resume to FRESH if necessary
|
||||||
if( rep.basics ) {
|
if( rep.basics ) {
|
||||||
rep = CONVERTER.toFRESH( rep );
|
rep = CONVERTER.toFRESH( rep );
|
||||||
@ -178,6 +175,13 @@ Definition of the FRESHResume class.
|
|||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
Initialize the the FreshResume from string data.
|
||||||
|
*/
|
||||||
|
FreshResume.prototype.parse = function( stringData, opts ) {
|
||||||
|
return this.parseJSON( JSON.parse( stringData ), opts );
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Return a unique list of all keywords across all skills.
|
Return a unique list of all keywords across all skills.
|
||||||
*/
|
*/
|
||||||
|
@ -1,37 +1,51 @@
|
|||||||
/**
|
/**
|
||||||
Definition of the Theme class.
|
Definition of the FRESHTheme class.
|
||||||
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
|
@module fresh-theme.js
|
||||||
@module theme.js
|
@license MIT. See LICENSE.md for details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var FS = require('fs')
|
var FS = require('fs')
|
||||||
, extend = require('../utils/extend')
|
, extend = require('../utils/extend')
|
||||||
, validator = require('is-my-json-valid')
|
, validator = require('is-my-json-valid')
|
||||||
, _ = require('underscore')
|
, _ = require('underscore')
|
||||||
, PATH = require('path')
|
, PATH = require('path')
|
||||||
, parsePath = require('parse-filepath')
|
, parsePath = require('parse-filepath')
|
||||||
|
, pathExists = require('path-exists').sync
|
||||||
, EXTEND = require('../utils/extend')
|
, EXTEND = require('../utils/extend')
|
||||||
, moment = require('moment')
|
, moment = require('moment')
|
||||||
, RECURSIVE_READ_DIR = require('recursive-readdir-sync');
|
, READFILES = require('recursive-readdir-sync');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
The Theme class is a representation of a HackMyResume theme asset.
|
The FRESHTheme class is a representation of a FRESH theme
|
||||||
@class Theme
|
asset. See also: JRSTheme.
|
||||||
|
@class FRESHTheme
|
||||||
*/
|
*/
|
||||||
function Theme() {
|
function FRESHTheme() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Open and parse the specified theme.
|
Open and parse the specified theme.
|
||||||
*/
|
*/
|
||||||
Theme.prototype.open = function( themeFolder ) {
|
FRESHTheme.prototype.open = function( themeFolder ) {
|
||||||
|
|
||||||
|
this.folder = themeFolder;
|
||||||
|
|
||||||
// Open the [theme-name].json file; should have the same name as folder
|
// Open the [theme-name].json file; should have the same name as folder
|
||||||
this.folder = themeFolder;
|
|
||||||
var pathInfo = parsePath( themeFolder );
|
var pathInfo = parsePath( themeFolder );
|
||||||
|
|
||||||
|
// Set up a formats hash for the theme
|
||||||
|
var formatsHash = { };
|
||||||
|
|
||||||
|
// Load the theme
|
||||||
var themeFile = PATH.join( themeFolder, pathInfo.basename + '.json' );
|
var themeFile = PATH.join( themeFolder, pathInfo.basename + '.json' );
|
||||||
var themeInfo = JSON.parse( FS.readFileSync( themeFile, 'utf8' ) );
|
var themeInfo = JSON.parse( FS.readFileSync( themeFile, 'utf8' ) );
|
||||||
var that = this;
|
var that = this;
|
||||||
@ -39,9 +53,6 @@ Definition of the Theme class.
|
|||||||
// Move properties from the theme JSON file to the theme object
|
// Move properties from the theme JSON file to the theme object
|
||||||
EXTEND( true, this, themeInfo );
|
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,
|
// Check for an explicit "formats" entry in the theme JSON. If it has one,
|
||||||
// then this theme declares its files explicitly.
|
// then this theme declares its files explicitly.
|
||||||
if( !!this.formats ) {
|
if( !!this.formats ) {
|
||||||
@ -52,10 +63,6 @@ Definition of the Theme class.
|
|||||||
formatsHash = loadImplicit.call( this );
|
formatsHash = loadImplicit.call( this );
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add freebie formats every theme gets
|
|
||||||
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 };
|
|
||||||
|
|
||||||
// Cache
|
// Cache
|
||||||
this.formats = formatsHash;
|
this.formats = formatsHash;
|
||||||
|
|
||||||
@ -65,20 +72,29 @@ Definition of the Theme class.
|
|||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Determine if the theme supports the specified output format.
|
Determine if the theme supports the specified output format.
|
||||||
*/
|
*/
|
||||||
Theme.prototype.hasFormat = function( fmt ) {
|
FRESHTheme.prototype.hasFormat = function( fmt ) {
|
||||||
return _.has( this.formats, fmt );
|
return _.has( this.formats, fmt );
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Determine if the theme supports the specified output format.
|
Determine if the theme supports the specified output format.
|
||||||
*/
|
*/
|
||||||
Theme.prototype.getFormat = function( fmt ) {
|
FRESHTheme.prototype.getFormat = function( fmt ) {
|
||||||
return this.formats[ fmt ];
|
return this.formats[ fmt ];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
Load the theme implicitly, by scanning the theme folder for
|
||||||
|
files. TODO: Refactor duplicated code with loadExplicit.
|
||||||
|
*/
|
||||||
function loadImplicit() {
|
function loadImplicit() {
|
||||||
|
|
||||||
// Set up a hash of formats supported by this theme.
|
// Set up a hash of formats supported by this theme.
|
||||||
@ -92,7 +108,7 @@ Definition of the Theme class.
|
|||||||
// Iterate over all files in the theme folder, producing an array, fmts,
|
// 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
|
// containing info for each file. While we're doing that, also build up
|
||||||
// the formatsHash object.
|
// the formatsHash object.
|
||||||
var fmts = RECURSIVE_READ_DIR( tplFolder ).map( function( absPath ) {
|
var fmts = READFILES(tplFolder).map( function(absPath) {
|
||||||
|
|
||||||
// If this file lives in a specific format folder within the theme,
|
// If this file lives in a specific format folder within the theme,
|
||||||
// such as "/latex" or "/html", then that format is the output format
|
// such as "/latex" or "/html", then that format is the output format
|
||||||
@ -120,7 +136,7 @@ Definition of the Theme class.
|
|||||||
// compact-[outputformat].[extension], for ex, compact-pdf.html.
|
// compact-[outputformat].[extension], for ex, compact-pdf.html.
|
||||||
if( !outFmt ) {
|
if( !outFmt ) {
|
||||||
var idx = pathInfo.name.lastIndexOf('-');
|
var idx = pathInfo.name.lastIndexOf('-');
|
||||||
outFmt = ( idx === -1 ) ? pathInfo.name : pathInfo.name.substr( idx + 1 );
|
outFmt = (idx === -1) ? pathInfo.name : pathInfo.name.substr(idx + 1);
|
||||||
isMajor = true;
|
isMajor = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,9 +166,13 @@ Definition of the Theme class.
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Now, get all the CSS files...
|
// Now, get all the CSS files...
|
||||||
(this.cssFiles = fmts.filter(function( fmt ){ return fmt && (fmt.ext === 'css'); }))
|
(this.cssFiles = fmts.filter(function( fmt ){
|
||||||
.forEach(function( cssf ) {
|
return fmt && (fmt.ext === 'css');
|
||||||
|
}))
|
||||||
|
|
||||||
// For each CSS file, get its corresponding HTML file
|
// For each CSS file, get its corresponding HTML file
|
||||||
|
.forEach(function( cssf ) {
|
||||||
|
|
||||||
var idx = _.findIndex(fmts, function( fmt ) {
|
var idx = _.findIndex(fmts, function( fmt ) {
|
||||||
return fmt && fmt.pre === cssf.pre && fmt.ext === 'html';
|
return fmt && fmt.pre === cssf.pre && fmt.ext === 'html';
|
||||||
});
|
});
|
||||||
@ -169,30 +189,36 @@ Definition of the Theme class.
|
|||||||
return formatsHash;
|
return formatsHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
Load the theme explicitly, by following the 'formats' hash
|
||||||
|
in the theme's JSON settings file.
|
||||||
|
*/
|
||||||
function loadExplicit() {
|
function loadExplicit() {
|
||||||
|
|
||||||
var that = this;
|
// Housekeeping
|
||||||
// Set up a hash of formats supported by this theme.
|
|
||||||
var formatsHash = { };
|
var formatsHash = { };
|
||||||
|
|
||||||
// Establish the base theme folder
|
|
||||||
var tplFolder = PATH.join( this.folder, 'src' );
|
var tplFolder = PATH.join( this.folder, 'src' );
|
||||||
|
|
||||||
var act = null;
|
var act = null;
|
||||||
|
var that = this;
|
||||||
|
|
||||||
// Iterate over all files in the theme folder, producing an array, fmts,
|
// 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
|
// containing info for each file. While we're doing that, also build up
|
||||||
// the formatsHash object.
|
// the formatsHash object.
|
||||||
var fmts = RECURSIVE_READ_DIR( tplFolder ).map( function( absPath ) {
|
var fmts = READFILES( tplFolder ).map( function( absPath ) {
|
||||||
|
|
||||||
act = null;
|
act = null;
|
||||||
// If this file is mentioned in the theme's JSON file under "transforms"
|
// If this file is mentioned in the theme's JSON file under "transforms"
|
||||||
var pathInfo = parsePath(absPath);
|
var pathInfo = parsePath(absPath);
|
||||||
var absPathSafe = absPath.trim().toLowerCase();
|
var absPathSafe = absPath.trim().toLowerCase();
|
||||||
var outFmt = _.find( Object.keys( that.formats ), function( fmtKey ) {
|
var outFmt = _.find(
|
||||||
|
Object.keys( that.formats ),
|
||||||
|
function( fmtKey ) {
|
||||||
var fmtVal = that.formats[ fmtKey ];
|
var fmtVal = that.formats[ fmtKey ];
|
||||||
return _.some( fmtVal.transform, function( fpath ) {
|
return _.some( fmtVal.transform, function(fpath) {
|
||||||
var absPathB = PATH.join( that.folder, fpath ).trim().toLowerCase();
|
var absPathB = PATH.join( that.folder, fpath )
|
||||||
|
.trim().toLowerCase();
|
||||||
return absPathB === absPathSafe;
|
return absPathB === absPathSafe;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -216,7 +242,7 @@ Definition of the Theme class.
|
|||||||
// compact-[outputformat].[extension], for ex, compact-pdf.html.
|
// compact-[outputformat].[extension], for ex, compact-pdf.html.
|
||||||
if( !outFmt ) {
|
if( !outFmt ) {
|
||||||
var idx = pathInfo.name.lastIndexOf('-');
|
var idx = pathInfo.name.lastIndexOf('-');
|
||||||
outFmt = ( idx === -1 ) ? pathInfo.name : pathInfo.name.substr( idx + 1 );
|
outFmt = (idx === -1) ? pathInfo.name : pathInfo.name.substr(idx + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// We should have a valid output format now.
|
// We should have a valid output format now.
|
||||||
@ -246,7 +272,11 @@ Definition of the Theme class.
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Now, get all the CSS files...
|
// Now, get all the CSS files...
|
||||||
(this.cssFiles = fmts.filter(function( fmt ){ return fmt.ext === 'css'; }))
|
(this.cssFiles = fmts.filter(function( fmt ){
|
||||||
|
return fmt.ext === 'css';
|
||||||
|
}))
|
||||||
|
|
||||||
|
// For each CSS file, get its corresponding HTML file
|
||||||
.forEach(function( cssf ) {
|
.forEach(function( cssf ) {
|
||||||
// For each CSS file, get its corresponding HTML file
|
// For each CSS file, get its corresponding HTML file
|
||||||
var idx = _.findIndex(fmts, function( fmt ) {
|
var idx = _.findIndex(fmts, function( fmt ) {
|
||||||
@ -264,12 +294,22 @@ Definition of the Theme class.
|
|||||||
return formatsHash;
|
return formatsHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
Return a more friendly name for certain formats.
|
||||||
|
TODO: Refactor
|
||||||
|
*/
|
||||||
function friendlyName( val ) {
|
function friendlyName( val ) {
|
||||||
val = val.trim().toLowerCase();
|
val = val.trim().toLowerCase();
|
||||||
var friendly = { yml: 'yaml', md: 'markdown', txt: 'text' };
|
var friendly = { yml: 'yaml', md: 'markdown', txt: 'text' };
|
||||||
return friendly[val] || val;
|
return friendly[val] || val;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Theme;
|
|
||||||
|
|
||||||
|
module.exports = FRESHTheme;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}());
|
}());
|
@ -11,6 +11,7 @@ Definition of the JRSResume class.
|
|||||||
, validator = require('is-my-json-valid')
|
, validator = require('is-my-json-valid')
|
||||||
, _ = require('underscore')
|
, _ = require('underscore')
|
||||||
, PATH = require('path')
|
, PATH = require('path')
|
||||||
|
, MD = require('marked')
|
||||||
, moment = require('moment');
|
, moment = require('moment');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -70,6 +71,7 @@ Definition of the JRSResume class.
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Initialize the JRS Resume from string data.
|
||||||
Open and parse the specified JSON resume sheet. Merge the JSON object model
|
Open and parse the specified JSON resume sheet. Merge the JSON object model
|
||||||
onto this Sheet instance with extend() and convert sheet dates to a safe &
|
onto this Sheet instance with extend() and convert sheet dates to a safe &
|
||||||
consistent format. Then sort each section by startDate descending.
|
consistent format. Then sort each section by startDate descending.
|
||||||
@ -77,7 +79,14 @@ Definition of the JRSResume class.
|
|||||||
JRSResume.prototype.parse = function( stringData, opts ) {
|
JRSResume.prototype.parse = function( stringData, opts ) {
|
||||||
opts = opts || { };
|
opts = opts || { };
|
||||||
var rep = JSON.parse( stringData );
|
var rep = JSON.parse( stringData );
|
||||||
|
return this.parseJSON( rep, opts );
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
Initialize the JRSRume from JSON data.
|
||||||
|
*/
|
||||||
|
JRSResume.prototype.parseJSON = function( rep, opts ) {
|
||||||
|
opts = opts || { };
|
||||||
extend( true, this, rep );
|
extend( true, this, rep );
|
||||||
// Set up metadata
|
// Set up metadata
|
||||||
if( opts.imp === undefined || opts.imp ) {
|
if( opts.imp === undefined || opts.imp ) {
|
||||||
@ -230,6 +239,70 @@ Definition of the JRSResume class.
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
JRSResume.prototype.dupe = function() {
|
||||||
|
var rnew = new JRSResume();
|
||||||
|
rnew.parse( this.stringify(), { } );
|
||||||
|
return rnew;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
Create a copy of this resume in which all fields have been interpreted as
|
||||||
|
Markdown.
|
||||||
|
*/
|
||||||
|
JRSResume.prototype.harden = function() {
|
||||||
|
|
||||||
|
var that = this;
|
||||||
|
var ret = this.dupe();
|
||||||
|
|
||||||
|
function HD(txt) {
|
||||||
|
return '@@@@~' + txt + '~@@@@';
|
||||||
|
}
|
||||||
|
|
||||||
|
function HDIN(txt){
|
||||||
|
//return MD(txt || '' ).replace(/^\s*<p>|<\/p>\s*$/gi, '');
|
||||||
|
return HD(txt);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: refactor recursion
|
||||||
|
function hardenStringsInObject( 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 ? HDIN(elem) : HD( elem );
|
||||||
|
else
|
||||||
|
hardenStringsInObject( 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','website','startDate','endDate','releaseDate','date','phone','email','address','postalCode','city','country','region'], key) )
|
||||||
|
return;
|
||||||
|
if( key === 'summary' )
|
||||||
|
obj[key] = HD( obj[key] );
|
||||||
|
else
|
||||||
|
obj[key] = inline ? HDIN( obj[key] ) : HD( obj[key] );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
hardenStringsInObject( sub );
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.keys( ret ).forEach(function(member){
|
||||||
|
hardenStringsInObject( ret[ member ] );
|
||||||
|
});
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Convert human-friendly dates into formal Moment.js dates for all collections.
|
Convert human-friendly dates into formal Moment.js dates for all collections.
|
||||||
We don't want to lose the raw textual date as entered by the user, so we store
|
We don't want to lose the raw textual date as entered by the user, so we store
|
||||||
|
85
src/core/jrs-theme.js
Normal file
85
src/core/jrs-theme.js
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
/**
|
||||||
|
Definition of the JRSTheme class.
|
||||||
|
@module jrs-theme.js
|
||||||
|
@license MIT. See LICENSE.MD for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
var _ = require('underscore')
|
||||||
|
, PATH = require('path')
|
||||||
|
, parsePath = require('parse-filepath')
|
||||||
|
, pathExists = require('path-exists').sync;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
The JRSTheme class is a representation of a JSON Resume
|
||||||
|
theme asset. See also: FRESHTheme.
|
||||||
|
@class JRSTheme
|
||||||
|
*/
|
||||||
|
function JRSTheme() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
Open and parse the specified theme.
|
||||||
|
@method open
|
||||||
|
*/
|
||||||
|
JRSTheme.prototype.open = function( thFolder ) {
|
||||||
|
|
||||||
|
this.folder = thFolder;
|
||||||
|
|
||||||
|
// Open the [theme-name].json file; should have the same
|
||||||
|
// name as folder
|
||||||
|
var pathInfo = parsePath( thFolder );
|
||||||
|
|
||||||
|
// Open and parse the theme's package.json file.
|
||||||
|
var pkgJsonPath = PATH.join( thFolder, 'package.json' );
|
||||||
|
if( pathExists( pkgJsonPath )) {
|
||||||
|
var thApi = require( thFolder )
|
||||||
|
, thPkg = require( pkgJsonPath );
|
||||||
|
this.name = thPkg.name;
|
||||||
|
this.render = (thApi && thApi.render) || undefined;
|
||||||
|
this.formats = {
|
||||||
|
html: { title:'html', outFormat:'html', ext:'html' }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw { fluenterror: 10 };
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
Determine if the theme supports the output format.
|
||||||
|
@method hasFormat
|
||||||
|
*/
|
||||||
|
JRSTheme.prototype.hasFormat = function( fmt ) {
|
||||||
|
return _.has( this.formats, fmt );
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
Return the requested output format.
|
||||||
|
@method getFormat
|
||||||
|
*/
|
||||||
|
JRSTheme.prototype.getFormat = function( fmt ) {
|
||||||
|
return this.formats[ fmt ];
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = JRSTheme;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}());
|
@ -1,13 +0,0 @@
|
|||||||
(function(){
|
|
||||||
|
|
||||||
var FRESHResume = require('../core/fresh-resume');
|
|
||||||
|
|
||||||
module.exports = function loadSourceResumes( src, log, fn ) {
|
|
||||||
return src.map( function( res ) {
|
|
||||||
log( 'Reading '.info + 'SOURCE'.infoBold + ' resume: '.info +
|
|
||||||
res.cyan.bold );
|
|
||||||
return (fn && fn(res)) || (new FRESHResume()).open( res );
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
}());
|
|
43
src/core/resume-factory.js
Normal file
43
src/core/resume-factory.js
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
/**
|
||||||
|
Core resume-loading logic for HackMyResume.
|
||||||
|
@module resume-factory.js
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function(){
|
||||||
|
|
||||||
|
require('string.prototype.startswith');
|
||||||
|
var FS = require('fs');
|
||||||
|
var ResumeConverter = require('./convert');
|
||||||
|
|
||||||
|
/**
|
||||||
|
A simple factory class for FRESH and JSON Resumes.
|
||||||
|
@class ResumeFactory
|
||||||
|
*/
|
||||||
|
module.exports = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
Load one or more resumes in a specific source format.
|
||||||
|
*/
|
||||||
|
load: function ( src, log, fn, toFormat ) {
|
||||||
|
|
||||||
|
toFormat = toFormat && (toFormat.toLowerCase().trim()) || 'fresh';
|
||||||
|
var ResumeClass = require('../core/' + toFormat + '-resume');
|
||||||
|
|
||||||
|
return src.map( function( res ) {
|
||||||
|
var rezJson = JSON.parse( FS.readFileSync( res ) );
|
||||||
|
var orgFormat = ( rezJson.meta && rezJson.meta.format &&
|
||||||
|
rezJson.meta.format.startsWith('FRESH@') ) ?
|
||||||
|
'fresh' : 'jrs';
|
||||||
|
if(orgFormat !== toFormat) {
|
||||||
|
rezJson = ResumeConverter[ 'to' + toFormat.toUpperCase() ]( rezJson );
|
||||||
|
}
|
||||||
|
// TODO: Core should not log
|
||||||
|
log( 'Reading '.info + orgFormat.toUpperCase().infoBold + ' resume: '.info + res.cyan.bold );
|
||||||
|
return (fn && fn(res)) || (new ResumeClass()).parseJSON( rezJson );
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}());
|
22
src/core/spawn-watch.js
Normal file
22
src/core/spawn-watch.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
@module spawn-watch.js
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
|
||||||
|
// Catch various out-of-band child process errors such as ENOENT for PDFs
|
||||||
|
// http://stackoverflow.com/q/27688804
|
||||||
|
var SpawnWatcher = module.exports = function() {
|
||||||
|
var childProcess = require("child_process");
|
||||||
|
var oldSpawn = childProcess.spawn;
|
||||||
|
childProcess.spawn = function() {
|
||||||
|
return oldSpawn.apply(this, arguments)
|
||||||
|
.on('error', function(err) {
|
||||||
|
require('./error-handler').err( err, false );
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}();
|
||||||
|
|
||||||
|
//SpawnWatcher();
|
||||||
|
|
||||||
|
}());
|
23
src/core/status-codes.js
Normal file
23
src/core/status-codes.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
/**
|
||||||
|
Status codes for HackMyResume.
|
||||||
|
@module status-codes.js
|
||||||
|
@license MIT. See LICENSE.MD for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function(){
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
success: 0,
|
||||||
|
themeNotFound: 1,
|
||||||
|
copyCss: 2,
|
||||||
|
resumeNotFound: 3,
|
||||||
|
missingCommand: 4,
|
||||||
|
invalidCommand: 5,
|
||||||
|
resumeNotFoundAlt: 6,
|
||||||
|
inputOutputParity: 7,
|
||||||
|
createNameMissing: 8,
|
||||||
|
wkhtmltopdf: 9,
|
||||||
|
missingPackageJSON: 10
|
||||||
|
};
|
||||||
|
|
||||||
|
}());
|
@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
Generic template helper definitions for FluentCV.
|
Generic template helper definitions for HackMyResume / FluentCV.
|
||||||
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
|
@license MIT. See LICENSE.md for details.
|
||||||
@module generic-helpers.js
|
@module generic-helpers.js
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -114,6 +114,16 @@ Generic template helper definitions for FluentCV.
|
|||||||
if (lhs || rhs) return options.fn(this);
|
if (lhs || rhs) return options.fn(this);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
Conditional stylesheet link. Either display the link or embed the stylesheet
|
||||||
|
via <style></style> tag.
|
||||||
|
*/
|
||||||
|
styleSheet: function( file, options ) {
|
||||||
|
return ( this.opts.css === 'link') ?
|
||||||
|
'<link href="' + file + '" rel="stylesheet" type="text/css">' :
|
||||||
|
'<style>' + this.cssInfo.data + '</style>';
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Perform a generic comparison.
|
Perform a generic comparison.
|
||||||
See: http://doginthehat.com.au/2012/02/comparison-block-helper-for-handlebars-templates
|
See: http://doginthehat.com.au/2012/02/comparison-block-helper-for-handlebars-templates
|
||||||
|
@ -40,6 +40,7 @@ Definition of the HandlebarsGenerator class.
|
|||||||
RAW: json,
|
RAW: json,
|
||||||
filt: opts.filters,
|
filt: opts.filters,
|
||||||
cssInfo: cssInfo,
|
cssInfo: cssInfo,
|
||||||
|
opts: opts,
|
||||||
headFragment: opts.headFragment || ''
|
headFragment: opts.headFragment || ''
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -11,7 +11,6 @@ Definition of the UnderscoreGenerator class.
|
|||||||
var _ = require('underscore');
|
var _ = require('underscore');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Perform template-based resume generation using Underscore.js.
|
Perform template-based resume generation using Underscore.js.
|
||||||
@class UnderscoreGenerator
|
@class UnderscoreGenerator
|
||||||
@ -32,6 +31,10 @@ Definition of the UnderscoreGenerator class.
|
|||||||
// Strip {# comments #}
|
// Strip {# comments #}
|
||||||
jst = jst.replace( delims.comment, '');
|
jst = jst.replace( delims.comment, '');
|
||||||
|
|
||||||
|
var helpers = require('./generic-helpers');
|
||||||
|
helpers.opts = opts;
|
||||||
|
helpers.cssInfo = cssInfo;
|
||||||
|
|
||||||
// Compile and run the template. TODO: avoid unnecessary recompiles.
|
// Compile and run the template. TODO: avoid unnecessary recompiles.
|
||||||
var compiled = _.template(jst);
|
var compiled = _.template(jst);
|
||||||
var ret = compiled({
|
var ret = compiled({
|
||||||
@ -40,7 +43,9 @@ Definition of the UnderscoreGenerator class.
|
|||||||
XML: require('xml-escape'),
|
XML: require('xml-escape'),
|
||||||
RAW: json,
|
RAW: json,
|
||||||
cssInfo: cssInfo,
|
cssInfo: cssInfo,
|
||||||
headFragment: opts.headFragment || ''
|
headFragment: opts.headFragment || '',
|
||||||
|
opts: opts,
|
||||||
|
h: helpers
|
||||||
});
|
});
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
Definition of the BaseGenerator class.
|
Definition of the BaseGenerator class.
|
||||||
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
|
|
||||||
@module base-generator.js
|
@module base-generator.js
|
||||||
|
@license MIT. See LICENSE.md for details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
@ -26,14 +26,7 @@ Definition of the BaseGenerator class.
|
|||||||
/**
|
/**
|
||||||
Status codes.
|
Status codes.
|
||||||
*/
|
*/
|
||||||
codes: {
|
codes: require('../core/status-codes'),
|
||||||
success: 0,
|
|
||||||
themeNotFound: 1,
|
|
||||||
copyCss: 2,
|
|
||||||
resumeNotFound: 3,
|
|
||||||
missingCommand: 4,
|
|
||||||
invalidCommand: 5
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Generator options.
|
Generator options.
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
Definition of the HtmlPdfGenerator class.
|
Definition of the HtmlPdfGenerator class.
|
||||||
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
|
|
||||||
@module html-pdf-generator.js
|
@module html-pdf-generator.js
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -23,7 +22,7 @@ Definition of the HtmlPdfGenerator class.
|
|||||||
Generate the binary PDF.
|
Generate the binary PDF.
|
||||||
*/
|
*/
|
||||||
onBeforeSave: function( info ) {
|
onBeforeSave: function( info ) {
|
||||||
pdf( info.mk, info.outputFile );
|
pdf.call( this, info.mk, info.outputFile );
|
||||||
return null; // halt further processing
|
return null; // halt further processing
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,39 +33,50 @@ Definition of the HtmlPdfGenerator class.
|
|||||||
*/
|
*/
|
||||||
function pdf( markup, fOut ) {
|
function pdf( markup, fOut ) {
|
||||||
|
|
||||||
var pdfCount = 0;
|
pdf_wkhtmltopdf.call( this, markup, fOut );
|
||||||
if( false ) { //( _opts.pdf === 'phantom' || _opts.pdf == 'all' ) {
|
|
||||||
pdfCount++;
|
|
||||||
require('phantom').create( function( ph ) {
|
|
||||||
ph.createPage( function( page ) {
|
|
||||||
page.setContent( markup );
|
|
||||||
page.set('paperSize', {
|
|
||||||
format: 'A4',
|
|
||||||
orientation: 'portrait',
|
|
||||||
margin: '1cm'
|
|
||||||
});
|
|
||||||
page.set("viewportSize", {
|
|
||||||
width: 1024, // TODO: option-ify
|
|
||||||
height: 768 // TODO: Use "A" sizes
|
|
||||||
});
|
|
||||||
page.set('onLoadFinished', function(success) {
|
|
||||||
page.render( fOut );
|
|
||||||
pdfCount++;
|
|
||||||
ph.exit();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
{ dnodeOpts: { weak: false } } );
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
if( true ) { // _opts.pdf === 'wkhtmltopdf' || _opts.pdf == 'all' ) {
|
|
||||||
var fOut2 = fOut;
|
/**
|
||||||
if( pdfCount == 1 ) {
|
Generate a PDF from HTML using wkhtmltopdf.
|
||||||
fOut2 = fOut2.replace(/\.pdf$/g, '.b.pdf');
|
*/
|
||||||
|
function pdf_wkhtmltopdf( markup, fOut ) {
|
||||||
|
var wk;
|
||||||
|
try {
|
||||||
|
wk = require('wkhtmltopdf');
|
||||||
|
wk( markup, { pageSize: 'letter' } )
|
||||||
|
.pipe( FS.createWriteStream( fOut ) );
|
||||||
}
|
}
|
||||||
require('wkhtmltopdf')( markup, { pageSize: 'letter' } )
|
catch(ex) {
|
||||||
.pipe( FS.createWriteStream( fOut2 ) );
|
// { [Error: write EPIPE] code: 'EPIPE', errno: 'EPIPE', syscall: 'write' }
|
||||||
pdfCount++;
|
// { [Error: ENOENT] }
|
||||||
|
throw { fluenterror: this.codes.wkhtmltopdf };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// function pdf_phantom() {
|
||||||
|
// pdfCount++;
|
||||||
|
// require('phantom').create( function( ph ) {
|
||||||
|
// ph.createPage( function( page ) {
|
||||||
|
// page.setContent( markup );
|
||||||
|
// page.set('paperSize', {
|
||||||
|
// format: 'A4',
|
||||||
|
// orientation: 'portrait',
|
||||||
|
// margin: '1cm'
|
||||||
|
// });
|
||||||
|
// page.set("viewportSize", {
|
||||||
|
// width: 1024, // TODO: option-ify
|
||||||
|
// height: 768 // TODO: Use "A" sizes
|
||||||
|
// });
|
||||||
|
// page.set('onLoadFinished', function(success) {
|
||||||
|
// page.render( fOut );
|
||||||
|
// pdfCount++;
|
||||||
|
// ph.exit();
|
||||||
|
// });
|
||||||
|
// },
|
||||||
|
// { dnodeOpts: { weak: false } } );
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
}());
|
}());
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
Definition of the HtmlPngGenerator class.
|
Definition of the HtmlPngGenerator class.
|
||||||
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
|
@license MIT. See LICENSE.MD for details.
|
||||||
@module html-png-generator.js
|
@module html-png-generator.js
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -11,7 +11,7 @@ Definition of the HtmlPngGenerator class.
|
|||||||
, HTML = require( 'html' );
|
, HTML = require( 'html' );
|
||||||
|
|
||||||
/**
|
/**
|
||||||
An HTML-based PDF resume generator for HackMyResume.
|
An HTML-based PNG resume generator for HackMyResume.
|
||||||
*/
|
*/
|
||||||
var HtmlPngGenerator = module.exports = TemplateGenerator.extend({
|
var HtmlPngGenerator = module.exports = TemplateGenerator.extend({
|
||||||
|
|
||||||
@ -19,24 +19,29 @@ Definition of the HtmlPngGenerator class.
|
|||||||
this._super( 'png', 'html' );
|
this._super( 'png', 'html' );
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
invoke: function( rez, themeMarkup, cssInfo, opts ) {
|
||||||
Generate the binary PDF.
|
//return YAML.stringify( JSON.parse( rez.stringify() ), Infinity, 2 );
|
||||||
*/
|
},
|
||||||
onBeforeSave: function( info ) {
|
|
||||||
png( info.mk, info.outputFile );
|
generate: function( rez, f, opts ) {
|
||||||
return null; // halt further processing
|
var htmlResults = opts.targets.filter(function(t){
|
||||||
|
return t.fmt.outFormat === 'html';
|
||||||
|
});
|
||||||
|
var htmlFile = htmlResults[0].final.files.filter(function(fl){
|
||||||
|
return fl.info.ext === 'html';
|
||||||
|
});
|
||||||
|
png(htmlFile[0].data, f);
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Generate a PDF from HTML.
|
Generate a PNG from HTML.
|
||||||
*/
|
*/
|
||||||
function png( markup, fOut ) {
|
function png( markup, fOut ) {
|
||||||
|
// require('webshot')( markup , { encoding: 'binary', siteType: 'html' } )
|
||||||
require('webshot')( markup , { encoding: 'binary', siteType: 'html' } )
|
// .pipe( FS.createWriteStream( fOut ) );
|
||||||
.pipe( FS.createWriteStream( fOut ) );
|
require('webshot')( markup , fOut, { siteType: 'html' }, function(err) { } );
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}());
|
}());
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
Definition of the TemplateGenerator class.
|
Definition of the TemplateGenerator class.
|
||||||
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
|
@license MIT. See LICENSE.md for details.
|
||||||
@module template-generator.js
|
@module template-generator.js
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -17,7 +17,8 @@ Definition of the TemplateGenerator class.
|
|||||||
, MKDIRP = require('mkdirp')
|
, MKDIRP = require('mkdirp')
|
||||||
, BaseGenerator = require( './base-generator' )
|
, BaseGenerator = require( './base-generator' )
|
||||||
, EXTEND = require('../utils/extend')
|
, EXTEND = require('../utils/extend')
|
||||||
, Theme = require('../core/theme');
|
, FRESHTheme = require('../core/fresh-theme')
|
||||||
|
, JRSTheme = require('../core/jrs-theme');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -77,7 +78,14 @@ Definition of the TemplateGenerator class.
|
|||||||
@method invoke
|
@method invoke
|
||||||
@param rez A FreshResume object.
|
@param rez A FreshResume object.
|
||||||
@param opts Generator options.
|
@param opts Generator options.
|
||||||
@returns An array of strings representing generated output files.
|
@returns An array of objects representing the generated output files. Each
|
||||||
|
object has this format:
|
||||||
|
|
||||||
|
{
|
||||||
|
files: [ { info: { }, data: [ ] }, { ... } ],
|
||||||
|
themeInfo: { }
|
||||||
|
}
|
||||||
|
|
||||||
*/
|
*/
|
||||||
invoke: function( rez, opts ) {
|
invoke: function( rez, opts ) {
|
||||||
|
|
||||||
@ -156,7 +164,7 @@ Definition of the TemplateGenerator class.
|
|||||||
{ outputFile: fileName, mk: file.data } );
|
{ outputFile: fileName, mk: file.data } );
|
||||||
}
|
}
|
||||||
catch(ex) {
|
catch(ex) {
|
||||||
console.log(ex);
|
require('../core/error-handler').err(ex, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if( file.info.action === null/* && theme.explicit*/ ) {
|
else if( file.info.action === null/* && theme.explicit*/ ) {
|
||||||
@ -182,6 +190,8 @@ Definition of the TemplateGenerator class.
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return genInfo;
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
@ -220,11 +230,19 @@ Definition of the TemplateGenerator class.
|
|||||||
Given a theme title, load the corresponding theme.
|
Given a theme title, load the corresponding theme.
|
||||||
*/
|
*/
|
||||||
function themeFromMoniker() {
|
function themeFromMoniker() {
|
||||||
|
|
||||||
// Verify the specified theme name/path
|
// Verify the specified theme name/path
|
||||||
var tFolder = PATH.join(
|
var tFolder = PATH.join(
|
||||||
parsePath( require.resolve('fluent-themes') ).dirname,
|
parsePath( require.resolve('fresh-themes') ).dirname,
|
||||||
this.opts.theme
|
this.opts.theme
|
||||||
);
|
);
|
||||||
|
|
||||||
|
var t;
|
||||||
|
if( this.opts.theme.startsWith('jsonresume-theme-') ) {
|
||||||
|
console.log('LOADING JSON RESUME');
|
||||||
|
t = new JRSTheme().open( tFolder );
|
||||||
|
}
|
||||||
|
else {
|
||||||
var exists = require('path-exists').sync;
|
var exists = require('path-exists').sync;
|
||||||
if( !exists( tFolder ) ) {
|
if( !exists( tFolder ) ) {
|
||||||
tFolder = PATH.resolve( this.opts.theme );
|
tFolder = PATH.resolve( this.opts.theme );
|
||||||
@ -232,8 +250,8 @@ Definition of the TemplateGenerator class.
|
|||||||
throw { fluenterror: this.codes.themeNotFound, data: this.opts.theme};
|
throw { fluenterror: this.codes.themeNotFound, data: this.opts.theme};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
t = this.opts.themeObj || new FRESHTheme().open( tFolder );
|
||||||
var t = this.opts.themeObj || new Theme().open( tFolder );
|
}
|
||||||
|
|
||||||
// Load the theme and format
|
// Load the theme and format
|
||||||
return {
|
return {
|
||||||
|
@ -8,7 +8,8 @@ module.exports = {
|
|||||||
Sheet: require('./core/fresh-resume'),
|
Sheet: require('./core/fresh-resume'),
|
||||||
FRESHResume: require('./core/fresh-resume'),
|
FRESHResume: require('./core/fresh-resume'),
|
||||||
JRSResume: require('./core/jrs-resume'),
|
JRSResume: require('./core/jrs-resume'),
|
||||||
Theme: require('./core/theme'),
|
FRESHTheme: require('./core/fresh-theme'),
|
||||||
|
JRSTheme: require('./core/jrs-theme'),
|
||||||
FluentDate: require('./core/fluent-date'),
|
FluentDate: require('./core/fluent-date'),
|
||||||
HtmlGenerator: require('./gen/html-generator'),
|
HtmlGenerator: require('./gen/html-generator'),
|
||||||
TextGenerator: require('./gen/text-generator'),
|
TextGenerator: require('./gen/text-generator'),
|
||||||
|
@ -8,29 +8,34 @@ Internal resume generation logic for HackMyResume.
|
|||||||
module.exports = function () {
|
module.exports = function () {
|
||||||
|
|
||||||
var unused = require('./utils/string')
|
var unused = require('./utils/string')
|
||||||
, PATH = require('path');
|
, PATH = require('path')
|
||||||
|
, FS = require('fs');
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Display help documentation.
|
Display help documentation.
|
||||||
*/
|
*/
|
||||||
function help() {
|
function help() {
|
||||||
console.log( FS.readFileSync( PATH.join(__dirname, 'use.txt'), 'utf8' )
|
var manPage = FS.readFileSync( PATH.join(__dirname, 'use.txt'), 'utf8' );
|
||||||
.useful.bold );
|
console.log( manPage.useful.bold );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Internal module interface. Used by FCV Desktop and HMR.
|
Internal module interface. Used by FCV Desktop and HMR.
|
||||||
*/
|
*/
|
||||||
return {
|
var v = {
|
||||||
verbs: {
|
|
||||||
generate: require('./verbs/generate'),
|
|
||||||
build: require('./verbs/generate'),
|
build: require('./verbs/generate'),
|
||||||
validate: require('./verbs/validate'),
|
validate: require('./verbs/validate'),
|
||||||
convert: require('./verbs/convert'),
|
convert: require('./verbs/convert'),
|
||||||
create: require('./verbs/create'),
|
|
||||||
new: require('./verbs/create'),
|
new: require('./verbs/create'),
|
||||||
help: help
|
help: help
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
verbs: v,
|
||||||
|
alias: {
|
||||||
|
generate: v.build,
|
||||||
|
create: v.build
|
||||||
},
|
},
|
||||||
lib: require('./hackmyapi'),
|
lib: require('./hackmyapi'),
|
||||||
options: require('./core/default-options'),
|
options: require('./core/default-options'),
|
||||||
|
63
src/index.js
63
src/index.js
@ -2,27 +2,32 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
Command-line interface (CLI) for HackMyResume.
|
Command-line interface (CLI) for HackMyResume.
|
||||||
@license MIT. Copyright (c) 2015 James M. Devlin / FluentDesk.
|
@license MIT. Copyright (c) 2015 hacksalot (https://github.com/hacksalot)
|
||||||
@module index.js
|
@module index.js
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var ARGS = require( 'minimist' )
|
|
||||||
|
|
||||||
|
var SPAWNW = require('./core/spawn-watch')
|
||||||
|
, ARGS = require( 'minimist' )
|
||||||
, FCMD = require( './hackmycmd')
|
, FCMD = require( './hackmycmd')
|
||||||
, PKG = require('../package.json')
|
, PKG = require('../package.json')
|
||||||
, COLORS = require('colors')
|
, COLORS = require('colors')
|
||||||
, FS = require('fs')
|
, FS = require('fs')
|
||||||
, PATH = require('path')
|
, PATH = require('path')
|
||||||
|
, HACKMYSTATUS = require('./core/status-codes')
|
||||||
, opts = { }
|
, opts = { }
|
||||||
, title = ('\n*** HackMyResume v' + PKG.version + ' ***').bold.white
|
, title = ('\n*** HackMyResume v' + PKG.version + ' ***').bold.white
|
||||||
, _ = require('underscore');
|
, _ = require('underscore');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
main();
|
main();
|
||||||
}
|
}
|
||||||
catch( ex ) {
|
catch( ex ) {
|
||||||
handleError( ex );
|
require('./core/error-handler').err( ex, true );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -50,7 +55,7 @@ function main() {
|
|||||||
// Get the action to be performed
|
// Get the action to be performed
|
||||||
var params = a._.map( function(p){ return p.toLowerCase().trim(); });
|
var params = a._.map( function(p){ return p.toLowerCase().trim(); });
|
||||||
var verb = params[0];
|
var verb = params[0];
|
||||||
if( !FCMD.verbs[ verb ] ) {
|
if( !FCMD.verbs[ verb ] && !FCMD.alias[ verb ] ) {
|
||||||
logMsg('Invalid command: "'.warn + verb.warn.bold + '"'.warn);
|
logMsg('Invalid command: "'.warn + verb.warn.bold + '"'.warn);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -69,10 +74,9 @@ function main() {
|
|||||||
var src = a._.slice(1, splitAt === -1 ? undefined : splitAt );
|
var src = a._.slice(1, splitAt === -1 ? undefined : splitAt );
|
||||||
var dst = splitAt === -1 ? [] : a._.slice( splitAt + 1 );
|
var dst = splitAt === -1 ? [] : a._.slice( splitAt + 1 );
|
||||||
( splitAt === -1 ) && src.length > 1 && dst.push( src.pop() ); // Allow omitting TO keyword
|
( splitAt === -1 ) && src.length > 1 && dst.push( src.pop() ); // Allow omitting TO keyword
|
||||||
var parms = [ src, dst, opts, logMsg ];
|
|
||||||
|
|
||||||
// Invoke the action
|
// Invoke the action
|
||||||
FCMD.verbs[ verb ].apply( null, parms );
|
(FCMD.verbs[verb] || FCMD.alias[verb]).apply(null, [src, dst, opts, logMsg]);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,50 +91,7 @@ function getOpts( args ) {
|
|||||||
theme: args.t || 'modern',
|
theme: args.t || 'modern',
|
||||||
format: args.f || 'FRESH',
|
format: args.f || 'FRESH',
|
||||||
prettify: !noPretty,
|
prettify: !noPretty,
|
||||||
silent: args.s || args.silent
|
silent: args.s || args.silent,
|
||||||
|
css: args.css || 'embed'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: refactor
|
|
||||||
function handleError( ex ) {
|
|
||||||
var msg = '', exitCode;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if( ex.fluenterror ){
|
|
||||||
switch( ex.fluenterror ) { // TODO: Remove magic numbers
|
|
||||||
case 1: msg = "The specified theme couldn't be found: " + ex.data; break;
|
|
||||||
case 2: msg = "Couldn't copy CSS file to destination folder"; break;
|
|
||||||
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) + ").\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.'.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;
|
|
||||||
case 8: msg = 'Please '.guide + 'specify the filename of the resume'.guide.bold + ' to create.'.guide; break;
|
|
||||||
}
|
|
||||||
exitCode = ex.fluenterror;
|
|
||||||
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
msg = ex.toString();
|
|
||||||
exitCode = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
var idx = msg.indexOf('Error: ');
|
|
||||||
var trimmed = idx === -1 ? msg : msg.substring( idx + 7 );
|
|
||||||
if( !ex.fluenterror || ex.fluenterror < 3 ) { // TODO: magic #s
|
|
||||||
console.log( ('ERROR: ' + trimmed.toString()).red.bold );
|
|
||||||
console.log( ex.stack.gray);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
console.log( trimmed.toString() );
|
|
||||||
|
|
||||||
process.exit( exitCode );
|
|
||||||
|
|
||||||
}
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
Definitions of string utility functions.
|
Definitions of string utility functions.
|
||||||
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
|
|
||||||
@module string.js
|
@module string.js
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -10,14 +9,18 @@ See: http://stackoverflow.com/a/32800728/4942583
|
|||||||
@method isNullOrWhitespace
|
@method isNullOrWhitespace
|
||||||
*/
|
*/
|
||||||
|
|
||||||
String.isNullOrWhitespace = function( input ) {
|
(function() {
|
||||||
|
|
||||||
|
String.isNullOrWhitespace = function( input ) {
|
||||||
return !input || !input.trim();
|
return !input || !input.trim();
|
||||||
};
|
};
|
||||||
|
|
||||||
String.prototype.endsWith = function(suffix) {
|
String.prototype.endsWith = function(suffix) {
|
||||||
return this.indexOf(suffix, this.length - suffix.length) !== -1;
|
return this.indexOf(suffix, this.length - suffix.length) !== -1;
|
||||||
};
|
};
|
||||||
|
|
||||||
String.is = function( val ) {
|
String.is = function( val ) {
|
||||||
return typeof val === 'string' || val instanceof String;
|
return typeof val === 'string' || val instanceof String;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
}());
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
(function(){
|
(function(){
|
||||||
|
|
||||||
var loadSourceResumes = require('../core/load-source-resumes');
|
var ResumeFactory = require('../core/resume-factory');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Convert between FRESH and JRS formats.
|
Convert between FRESH and JRS formats.
|
||||||
@ -16,7 +16,7 @@
|
|||||||
if( src && dst && src.length && dst.length && src.length !== dst.length ) {
|
if( src && dst && src.length && dst.length && src.length !== dst.length ) {
|
||||||
throw { fluenterror: 7 };
|
throw { fluenterror: 7 };
|
||||||
}
|
}
|
||||||
var sheets = loadSourceResumes( src, _log );
|
var sheets = ResumeFactory.load( src, _log );
|
||||||
sheets.forEach(function(sheet, idx){
|
sheets.forEach(function(sheet, idx){
|
||||||
var sourceFormat = sheet.imp.orgFormat === 'JRS' ? 'JRS' : 'FRESH';
|
var sourceFormat = sheet.imp.orgFormat === 'JRS' ? 'JRS' : 'FRESH';
|
||||||
var targetFormat = sourceFormat === 'JRS' ? 'FRESH' : 'JRS';
|
var targetFormat = sourceFormat === 'JRS' ? 'FRESH' : 'JRS';
|
||||||
|
@ -1,15 +1,29 @@
|
|||||||
|
/**
|
||||||
|
Implementation of the 'generate' verb for HackMyResume.
|
||||||
|
@module generate.js
|
||||||
|
@license MIT. See LICENSE.md for details.
|
||||||
|
*/
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var PATH = require('path')
|
var PATH = require('path')
|
||||||
, parsePath = require('parse-filepath')
|
, FS = require('fs')
|
||||||
|
, MD = require('marked')
|
||||||
, MKDIRP = require('mkdirp')
|
, MKDIRP = require('mkdirp')
|
||||||
|
, EXTEND = require('../utils/extend')
|
||||||
|
, parsePath = require('parse-filepath')
|
||||||
, _opts = require('../core/default-options')
|
, _opts = require('../core/default-options')
|
||||||
, FluentTheme = require('../core/theme')
|
, FluentTheme = require('../core/fresh-theme')
|
||||||
, loadSourceResumes = require('../core/load-source-resumes')
|
, JRSTheme = require('../core/jrs-theme')
|
||||||
|
, ResumeFactory = require('../core/resume-factory')
|
||||||
, _ = require('underscore')
|
, _ = require('underscore')
|
||||||
, _fmts = require('../core/default-formats')
|
, _fmts = require('../core/default-formats')
|
||||||
, _err, _log, rez;
|
, _err, _log, rez;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Handle an exception.
|
Handle an exception.
|
||||||
*/
|
*/
|
||||||
@ -17,28 +31,34 @@
|
|||||||
throw ex;
|
throw ex;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports =
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Given a source JSON resume, a destination resume path, and a theme file,
|
Given a source resume in FRESH or JRS format, a destination resume path, and a
|
||||||
generate 0..N resumes in the desired formats.
|
theme file, generate 0..N resumes in the desired formats.
|
||||||
@param src Path to the source JSON resume file: "rez/resume.json".
|
@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 dst An array of paths to the target resume file(s).
|
||||||
@param theme Friendly name of the resume theme. Defaults to "modern".
|
@param theme Friendly name of the resume theme. Defaults to "modern".
|
||||||
@param logger Optional logging override.
|
@param logger Optional logging override.
|
||||||
*/
|
*/
|
||||||
function generate( src, dst, opts, logger, errHandler ) {
|
function build( src, dst, opts, logger, errHandler ) {
|
||||||
|
|
||||||
|
// Housekeeping...
|
||||||
_log = logger || console.log;
|
_log = logger || console.log;
|
||||||
_err = errHandler || error;
|
_err = errHandler || error;
|
||||||
|
|
||||||
//_opts = extend( true, _opts, opts );
|
//_opts = extend( true, _opts, opts );
|
||||||
_opts.theme = (opts.theme && opts.theme.toLowerCase().trim())|| 'modern';
|
_opts.theme = (opts.theme && opts.theme.toLowerCase().trim())|| 'modern';
|
||||||
_opts.prettify = opts.prettify === true ? _opts.prettify : false;
|
_opts.prettify = opts.prettify === true ? _opts.prettify : false;
|
||||||
|
_opts.css = opts.css;
|
||||||
|
|
||||||
|
// Load the theme...
|
||||||
|
var tFolder = verify_theme( _opts.theme );
|
||||||
|
var theTheme = load_theme( tFolder );
|
||||||
|
|
||||||
// Load input resumes...
|
// Load input resumes...
|
||||||
if( !src || !src.length ) { throw { fluenterror: 3 }; }
|
if( !src || !src.length ) { throw { fluenterror: 3 }; }
|
||||||
var sheets = loadSourceResumes( src, _log );
|
var sheets = ResumeFactory.load( src, _log, null,
|
||||||
|
theTheme.render ? 'JRS' : 'FRESH' );
|
||||||
|
|
||||||
// Merge input resumes...
|
// Merge input resumes...
|
||||||
var msg = '';
|
var msg = '';
|
||||||
@ -49,97 +69,100 @@
|
|||||||
});
|
});
|
||||||
msg && _log(msg);
|
msg && _log(msg);
|
||||||
|
|
||||||
// Verify the specified theme name/path
|
// Expand output resumes...
|
||||||
var relativeThemeFolder = '../../node_modules/fluent-themes/themes';
|
var targets = expand( dst, theTheme );
|
||||||
var tFolder = PATH.resolve( __dirname, relativeThemeFolder, _opts.theme);
|
|
||||||
var exists = require('path-exists').sync;
|
|
||||||
if (!exists( tFolder )) {
|
|
||||||
tFolder = PATH.resolve( _opts.theme );
|
|
||||||
if (!exists( tFolder )) {
|
|
||||||
throw { fluenterror: 1, data: _opts.theme };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load the theme
|
|
||||||
var theTheme = (new FluentTheme()).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 = parsePath(to),
|
|
||||||
fmat = pa.extname || '.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) ) }]);
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
// Run the transformation!
|
// Run the transformation!
|
||||||
var finished = targets.map( function(t) { return single(t, theTheme); });
|
targets.forEach( function(t) {
|
||||||
|
t.final = single( t, theTheme, targets );
|
||||||
|
});
|
||||||
|
|
||||||
// Don't send the client back empty-handed
|
// Don't send the client back empty-handed
|
||||||
return { sheet: rez, targets: targets, processed: finished };
|
return { sheet: rez, targets: targets, processed: targets };
|
||||||
};
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Generate a single resume of a specific format.
|
Generate a single target resume such as "out/rez.html" or "out/rez.doc".
|
||||||
@param f Full path to the destination resume to generate, for example,
|
@param targInfo Information for the target resume.
|
||||||
"/foo/bar/resume.pdf" or "c:\foo\bar\resume.txt".
|
@param theme A FRESHTheme or JRSTheme object.
|
||||||
|
@returns
|
||||||
*/
|
*/
|
||||||
function single( targInfo, theme ) {
|
function single( targInfo, theme, finished ) {
|
||||||
|
|
||||||
|
function MDIN(txt) { // TODO: Move this
|
||||||
|
return MD(txt || '' ).replace(/^\s*<p>|<\/p>\s*$/gi, '');
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var f = targInfo.file
|
var f = targInfo.file
|
||||||
, fType = targInfo.fmt.outFormat
|
, fType = targInfo.fmt.outFormat
|
||||||
, fName = PATH.basename(f, '.' + fType)
|
, fName = PATH.basename(f, '.' + fType)
|
||||||
, theFormat;
|
, theFormat;
|
||||||
|
|
||||||
// If targInfo.fmt.files exists, this theme has an explicit "files"
|
_log( 'Generating '.useful +
|
||||||
// section in its theme.json file.
|
targInfo.fmt.outFormat.toUpperCase().useful.bold +
|
||||||
|
' resume: '.useful + PATH.relative(process.cwd(), f ).useful.bold );
|
||||||
|
|
||||||
|
// If targInfo.fmt.files exists, this format is backed by a document.
|
||||||
|
// Fluent/FRESH themes are handled here.
|
||||||
if( targInfo.fmt.files && targInfo.fmt.files.length ) {
|
if( targInfo.fmt.files && targInfo.fmt.files.length ) {
|
||||||
|
|
||||||
_log( 'Generating '.useful +
|
|
||||||
targInfo.fmt.outFormat.toUpperCase().useful.bold +
|
|
||||||
' resume: '.useful + PATH.relative(process.cwd(), f ).replace(/\\/g,'/').useful.bold);
|
|
||||||
|
|
||||||
theFormat = _fmts.filter(
|
theFormat = _fmts.filter(
|
||||||
function(fmt) { return fmt.name === targInfo.fmt.outFormat; })[0];
|
function(fmt) { return fmt.name === targInfo.fmt.outFormat; })[0];
|
||||||
MKDIRP.sync( PATH.dirname( f ) ); // Ensure dest folder exists;
|
MKDIRP.sync( PATH.dirname( f ) ); // Ensure dest folder exists;
|
||||||
theFormat.gen.generate( rez, f, _opts );
|
_opts.targets = finished;
|
||||||
|
return 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 ).replace(/\\/g,'/').useful.bold);
|
|
||||||
|
|
||||||
theFormat = _fmts.filter(
|
// Otherwise this is either a) a JSON Resume theme or b) an ad-hoc format
|
||||||
function(fmt) { return fmt.name === targInfo.fmt.outFormat; })[0];
|
// (JSON, YML, or PNG) that every theme gets "for free".
|
||||||
MKDIRP.sync( PATH.dirname( f ) ); // Ensure dest folder exists;
|
else {
|
||||||
theFormat.gen.generate( rez, f, _opts );
|
|
||||||
|
theFormat = _fmts.filter( function(fmt) {
|
||||||
|
return fmt.name === targInfo.fmt.outFormat;
|
||||||
|
})[0];
|
||||||
|
|
||||||
|
var outFolder = PATH.dirname( f );
|
||||||
|
MKDIRP.sync( outFolder ); // Ensure dest folder exists;
|
||||||
|
|
||||||
|
// JSON Resume themes have a 'render' method that needs to be called
|
||||||
|
if( theme.render ) {
|
||||||
|
var COPY = require('copy');
|
||||||
|
var globs = [ /*'**',*/ '*.css', '*.js', '*.png', '*.jpg', '*.gif', '*.bmp' ];
|
||||||
|
COPY.sync( globs , outFolder, {
|
||||||
|
cwd: theme.folder, nodir: true,
|
||||||
|
ignore: ['node_modules/','node_modules/**']
|
||||||
|
// rewrite: function(p1, p2) {
|
||||||
|
// return PATH.join(p2, p1);
|
||||||
|
// }
|
||||||
|
});
|
||||||
|
|
||||||
|
// Prevent JSON Resume theme .js from chattering (TODO: redirect IO)
|
||||||
|
var consoleLog = console.log;
|
||||||
|
console.log = function() { };
|
||||||
|
|
||||||
|
// Call the theme's render method
|
||||||
|
var rezDupe = rez.harden();
|
||||||
|
var rezHtml = theme.render( rezDupe );
|
||||||
|
|
||||||
|
// Turn logging back on
|
||||||
|
console.log = consoleLog;
|
||||||
|
|
||||||
|
// Unharden
|
||||||
|
rezHtml = rezHtml.replace( /@@@@~.+?~@@@@/g, function(val){
|
||||||
|
return MDIN( val.replace( /~@@@@/gm,'' ).replace( /@@@@~/gm,'' ) );
|
||||||
|
});
|
||||||
|
|
||||||
|
// Save the file
|
||||||
|
FS.writeFileSync( f, rezHtml );
|
||||||
|
|
||||||
|
// Return markup to the client
|
||||||
|
return rezHtml;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return theFormat.gen.generate( rez, f, _opts );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch( ex ) {
|
catch( ex ) {
|
||||||
@ -147,4 +170,117 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
Expand output files. For example, "foo.all" should be expanded to
|
||||||
|
["foo.html", "foo.doc", "foo.pdf", "etc"].
|
||||||
|
@param dst An array of output files as specified by the user.
|
||||||
|
@param theTheme A FRESHTheme or JRSTheme object.
|
||||||
|
*/
|
||||||
|
function expand( dst, theTheme ) {
|
||||||
|
|
||||||
|
// Add freebie formats (JSON, YAML, PNG) every theme gets...
|
||||||
|
// Add HTML-driven PNG only if the theme has an HTML format.
|
||||||
|
theTheme.formats.json = theTheme.formats.json || {
|
||||||
|
freebie: true, title: 'json', outFormat: 'json', pre: 'json',
|
||||||
|
ext: 'json', path: null, data: null
|
||||||
|
};
|
||||||
|
theTheme.formats.yml = theTheme.formats.yml || {
|
||||||
|
freebie: true, title: 'yaml', outFormat: 'yml', pre: 'yml',
|
||||||
|
ext: 'yml', path: null, data: null
|
||||||
|
};
|
||||||
|
if( theTheme.formats.html && !theTheme.formats.png ) {
|
||||||
|
theTheme.formats.png = {
|
||||||
|
freebie: true, title: 'png', outFormat: 'png',
|
||||||
|
ext: 'yml', path: null, data: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up the destination collection. It's either the array of files passed
|
||||||
|
// by the user or 'out/resume.all' if no targets were specified.
|
||||||
|
var destColl = (dst && dst.length && dst) ||
|
||||||
|
[PATH.normalize('out/resume.all')];
|
||||||
|
|
||||||
|
// Assemble an array of expanded target files... (can't use map() here)
|
||||||
|
var targets = [];
|
||||||
|
destColl.forEach( function(t) {
|
||||||
|
|
||||||
|
var to = PATH.resolve(t), pa = parsePath(to),fmat = pa.extname || '.all';
|
||||||
|
|
||||||
|
var explicitFormats = _.omit( theTheme.formats, function(val, key) {
|
||||||
|
return !val.freebie;
|
||||||
|
});
|
||||||
|
var implicitFormats = _.omit( theTheme.formats, function(val) {
|
||||||
|
return val.freebie;
|
||||||
|
});
|
||||||
|
|
||||||
|
targets.push.apply(
|
||||||
|
targets, fmat === '.all' ?
|
||||||
|
Object.keys( implicitFormats ).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) ) }]);
|
||||||
|
|
||||||
|
targets.push.apply(
|
||||||
|
targets, fmat === '.all' ?
|
||||||
|
Object.keys( explicitFormats ).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) ) }]);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
return targets;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
Verify the specified theme name/path.
|
||||||
|
*/
|
||||||
|
function verify_theme( themeNameOrPath ) {
|
||||||
|
var tFolder = PATH.resolve(
|
||||||
|
__dirname,
|
||||||
|
'../../node_modules/fresh-themes/themes',
|
||||||
|
themeNameOrPath
|
||||||
|
);
|
||||||
|
var exists = require('path-exists').sync;
|
||||||
|
if( !exists( tFolder ) ) {
|
||||||
|
tFolder = PATH.resolve( themeNameOrPath );
|
||||||
|
if( !exists( tFolder ) ) {
|
||||||
|
throw { fluenterror: 1, data: _opts.theme };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tFolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
Load the specified theme.
|
||||||
|
*/
|
||||||
|
function load_theme( tFolder ) {
|
||||||
|
|
||||||
|
// Create a FRESH or JRS theme object
|
||||||
|
var theTheme = _opts.theme.indexOf('jsonresume-theme-') > -1 ?
|
||||||
|
new JRSTheme().open(tFolder) : new FluentTheme().open( tFolder );
|
||||||
|
|
||||||
|
// Cache the theme object
|
||||||
|
_opts.themeObj = theTheme;
|
||||||
|
|
||||||
|
// Output a message TODO: core should not log
|
||||||
|
var numFormats = Object.keys(theTheme.formats).length;
|
||||||
|
_log( 'Applying '.info + theTheme.name.toUpperCase().infoBold +
|
||||||
|
(' theme (' + numFormats + ' formats)').info);
|
||||||
|
return theTheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = build;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}());
|
}());
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
(function() {
|
(function() {
|
||||||
|
|
||||||
var FS = require('fs');
|
var FS = require('fs');
|
||||||
var loadSourceResumes = require('../core/load-source-resumes');
|
var ResumeFactory = require('../core/resume-factory');
|
||||||
|
|
||||||
module.exports =
|
module.exports =
|
||||||
|
|
||||||
@ -20,7 +20,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Load input resumes...
|
// Load input resumes...
|
||||||
var sheets = loadSourceResumes(src, _log, function( res ) {
|
var sheets = ResumeFactory.load(src, _log, function( res ) {
|
||||||
try {
|
try {
|
||||||
return {
|
return {
|
||||||
file: res,
|
file: res,
|
||||||
|
@ -43,15 +43,15 @@ describe('Testing CLI interface', function () {
|
|||||||
silent: true
|
silent: true
|
||||||
};
|
};
|
||||||
|
|
||||||
run( 'new', ['tests/sandbox/new-fresh-resume.json'], [], opts, ' (FRESH format)' );
|
run( 'new', ['test/sandbox/new-fresh-resume.json'], [], opts, ' (FRESH format)' );
|
||||||
run( 'new', ['tests/sandbox/new-jrs-resume.json'], [], opts2, ' (JRS format)' );
|
run( 'new', ['test/sandbox/new-jrs-resume.json'], [], opts2, ' (JRS format)' );
|
||||||
run( 'new', ['tests/sandbox/new-1.json', 'tests/sandbox/new-2.json', 'tests/sandbox/new-3.json'], [], opts, ' (multiple FRESH resumes)' );
|
run( 'new', ['test/sandbox/new-1.json', 'test/sandbox/new-2.json', 'test/sandbox/new-3.json'], [], opts, ' (multiple FRESH resumes)' );
|
||||||
run( 'new', ['tests/sandbox/new-jrs-1.json', 'tests/sandbox/new-jrs-2.json', 'tests/sandbox/new-jrs-3.json'], [], opts, ' (multiple JRS resumes)' );
|
run( 'new', ['test/sandbox/new-jrs-1.json', 'test/sandbox/new-jrs-2.json', 'test/sandbox/new-jrs-3.json'], [], opts, ' (multiple JRS resumes)' );
|
||||||
run( 'new', ['tests/sandbox/new-jrs-resume.json'], [], opts2, ' (JRS format)' );
|
run( 'new', ['test/sandbox/new-jrs-resume.json'], [], opts2, ' (JRS format)' );
|
||||||
fail( 'new', [], [], opts, " (when a filename isn't specified)" );
|
fail( 'new', [], [], opts, " (when a filename isn't specified)" );
|
||||||
|
|
||||||
run( 'validate', ['node_modules/jane-q-fullstacker/resume/jane-resume.json'], [], opts, ' (FRESH format)' );
|
run( 'validate', ['node_modules/jane-q-fullstacker/resume/jane-resume.json'], [], opts, ' (FRESH format)' );
|
||||||
run( 'validate', ['tests/sandbox/new-fresh-resume.json'], [], opts, ' (FRESH format)' );
|
run( 'validate', ['test/sandbox/new-fresh-resume.json'], [], opts, ' (FRESH format)' );
|
||||||
|
|
||||||
function run( verb, src, dst, opts, msg ) {
|
function run( verb, src, dst, opts, msg ) {
|
||||||
msg = msg || '.';
|
msg = msg || '.';
|
||||||
|
@ -9,47 +9,37 @@ var chai = require('chai')
|
|||||||
|
|
||||||
chai.config.includeStack = false;
|
chai.config.includeStack = false;
|
||||||
|
|
||||||
describe('jane-doe.json (FRESH)', function () {
|
function testResume(opts) {
|
||||||
|
|
||||||
|
describe( opts.title + ' (FRESH)', function () {
|
||||||
|
|
||||||
var _sheet;
|
var _sheet;
|
||||||
|
|
||||||
it('should open without throwing an exception', function () {
|
it('should open without throwing an exception', function () {
|
||||||
function tryOpen() {
|
function tryOpen() {
|
||||||
_sheet = new FRESHResume().open(
|
_sheet = new FRESHResume().open( opts.path );
|
||||||
'node_modules/jane-q-fullstacker/resume/jane-resume.json' );
|
|
||||||
}
|
}
|
||||||
tryOpen.should.not.Throw();
|
tryOpen.should.not.Throw();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should have one or more of each section', function() {
|
it('should have one or more of each section', function() {
|
||||||
expect(
|
var newObj = _.pick( _sheet, opts.sections );
|
||||||
//(_sheet.basics) &&
|
expect( Object.keys(newObj).length ).to.equal( opts.sections.length );
|
||||||
_sheet.name && _sheet.info && _sheet.location && _sheet.contact &&
|
|
||||||
(_sheet.employment.history && _sheet.employment.history.length > 0) &&
|
|
||||||
(_sheet.skills && _sheet.skills.list.length > 0) &&
|
|
||||||
(_sheet.education.history && _sheet.education.history.length > 0) &&
|
|
||||||
(_sheet.service.history && _sheet.service.history.length > 0) &&
|
|
||||||
(_sheet.writing && _sheet.writing.length > 0) &&
|
|
||||||
(_sheet.recognition && _sheet.recognition.length > 0) &&
|
|
||||||
(_sheet.samples && _sheet.samples.length > 0) &&
|
|
||||||
(_sheet.references && _sheet.references.length > 0) &&
|
|
||||||
(_sheet.interests && _sheet.interests.length > 0)
|
|
||||||
).to.equal( true );
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should have a work duration of 7 years', function() {
|
it('should have a work duration of ' + opts.duration + ' years', function() {
|
||||||
_sheet.computed.numYears.should.equal( 7 );
|
_sheet.computed.numYears.should.equal( opts.duration );
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should save without throwing an exception', function(){
|
it('should save without throwing an exception', function(){
|
||||||
function trySave() {
|
function trySave() {
|
||||||
_sheet.save( 'tests/sandbox/jane-q-fullstacker.json' );
|
_sheet.save( 'test/sandbox/' + opts.title + '.json' );
|
||||||
}
|
}
|
||||||
trySave.should.not.Throw();
|
trySave.should.not.Throw();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not be modified after saving', function() {
|
it('should not be modified after saving', function() {
|
||||||
var savedSheet = new FRESHResume().open('tests/sandbox/jane-q-fullstacker.json');
|
var savedSheet = new FRESHResume().open('test/sandbox/' + opts.title + '.json');
|
||||||
_sheet.stringify().should.equal( savedSheet.stringify() );
|
_sheet.stringify().should.equal( savedSheet.stringify() );
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -64,4 +54,9 @@ describe('jane-doe.json (FRESH)', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var sects = [ 'info', 'employment', 'service', 'skills', 'education', 'writing', 'recognition', 'references' ];
|
||||||
|
testResume({ title: 'jane-q-fullstacker', path: 'node_modules/jane-q-fullstacker/resume/jane-resume.json', duration: 7, sections: sects });
|
||||||
|
testResume({ title: 'johnny-trouble-resume', path: 'node_modules/johnny-trouble-resume/src/johnny-trouble.fresh.json', duration: 3, sections: sects });
|
||||||
|
@ -38,13 +38,13 @@ function testResume( opts ) {
|
|||||||
it('should save without throwing an exception', function() {
|
it('should save without throwing an exception', function() {
|
||||||
var that = this;
|
var that = this;
|
||||||
function trySave() {
|
function trySave() {
|
||||||
_sheet.save( 'tests/sandbox/' + opts.title + '.json' );
|
_sheet.save( 'test/sandbox/' + opts.title + '.json' );
|
||||||
}
|
}
|
||||||
trySave.should.not.Throw();
|
trySave.should.not.Throw();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not be modified after saving', function() {
|
it('should not be modified after saving', function() {
|
||||||
var savedSheet = new JRSResume().open( 'tests/sandbox/' + opts.title + '.json' );
|
var savedSheet = new JRSResume().open( 'test/sandbox/' + opts.title + '.json' );
|
||||||
_sheet.stringify().should.equal( savedSheet.stringify() );
|
_sheet.stringify().should.equal( savedSheet.stringify() );
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
|
|
||||||
var chai = require('chai')
|
var SPAWNWATCHER = require('../src/core/spawn-watch')
|
||||||
|
, chai = require('chai')
|
||||||
, expect = chai.expect
|
, expect = chai.expect
|
||||||
, should = chai.should()
|
, should = chai.should()
|
||||||
, path = require('path')
|
, path = require('path')
|
||||||
@ -11,7 +12,9 @@ var chai = require('chai')
|
|||||||
|
|
||||||
chai.config.includeStack = false;
|
chai.config.includeStack = false;
|
||||||
|
|
||||||
describe('Testing themes', function () {
|
function genThemes( title, src, fmt ) {
|
||||||
|
|
||||||
|
describe('Testing themes against ' + title.toUpperCase() + ' resume ' + '(' + fmt + ')' , function () {
|
||||||
|
|
||||||
var _sheet;
|
var _sheet;
|
||||||
|
|
||||||
@ -26,28 +29,40 @@ describe('Testing themes', function () {
|
|||||||
useful: 'green',
|
useful: 'green',
|
||||||
});
|
});
|
||||||
|
|
||||||
function genTheme( themeName ) {
|
function genTheme( fmt, src, themeName, themeLoc, testTitle ) {
|
||||||
it( themeName.toUpperCase() + ' theme should generate without throwing an exception', function () {
|
themeLoc = themeLoc || themeName;
|
||||||
|
testTitle = themeName.toUpperCase() + ' theme (' + fmt + ') should generate without throwing an exception';
|
||||||
|
it( testTitle, function () {
|
||||||
function tryOpen() {
|
function tryOpen() {
|
||||||
var src = ['node_modules/jane-q-fullstacker/resume/jane-resume.json'];
|
//var src = ['node_modules/jane-q-fullstacker/resume/jane-resume.json'];
|
||||||
var dst = ['tests/sandbox/' + themeName + '/resume.all'];
|
var dst = ['test/sandbox/' + fmt + '/' + title + '/' + themeName + '/resume.all'];
|
||||||
var opts = {
|
var opts = {
|
||||||
theme: themeName,
|
theme: themeLoc,
|
||||||
format: 'FRESH',
|
format: fmt,
|
||||||
prettify: true,
|
prettify: true,
|
||||||
silent: true
|
silent: true
|
||||||
};
|
};
|
||||||
FCMD.verbs.build( src, dst, opts, function() { } );
|
FCMD.verbs.build( src, dst, opts, function() {} );
|
||||||
}
|
}
|
||||||
tryOpen.should.not.Throw();
|
tryOpen.should.not.Throw();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
genTheme('hello-world');
|
genTheme(fmt, src, 'hello-world');
|
||||||
genTheme('compact');
|
genTheme(fmt, src, 'compact');
|
||||||
genTheme('modern');
|
genTheme(fmt, src, 'modern');
|
||||||
genTheme('minimist');
|
genTheme(fmt, src, 'minimist');
|
||||||
genTheme('awesome');
|
genTheme(fmt, src, 'awesome');
|
||||||
genTheme('positive');
|
genTheme(fmt, src, 'positive');
|
||||||
|
genTheme(fmt, src, 'jsonresume-theme-boilerplate', 'node_modules/jsonresume-theme-boilerplate' );
|
||||||
|
genTheme(fmt, src, 'jsonresume-theme-sceptile', 'node_modules/jsonresume-theme-sceptile' );
|
||||||
|
genTheme(fmt, src, 'jsonresume-theme-modern', 'node_modules/jsonresume-theme-modern' );
|
||||||
|
genTheme(fmt, src, 'jsonresume-theme-classy', 'node_modules/jsonresume-theme-classy' );
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
genThemes( 'jane-q-fullstacker', ['node_modules/jane-q-fullstacker/resume/jane-resume.json'], 'FRESH' );
|
||||||
|
genThemes( 'johnny-trouble', ['node_modules/johnny-trouble-resume/src/johnny-trouble.fresh.json'], 'FRESH' );
|
||||||
|
genThemes( 'richard-hendriks', ['test/resumes/jrs-0.0.0/richard-hendriks.json'], 'JRS' );
|
||||||
|
Loading…
Reference in New Issue
Block a user