mirror of
https://github.com/JuanCanham/HackMyResume.git
synced 2025-05-12 00:27:08 +01:00
Compare commits
44 Commits
Author | SHA1 | Date | |
---|---|---|---|
6e5a44798b | |||
1fbfe2507b | |||
d6a3aab68a | |||
9fdfd1b5a6 | |||
f4e763bd9c | |||
fbfff2a4e4 | |||
1c93932737 | |||
cba29511bc | |||
1d655a4ddb | |||
ca94513630 | |||
971d4a5439 | |||
f3dcbd9081 | |||
29c53af843 | |||
8d24087faa | |||
95df8e5af4 | |||
8a1da777b0 | |||
44555da00f | |||
46bd5d51cc | |||
3964d300aa | |||
d6280e6d89 | |||
4a2a47f551 | |||
ae51930c9c | |||
fb33455bea | |||
28c703daf7 | |||
0246a5da19 | |||
840d17c67b | |||
9f22e94cf7 | |||
97ebecd84a | |||
96b9bb68e3 | |||
c5a5d3761d | |||
c147403b1c | |||
a2723452c2 | |||
cb3488276d | |||
43419c27cf | |||
0f0c399dd5 | |||
cb46497346 | |||
850c640368 | |||
60e455b36d | |||
af896c85ea | |||
6a7bb5ea5b | |||
3b6f2ad37e | |||
101eebdd95 | |||
830c36818e | |||
39e995213f |
142
FAQ.md
Normal file
142
FAQ.md
Normal file
@ -0,0 +1,142 @@
|
||||
Frequently Asked Questions (FAQ)
|
||||
================================
|
||||
|
||||
## How do I get started with HackMyResume?
|
||||
|
||||
1. Install with NPM: `[sudo] npm install hackmyresume -g`.
|
||||
|
||||
2. Create a new resume with: `hackmyresume NEW <resume-name>.json`.
|
||||
|
||||
3. Test with `hackmyresume BUILD <resume-name>.json`. Look in the `out/` folder.
|
||||
|
||||
4. Play around with different themes with the `-t` or `--theme` parameter. You can use any [FRESH](https://github.com/fluentdesk/fresh-themes) or [JSON Resume](https://jsonresume.org/themes) theme. The latter have to be installed first.
|
||||
|
||||
## What is FRESH?
|
||||
|
||||
FRESH is the **F**luent **R**esume and **E**mployment **S**ystem for **H**umans. It's an open-source, user-first workflow, schema, and set of practices for technical candidates and recruiters.
|
||||
|
||||
## What is FRESCA?
|
||||
|
||||
The **F**RESH **R**esume and **E**mployment **SC**hem**A**—an open-source, JSON-driven schema for resumes, CVs, and other employment artifacts. FRESCA is the recommended schema/format for FRESH, with optional support for JSON Resume.
|
||||
|
||||
## What is JSON Resume?
|
||||
|
||||
An [open resume standard](http://jsonresume.org/themes/) sponsored by Hired.com. Like FRESCA, JSON Resume is JSON-driven and open-source. Unlike FRESCA, JSON Resume targets a worldwide audience where FRESCA is optimized for technical candidates.
|
||||
|
||||
## Should I use the FRESH or JSON Resume format/schema for my resume?
|
||||
|
||||
Both! The workflow we like to use:
|
||||
|
||||
1. Create a resume in FRESH format for tooling and analysis.
|
||||
2. Convert it to JSON Resume format for additional themes/tools.
|
||||
3. Maintain both versions.
|
||||
|
||||
Both formats are open-source and both formats are JSON-driven. FRESH was designed as a universal container format and superset of existing formats, where the JSON Resume format is intended for a generic audience.
|
||||
|
||||
## How do I use a FRESH theme?
|
||||
|
||||
FRESH themes currently come preinstalled with HackMyResume.
|
||||
|
||||
1. Specify the theme name in the `--theme` or `-t` parameter to the **build** command:
|
||||
|
||||
```bash
|
||||
hackmyresume BUILD my-resume.json --theme <theme-name>
|
||||
```
|
||||
|
||||
`<theme-name>` can be one of `positive`, `compact`, `modern`, `minimist`, `hello-world`, or `awesome`.
|
||||
|
||||
2. Check your output folder. Although under FRESH, HTML formats are hardened to a degree for local file access, it's best to view HTML formats over a local web server connection.
|
||||
|
||||
## How do I use a JSON Resume theme?
|
||||
|
||||
1. Install the theme locally. The easiest way to do that is with NPM.
|
||||
|
||||
```bash
|
||||
npm install jsonresume-theme-classy
|
||||
```
|
||||
|
||||
2. Pass the theme folder path into HackMyResume:
|
||||
|
||||
```bash
|
||||
hackmyresume BUILD resume.json --theme node_modules/jsonresume-theme-classy
|
||||
```
|
||||
|
||||
3. Check your output folder. It's best to view HTML formats over a local web server connection.
|
||||
|
||||
## Should I keep my resume in version control?
|
||||
|
||||
Absolutely! As text-based, JSON-driven documents, both FRESH and JSON Resume are ideal candidates for version control. Future versions of HackMyResume will have this functionality built-in.
|
||||
|
||||
## Can I change the default section titles ("Employment", "Skills", etc.)?
|
||||
|
||||
If you're using a FRESH theme, yes. First, create a HackMyResume options file mapping resume sections to your preferred section title:
|
||||
|
||||
```javascript
|
||||
// myoptions.json
|
||||
{
|
||||
"sectionTitles": {
|
||||
"employment": "empleo",
|
||||
"skills": "habilidades",
|
||||
"education": "educación"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then, pass the options file into the `-o` or `--opts` parameter:
|
||||
|
||||
```bash
|
||||
hackmyresume BUILD resume.json -o myoptions.json
|
||||
```
|
||||
|
||||
This ability is currently only supported for FRESH resume themes.
|
||||
|
||||
## The HackMyResume terminal color scheme is giving me a headache. Can I disable it?
|
||||
|
||||
Yes. Use the `--no-color` option to disable terminal colors:
|
||||
|
||||
`hackmyresume <somecommand> <someoptions> --no-color`
|
||||
|
||||
## What's the difference between a FRESH theme and a JSON Resume theme?
|
||||
|
||||
FRESH themes are multiformat (HTML, Word, PDF, etc.) and required to support
|
||||
Markdown formatting, configurable section titles, and various other features.
|
||||
|
||||
JSON Resume themes are typically HTML-driven, but capable of expansion to other
|
||||
formats through tools. JSON Resume themes don't support Markdown natively, but
|
||||
HMR does its best to apply your Markdown, when present, to any JSON Resume
|
||||
themes it encounters.
|
||||
|
||||
## Do I have to have a FRESH resume to use a FRESH theme or a JSON Resume to use a JSON Resume theme?
|
||||
|
||||
No. You can mix and match FRESH and JRS-format themes freely. HackMyResume will
|
||||
perform the necessary conversions on the fly.
|
||||
|
||||
## Can I build my own custom FRESH theme?
|
||||
|
||||
Yes. The easiest way is to copy an existing FRESH theme, like `modern` or
|
||||
`compact`, and make your changes there. You can test your theme with:
|
||||
|
||||
```bash
|
||||
hackmyresume build resume.json --theme path/to/my/theme/folder
|
||||
```
|
||||
|
||||
## Can I build my own custom JSON Resume theme?
|
||||
|
||||
Yes. The easiest way is to copy an existing JSON Rsume theme and make your
|
||||
changes there. You can test your theme with:
|
||||
|
||||
```bash
|
||||
hackmyresume build resume.json --theme path/to/my/theme/folder
|
||||
```
|
||||
|
||||
## Can I build my own tools / services / apps / websites around FRESH / FRESCA?
|
||||
|
||||
Yes! FRESH/FRESCA formats are 100% open source, permissively licensed under MIT,
|
||||
and 100% free from company-specific, tool-specific, or commercially oriented
|
||||
lock-in or cruft. These are clean formats designed for users and builders.
|
||||
|
||||
## Can I build my own tools / services / apps / websites around JSON Resume?
|
||||
|
||||
Yes! HackMyResume is not affiliated with JSON Resume, but like FRESH/FRESCA,
|
||||
JSON Resume is open-source, permissively licensed, and free of proprietary
|
||||
lock-in. See the JSON Resume website for details.
|
152
README.md
152
README.md
@ -18,25 +18,29 @@ Use it to:
|
||||
1. **Generate** HTML, Markdown, LaTeX, MS Word, PDF, plain text, JSON, XML,
|
||||
YAML, print, smoke signal, carrier pigeon, and other arbitrary-format resumes
|
||||
and CVs, from a single source of truth—without violating DRY.
|
||||
2. **Convert** resumes between [FRESH][fresca] and [JSON Resume][6] formats.
|
||||
3. **Validate** resumes against either format.
|
||||
2. **Analyze** your resume for keyword density, gaps/overlaps, and other
|
||||
metrics.
|
||||
3. **Convert** resumes between [FRESH][fresca] and [JSON Resume][6] formats.
|
||||
4. **Validate** resumes against either format.
|
||||
|
||||
HackMyResume is built with Node.js and runs on recent versions of OS X, Linux,
|
||||
or Windows.
|
||||
or Windows. View the [FAQ](FAQ.md).
|
||||
|
||||
## Features
|
||||
|
||||
- OS X, Linux, and Windows.
|
||||
- Choose from dozens of FRESH or JSON Resume themes.
|
||||
- Private, local-only resume authoring and analysis.
|
||||
- Analyze your resume for keywords, gaps, and other metrics.
|
||||
- Store your resume data as a durable, versionable JSON or YAML document.
|
||||
- Generate polished resumes in multiple formats without violating [DRY][dry].
|
||||
- Output to HTML, Markdown, LaTeX, PDF, MS Word, JSON, YAML, plain text, or XML.
|
||||
- Validate resumes against the FRESH or JSON Resume schema.
|
||||
- Support for multiple input and output resumes.
|
||||
- Convert between FRESH and JSON Resume resumes.
|
||||
- Use from your command line or [desktop][7].
|
||||
- Free and open-source through the MIT license.
|
||||
- Updated daily / weekly. Contributions welcome.
|
||||
- Updated daily / weekly. Contributions are [welcome](CONTRIBUTING.md).
|
||||
|
||||
## Install
|
||||
|
||||
@ -46,13 +50,22 @@ Install the latest stable version of HackMyResume with NPM:
|
||||
[sudo] npm install hackmyresume -g
|
||||
```
|
||||
|
||||
To install the latest bleeding-edge version (updated daily):
|
||||
Alternately, install the latest bleeding-edge version (updated daily):
|
||||
|
||||
```bash
|
||||
[sudo] npm install hacksalot/hackmyresume#dev -g
|
||||
```
|
||||
|
||||
**For PDF generation**, you'll need to install a copy of [wkhtmltopdf][3] and/or PhantomJS for your platform.
|
||||
## Installing PDF Support (optional)
|
||||
|
||||
HackMyResume tries not to impose a specific PDF engine requirement on
|
||||
the user, but will instead work with whatever PDF engines you have installed.
|
||||
|
||||
Currently, HackMyResume's PDF generation requires either [Phantom.js][2] or
|
||||
[wkhtmltopdf][3] to be installed on your system and the `phantomjs` and/or
|
||||
`wkhtmltopdf` binaries to be accessible on your PATH. This is an optional
|
||||
requirement for users who care about PDF formats. If you don't care about PDF
|
||||
formats, skip this step.
|
||||
|
||||
## Installing Themes
|
||||
|
||||
@ -266,6 +279,101 @@ hackmyresume BUILD me.json TO out/resume.all
|
||||
`out/resume.doc`, `out/resume.html`, `out/resume.txt`, `out/resume.pdf`, and
|
||||
`out/resume.json`.
|
||||
|
||||
### Analyzing
|
||||
|
||||
HackMyResume can analyze your resume for keywords, employment gaps, and other
|
||||
metrics. Run:
|
||||
|
||||
```bash
|
||||
hackmyresume ANALYZE <my-resume>.json
|
||||
```
|
||||
|
||||
Depending on the HackMyResume version, you should see output similar to:
|
||||
|
||||
|
||||
```
|
||||
*** HackMyResume v1.4.1 ***
|
||||
Reading resume: resume.json
|
||||
Analyzing FRESH resume: resume.json
|
||||
|
||||
SECTIONS (10):
|
||||
|
||||
employment: 12
|
||||
education: 2
|
||||
service: 1
|
||||
skills: 8
|
||||
writing: 1
|
||||
recognition: 0
|
||||
social: 4
|
||||
interests: 2
|
||||
references: 1
|
||||
languages: 2
|
||||
|
||||
COVERAGE (61.1%):
|
||||
|
||||
Total Days: 6034
|
||||
Employed: 3688
|
||||
Gaps: 8 [31, 1065, 273, 153, 671, 61, 61, 31]
|
||||
Overlaps: 1 [243]
|
||||
|
||||
KEYWORDS (61):
|
||||
|
||||
Node.js: 6 mentions
|
||||
JavaScript: 9 mentions
|
||||
SQL Server: 3 mentions
|
||||
Visual Studio: 6 mentions
|
||||
Web API: 1 mentions
|
||||
N-tier / 3-tier: 1 mentions
|
||||
HTML 5: 1 mentions
|
||||
JavaScript: 6 mentions
|
||||
CSS: 2 mentions
|
||||
Sass / LESS / SCSS: 1 mentions
|
||||
LAMP: 3 mentions
|
||||
WISC: 1 mentions
|
||||
HTTP: 21 mentions
|
||||
JSON: 1 mentions
|
||||
XML: 2 mentions
|
||||
REST: 1 mentions
|
||||
WebSockets: 2 mentions
|
||||
Backbone.js: 3 mentions
|
||||
Angular.js: 1 mentions
|
||||
Node.js: 4 mentions
|
||||
NPM: 1 mentions
|
||||
Bower: 1 mentions
|
||||
Grunt: 2 mentions
|
||||
Gulp: 1 mentions
|
||||
jQuery: 2 mentions
|
||||
Bootstrap: 3 mentions
|
||||
Underscore.js: 1 mentions
|
||||
PhantomJS: 1 mentions
|
||||
CoffeeScript: 1 mentions
|
||||
Python: 11 mentions
|
||||
Perl: 4 mentions
|
||||
PHP: 7 mentions
|
||||
MySQL: 12 mentions
|
||||
PostgreSQL: 4 mentions
|
||||
NoSQL: 2 mentions
|
||||
Apache: 2 mentions
|
||||
AWS: 2 mentions
|
||||
EC2: 2 mentions
|
||||
RDS: 3 mentions
|
||||
S3: 1 mentions
|
||||
Azure: 1 mentions
|
||||
Rackspace: 1 mentions
|
||||
C++: 23 mentions
|
||||
C++ 11: 1 mentions
|
||||
Boost: 1 mentions
|
||||
Xcode: 2 mentions
|
||||
gcc: 1 mentions
|
||||
OO&AD: 1 mentions
|
||||
.NET: 20 mentions
|
||||
Unity 5: 2 mentions
|
||||
Mono: 3 mentions
|
||||
MonoDevelop: 1 mentions
|
||||
Xamarin: 1 mentions
|
||||
TOTAL: 180 mentions
|
||||
```
|
||||
|
||||
### Validating
|
||||
|
||||
HackMyResume can also validate your resumes against either the [FRESH /
|
||||
@ -299,14 +407,30 @@ where <INPUTS> is one or more resumes in FRESH or JSON Resume format, and
|
||||
autodetect the format (FRESH or JRS) of each input resume and convert it to the
|
||||
other format (JRS or FRESH).
|
||||
|
||||
### External options
|
||||
|
||||
Starting in v1.4.x you can pass options into HackMyResume via an external
|
||||
options or ".hackmyrc" file.
|
||||
|
||||
```javascript
|
||||
{
|
||||
// Set the default theme to "compact"
|
||||
"theme": "compact",
|
||||
// Change the "employment" section title text to "Work"
|
||||
"sectionTitles": {
|
||||
"employment": "Work"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Prettifying
|
||||
|
||||
HackMyResume applies [js-beautify][10]-style HTML prettification by default to
|
||||
HTML-formatted resumes. To disable prettification, the `--nopretty` or `-n` flag
|
||||
can be used:
|
||||
HTML-formatted resumes. To disable prettification, the `--no-prettify` or `-n`
|
||||
flag can be used:
|
||||
|
||||
```bash
|
||||
hackmyresume BUILD resume.json out.all --nopretty
|
||||
hackmyresume BUILD resume.json out.all --no-prettify
|
||||
```
|
||||
|
||||
### Silent Mode
|
||||
@ -318,6 +442,16 @@ hackmyresume BUILD resume.json -o someFile.all -s
|
||||
hackmyresume BUILD resume.json -o someFile.all --silent
|
||||
```
|
||||
|
||||
### Debug Mode
|
||||
|
||||
Use `-d` or `--debug` to force HMR to emit a call stack when errors occur. In
|
||||
the future, this option will emit detailed error logging.
|
||||
|
||||
```bash
|
||||
hackmyresume BUILD resume.json -d
|
||||
hackmyresume ANALYZE resume.json --debug
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
HackMyResume is a community-driven free and open source project under the MIT
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "hackmyresume",
|
||||
"version": "1.4.0",
|
||||
"version": "1.5.1",
|
||||
"description": "Generate polished résumés and CVs in HTML, Markdown, LaTeX, MS Word, PDF, plain text, JSON, XML, YAML, smoke signal, and carrier pigeon.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -51,7 +51,7 @@
|
||||
"commander": "^2.9.0",
|
||||
"copy": "^0.1.3",
|
||||
"fresca": "~0.3.0",
|
||||
"fresh-resume-empty": "^0.1.0",
|
||||
"fresh-resume-starter": "^0.1.1",
|
||||
"fresh-themes": "~0.12.0-beta",
|
||||
"fs-extra": "^0.24.0",
|
||||
"handlebars": "^4.0.5",
|
||||
@ -65,7 +65,6 @@
|
||||
"moment": "^2.10.6",
|
||||
"parse-filepath": "^0.6.3",
|
||||
"path-exists": "^2.1.0",
|
||||
"phantom": "^0.8.4",
|
||||
"recursive-readdir-sync": "^1.0.6",
|
||||
"simple-html-tokenizer": "^0.2.0",
|
||||
"slash": "^1.0.0",
|
||||
@ -73,7 +72,6 @@
|
||||
"string.prototype.startswith": "^0.2.0",
|
||||
"underscore": "^1.8.3",
|
||||
"webshot": "^0.16.0",
|
||||
"wkhtmltopdf": "^0.1.5",
|
||||
"word-wrap": "^1.1.0",
|
||||
"xml-escape": "^1.0.0",
|
||||
"yamljs": "^0.2.4"
|
||||
|
@ -6,15 +6,15 @@
|
||||
|
||||
module.exports = [
|
||||
|
||||
{ name: 'html', ext: 'html', gen: new (require('../gen/html-generator'))() },
|
||||
{ name: 'txt', ext: 'txt', gen: new (require('../gen/text-generator'))() },
|
||||
{ name: 'doc', ext: 'doc', fmt: 'xml', gen: new (require('../gen/word-generator'))() },
|
||||
{ name: 'pdf', ext: 'pdf', fmt: 'html', is: false, gen: new (require('../gen/html-pdf-generator'))() },
|
||||
{ name: 'png', ext: 'png', fmt: 'html', is: false, gen: new (require('../gen/html-png-generator'))() },
|
||||
{ name: 'md', ext: 'md', fmt: 'txt', gen: new (require('../gen/markdown-generator'))() },
|
||||
{ name: 'json', ext: 'json', gen: new (require('../gen/json-generator'))() },
|
||||
{ name: 'yml', ext: 'yml', fmt: 'yml', gen: new (require('../gen/json-yaml-generator'))() },
|
||||
{ name: 'latex', ext: 'tex', fmt: 'latex', gen: new (require('../gen/latex-generator'))() }
|
||||
{ name: 'html', ext: 'html', gen: new (require('../generators/html-generator'))() },
|
||||
{ name: 'txt', ext: 'txt', gen: new (require('../generators/text-generator'))() },
|
||||
{ name: 'doc', ext: 'doc', fmt: 'xml', gen: new (require('../generators/word-generator'))() },
|
||||
{ name: 'pdf', ext: 'pdf', fmt: 'html', is: false, gen: new (require('../generators/html-pdf-cli-generator'))() },
|
||||
{ name: 'png', ext: 'png', fmt: 'html', is: false, gen: new (require('../generators/html-png-generator'))() },
|
||||
{ name: 'md', ext: 'md', fmt: 'txt', gen: new (require('../generators/markdown-generator'))() },
|
||||
{ name: 'json', ext: 'json', gen: new (require('../generators/json-generator'))() },
|
||||
{ name: 'yml', ext: 'yml', fmt: 'yml', gen: new (require('../generators/json-yaml-generator'))() },
|
||||
{ name: 'latex', ext: 'tex', fmt: 'latex', gen: new (require('../generators/latex-generator'))() }
|
||||
|
||||
];
|
||||
|
||||
|
@ -3,7 +3,7 @@ Error-handling routines for HackMyResume.
|
||||
@module error-handler.js
|
||||
@license MIT. See LICENSE.md for details.
|
||||
*/
|
||||
|
||||
// TODO: Logging library
|
||||
|
||||
|
||||
(function() {
|
||||
@ -15,6 +15,7 @@ Error-handling routines for HackMyResume.
|
||||
, FS = require('fs')
|
||||
, FCMD = require('../hackmyapi')
|
||||
, PATH = require('path')
|
||||
, WRAP = require('word-wrap')
|
||||
, chalk = require('chalk');
|
||||
|
||||
|
||||
@ -25,104 +26,149 @@ Error-handling routines for HackMyResume.
|
||||
*/
|
||||
var ErrorHandler = module.exports = {
|
||||
|
||||
|
||||
init: function( debug ) {
|
||||
this.debug = debug;
|
||||
},
|
||||
|
||||
err: function( ex, shouldExit ) {
|
||||
var msg = '', exitCode;
|
||||
|
||||
var msg = '', exitCode, log = console.log, showStack = ex.showStack;
|
||||
|
||||
// If the exception has been handled elsewhere and shouldExit is true,
|
||||
// let's get out of here, otherwise silently return.
|
||||
if( ex.handled ) {
|
||||
if( shouldExit )
|
||||
process.exit( exitCode );
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Get an error message -- either a HackMyResume error message or the
|
||||
// exception's associated error message
|
||||
if( ex.fluenterror ){
|
||||
|
||||
switch( ex.fluenterror ) {
|
||||
|
||||
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 = chalk.yellow('Please ') + chalk.yellow.bold('feed me a resume') +
|
||||
chalk.yellow(' in FRESH or JSON Resume format.');
|
||||
break;
|
||||
|
||||
case HACKMYSTATUS.missingCommand:
|
||||
msg = chalk.yellow("Please ") + chalk.yellow.bold("give me a command") +
|
||||
chalk.yellow(" (");
|
||||
|
||||
msg += Object.keys( FCMD.verbs ).map( function(v, idx, ar) {
|
||||
return (idx === ar.length - 1 ? chalk.yellow('or ') : '') +
|
||||
chalk.yellow.bold(v.toUpperCase());
|
||||
}).join( chalk.yellow(', ')) + chalk.yellow(").\n\n");
|
||||
|
||||
msg += chalk.gray(FS.readFileSync( PATH.resolve(__dirname, '../use.txt'), 'utf8' ));
|
||||
break;
|
||||
|
||||
case HACKMYSTATUS.invalidCommand:
|
||||
msg = chalk.yellow('Invalid command: "') + chalk.yellow.bold(ex.attempted) + chalk.yellow('"');
|
||||
break;
|
||||
|
||||
case HACKMYSTATUS.resumeNotFoundAlt:
|
||||
msg = chalk.yellow('Please ') + chalk.yellow.bold('feed me a resume') +
|
||||
chalk.yellow(' in either FRESH or JSON Resume format.');
|
||||
break;
|
||||
|
||||
case HACKMYSTATUS.inputOutputParity:
|
||||
msg = chalk.yellow('Please ') + chalk.yellow.bold('specify an output file name') +
|
||||
chalk.yellow(' for every input file you wish to convert.');
|
||||
break;
|
||||
|
||||
case HACKMYSTATUS.createNameMissing:
|
||||
msg = chalk.yellow('Please ') + chalk.yellow.bold('specify the filename of the resume') +
|
||||
chalk.yellow(' to create.');
|
||||
break;
|
||||
|
||||
case HACKMYSTATUS.wkhtmltopdf:
|
||||
msg = chalk.red.bold('ERROR: PDF generation failed. ') + chalk.red('Make sure wkhtmltopdf is ' +
|
||||
'installed and accessible from your path.');
|
||||
break;
|
||||
|
||||
case HACKMYSTATUS.invalid:
|
||||
msg = chalk.red.bold('ERROR: Validation failed and the --assert option was specified.');
|
||||
break;
|
||||
}
|
||||
var errInfo = get_error_msg( ex );
|
||||
msg = errInfo.msg;
|
||||
exitCode = ex.fluenterror;
|
||||
|
||||
showStack = errInfo.showStack;
|
||||
}
|
||||
else {
|
||||
msg = ex.toString();
|
||||
exitCode = 4;
|
||||
exitCode = -1;
|
||||
// Deal with pesky 'Error:' prefix.
|
||||
var idx = msg.indexOf('Error: ');
|
||||
msg = idx === -1 ? msg : msg.substring( idx + 7 );
|
||||
}
|
||||
|
||||
// Deal with pesky 'Error:' prefix.
|
||||
var idx = msg.indexOf('Error: ');
|
||||
var trimmed = idx === -1 ? msg : msg.substring( idx + 7 );
|
||||
// Log non-HackMyResume-handled errors in red with ERROR prefix. Log HMR
|
||||
// errors as-is.
|
||||
ex.fluenterror ?
|
||||
log( msg.toString() ) :
|
||||
log( chalk.red.bold('ERROR: ' + msg.toString()) );
|
||||
|
||||
// If this is an unhandled error, or a specific class of handled error,
|
||||
// output the error message and stack.
|
||||
if( !ex.fluenterror || ex.fluenterror < 3 ) { // TODO: magic #s
|
||||
console.log( chalk.red.bold('ERROR: ' + trimmed.toString()) );
|
||||
if( ex.code !== 'ENOENT' ) // Don't emit stack for common stuff
|
||||
console.log( chalk.gray(ex.stack) );
|
||||
}
|
||||
else {
|
||||
console.log( trimmed.toString() );
|
||||
}
|
||||
// Selectively show the stack trace
|
||||
if( (ex.stack || (ex.inner && ex.inner.stack)) &&
|
||||
((showStack && ex.code !== 'ENOENT' ) || (this.debug) ))
|
||||
log( chalk.red( ex.stack || ex.stack.inner ) );
|
||||
|
||||
// Let the error code be the process's return code.
|
||||
if( shouldExit || ex.shouldExit )
|
||||
process.exit( exitCode );
|
||||
|
||||
( shouldExit || ex.shouldExit ) && process.exit( exitCode );
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
function get_error_msg( ex ) {
|
||||
|
||||
var msg = '', withStack = false, isError = false;
|
||||
switch( ex.fluenterror ) {
|
||||
|
||||
case HACKMYSTATUS.themeNotFound:
|
||||
msg = formatWarning(
|
||||
chalk.bold("Couldn't find the '" + ex.data + "' theme."),
|
||||
" Please specify the name of a preinstalled FRESH theme " +
|
||||
"or the path to a locally installed FRESH or JSON Resume theme.");
|
||||
break;
|
||||
|
||||
case HACKMYSTATUS.copyCSS:
|
||||
msg = formatWarning("Couldn't copy CSS file to destination folder.");
|
||||
break;
|
||||
|
||||
case HACKMYSTATUS.resumeNotFound:
|
||||
msg = formatWarning('Please ' + chalk.bold('feed me a resume') +
|
||||
' in FRESH or JSON Resume format.');
|
||||
break;
|
||||
|
||||
case HACKMYSTATUS.missingCommand:
|
||||
msg = formatWarning("Please " +chalk.bold("give me a command") + " (");
|
||||
|
||||
msg += Object.keys( FCMD.verbs ).map( function(v, idx, ar) {
|
||||
return (idx === ar.length - 1 ? chalk.yellow('or ') : '') +
|
||||
chalk.yellow.bold(v.toUpperCase());
|
||||
}).join( chalk.yellow(', ')) + chalk.yellow(").\n\n");
|
||||
|
||||
msg += chalk.gray(FS.readFileSync(
|
||||
PATH.resolve(__dirname, '../use.txt'), 'utf8' ));
|
||||
break;
|
||||
|
||||
case HACKMYSTATUS.invalidCommand:
|
||||
msg = formatWarning('Invalid command: "'+chalk.bold(ex.attempted)+'"');
|
||||
break;
|
||||
|
||||
case HACKMYSTATUS.resumeNotFoundAlt:
|
||||
msg = formatWarning('Please ' + chalk.bold('feed me a resume') +
|
||||
' in either FRESH or JSON Resume format.');
|
||||
break;
|
||||
|
||||
case HACKMYSTATUS.inputOutputParity:
|
||||
msg = formatWarning('Please ' +
|
||||
chalk.bold('specify an output file name') +
|
||||
' for every input file you wish to convert.');
|
||||
break;
|
||||
|
||||
case HACKMYSTATUS.createNameMissing:
|
||||
msg = formatWarning('Please ' +
|
||||
chalk.bold('specify the filename of the resume') + ' to create.');
|
||||
break;
|
||||
|
||||
case HACKMYSTATUS.pdfGeneration:
|
||||
msg = formatError(chalk.bold('ERROR: PDF generation failed. ') +
|
||||
'Make sure wkhtmltopdf is installed and accessible from your path.');
|
||||
if( ex.inner ) msg += chalk.red('\n' + ex.inner);
|
||||
withStack = true;
|
||||
break;
|
||||
|
||||
case HACKMYSTATUS.invalid:
|
||||
msg = formatError('Validation failed and the --assert option was ' +
|
||||
'specified.');
|
||||
break;
|
||||
|
||||
case HACKMYSTATUS.invalidFormat:
|
||||
ex.data.forEach(function(d){ msg +=
|
||||
formatWarning('The ' + chalk.bold(ex.theme.name.toUpperCase()) +
|
||||
" theme doesn't support the " + chalk.bold(d.format.toUpperCase()) +
|
||||
" format.\n");
|
||||
});
|
||||
break;
|
||||
|
||||
case HACKMYSTATUS.notOnPath:
|
||||
msg = formatError( ex.engine + " wasn't found on your system path or" +
|
||||
" is inaccessible. PDF not generated." );
|
||||
break;
|
||||
|
||||
}
|
||||
return {
|
||||
msg: msg,
|
||||
withStack: withStack
|
||||
};
|
||||
}
|
||||
|
||||
function formatError( msg ) {
|
||||
return chalk.red.bold( 'ERROR: ' + msg );
|
||||
}
|
||||
|
||||
function formatWarning( brief, msg ) {
|
||||
return chalk.yellow(brief) + chalk.yellow(msg || '');
|
||||
}
|
||||
|
||||
|
||||
}());
|
||||
|
@ -313,10 +313,10 @@ Definition of the FRESHResume class.
|
||||
|
||||
|
||||
/**
|
||||
Get the default (empty) sheet.
|
||||
Get the default (starter) sheet.
|
||||
*/
|
||||
FreshResume.default = function() {
|
||||
return new FreshResume().parseJSON( require('fresh-resume-empty') );
|
||||
return new FreshResume().parseJSON( require('fresh-resume-starter') );
|
||||
};
|
||||
|
||||
|
||||
|
@ -274,7 +274,8 @@ Definition of the JRSResume class.
|
||||
*latest end date of all jobs in the work history*. This last condition is for
|
||||
sheets that have overlapping jobs.
|
||||
*/
|
||||
JRSResume.prototype.duration = function() {
|
||||
JRSResume.prototype.duration = function( unit ) {
|
||||
unit = unit || 'years';
|
||||
if( this.work && this.work.length ) {
|
||||
var careerStart = this.work[ this.work.length - 1].safeStartDate;
|
||||
if ((typeof careerStart === 'string' || careerStart instanceof String) &&
|
||||
@ -283,7 +284,7 @@ Definition of the JRSResume class.
|
||||
var careerLast = _.max( this.work, function( w ) {
|
||||
return w.safeEndDate.unix();
|
||||
}).safeEndDate;
|
||||
return careerLast.diff( careerStart, 'years' );
|
||||
return careerLast.diff( careerStart, unit );
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
@ -16,8 +16,7 @@ Definition of the JRSTheme class.
|
||||
|
||||
|
||||
/**
|
||||
The JRSTheme class is a representation of a JSON Resume
|
||||
theme asset. See also: FRESHTheme.
|
||||
The JRSTheme class is a representation of a JSON Resume theme asset.
|
||||
@class JRSTheme
|
||||
*/
|
||||
function JRSTheme() {
|
||||
@ -41,16 +40,40 @@ Definition of the JRSTheme class.
|
||||
// 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.engine = 'jrs';
|
||||
|
||||
// Create theme formats (HTML and PDF). Just add the bare minimum mix of
|
||||
// properties necessary to allow JSON Resume themes to share a rendering
|
||||
// path with FRESH themes.
|
||||
this.formats = {
|
||||
html: { title:'html', outFormat:'html', ext:'html' }
|
||||
html: { outFormat: 'html', files: [
|
||||
{
|
||||
action: 'transform',
|
||||
render: this.render,
|
||||
major: true,
|
||||
ext: 'html',
|
||||
css: null
|
||||
}
|
||||
]},
|
||||
pdf: { outFormat: 'pdf', files: [
|
||||
{
|
||||
action: 'transform',
|
||||
render: this.render,
|
||||
major: true,
|
||||
ext: 'pdf',
|
||||
css: null
|
||||
}
|
||||
]}
|
||||
};
|
||||
}
|
||||
else {
|
||||
throw { fluenterror: 10 };
|
||||
throw { fluenterror: HACKMYSTATUS.missingPackageJSON };
|
||||
}
|
||||
|
||||
return this;
|
||||
|
@ -116,8 +116,10 @@ Definition of the ResumeFactory class.
|
||||
ex.handled = true;
|
||||
}
|
||||
|
||||
if( opts.throw ) throw ex;
|
||||
else return {
|
||||
// FS.readFileSync failed
|
||||
if( !rawData || opts.throw ) throw ex;
|
||||
|
||||
return {
|
||||
error: ex,
|
||||
raw: rawData,
|
||||
file: fileName
|
||||
|
@ -16,9 +16,11 @@ Status codes for HackMyResume.
|
||||
resumeNotFoundAlt: 6,
|
||||
inputOutputParity: 7,
|
||||
createNameMissing: 8,
|
||||
wkhtmltopdf: 9,
|
||||
pdfgeneration: 9,
|
||||
missingPackageJSON: 10,
|
||||
invalid: 11
|
||||
invalid: 11,
|
||||
invalidFormat: 12,
|
||||
notOnPath: 13
|
||||
};
|
||||
|
||||
}());
|
||||
|
@ -1,81 +0,0 @@
|
||||
/**
|
||||
Definition of the HtmlPdfGenerator class.
|
||||
@module html-pdf-generator.js
|
||||
@license MIT. See LICENSE.md for details.
|
||||
*/
|
||||
|
||||
(function() {
|
||||
|
||||
var TemplateGenerator = require('./template-generator')
|
||||
, FS = require('fs-extra')
|
||||
, HTML = require( 'html' );
|
||||
|
||||
/**
|
||||
An HTML-driven PDF resume generator for HackMyResume.
|
||||
*/
|
||||
var HtmlPdfGenerator = module.exports = TemplateGenerator.extend({
|
||||
|
||||
init: function() {
|
||||
this._super( 'pdf', 'html' );
|
||||
},
|
||||
|
||||
/**
|
||||
Generate the binary PDF.
|
||||
*/
|
||||
onBeforeSave: function( info ) {
|
||||
engines[ info.opts.pdf || 'wkhtmltopdf' ]
|
||||
.call( this, info.mk, info.outputFile );
|
||||
return null; // halt further processing
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
var engines = {
|
||||
/**
|
||||
Generate a PDF from HTML using wkhtmltopdf.
|
||||
*/
|
||||
wkhtmltopdf: function(markup, fOut) {
|
||||
var wk;
|
||||
try {
|
||||
wk = require('wkhtmltopdf');
|
||||
wk( markup, { pageSize: 'letter' } )
|
||||
.pipe( FS.createWriteStream( fOut ) );
|
||||
}
|
||||
catch(ex) {
|
||||
// { [Error: write EPIPE] code: 'EPIPE', errno: 'EPIPE', ... }
|
||||
// { [Error: ENOENT] }
|
||||
throw { fluenterror: this.codes.wkhtmltopdf };
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
Generate a PDF from HTML using Phantom.
|
||||
*/
|
||||
phantom: function( markup, fOut ) {
|
||||
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 );
|
||||
ph.exit();
|
||||
});
|
||||
},
|
||||
{ dnodeOpts: { weak: false } } );
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
}());
|
139
src/generators/html-pdf-cli-generator.js
Normal file
139
src/generators/html-pdf-cli-generator.js
Normal file
@ -0,0 +1,139 @@
|
||||
/**
|
||||
Definition of the HtmlPdfCLIGenerator class.
|
||||
@module html-pdf-generator.js
|
||||
@license MIT. See LICENSE.md for details.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
(function() {
|
||||
|
||||
|
||||
|
||||
var TemplateGenerator = require('./template-generator')
|
||||
, FS = require('fs-extra')
|
||||
, HTML = require( 'html' )
|
||||
, PATH = require('path')
|
||||
, SLASH = require('slash');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
An HTML-driven PDF resume generator for HackMyResume. Talks to Phantom,
|
||||
wkhtmltopdf, and other PDF engines over a CLI (command-line interface).
|
||||
If an engine isn't installed for a particular platform, error out gracefully.
|
||||
*/
|
||||
var HtmlPdfCLIGenerator = module.exports = TemplateGenerator.extend({
|
||||
|
||||
|
||||
|
||||
init: function() {
|
||||
this._super( 'pdf', 'html' );
|
||||
},
|
||||
|
||||
|
||||
|
||||
/**
|
||||
Generate the binary PDF.
|
||||
*/
|
||||
onBeforeSave: function( info ) {
|
||||
|
||||
try {
|
||||
var safe_eng = info.opts.pdf || 'wkhtmltopdf';
|
||||
engines[ safe_eng ].call( this, info.mk, info.outputFile );
|
||||
return null; // halt further processing
|
||||
}
|
||||
catch(ex) {
|
||||
// { [Error: write EPIPE] code: 'EPIPE', errno: 'EPIPE', ... }
|
||||
// { [Error: ENOENT] }
|
||||
throw ( ex.inner && ex.inner.code === 'ENOENT' ) ?
|
||||
{ fluenterror: this.codes.notOnPath, engine: ex.cmd, stack: ex.inner.stack } :
|
||||
{ fluenterror: this.codes.pdfGeneration, inner: ex.inner, stack: ex.inner.stack };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
// TODO: Move each engine to a separate module
|
||||
var engines = {
|
||||
|
||||
|
||||
|
||||
/**
|
||||
Generate a PDF from HTML using wkhtmltopdf's CLI interface.
|
||||
Spawns a child process with `wkhtmltopdf <source> <target>`. wkhtmltopdf
|
||||
must be installed and path-accessible.
|
||||
TODO: If HTML generation has run, reuse that output
|
||||
TODO: Local web server to ease wkhtmltopdf rendering
|
||||
*/
|
||||
wkhtmltopdf: function(markup, fOut) {
|
||||
|
||||
// Save the markup to a temporary file
|
||||
var tempFile = fOut.replace(/\.pdf$/i, '.pdf.html');
|
||||
FS.writeFileSync( tempFile, markup, 'utf8' );
|
||||
|
||||
var spawn = require('child_process').spawnSync;
|
||||
var info = spawn('wkhtmltopdf', [
|
||||
tempFile, fOut
|
||||
]);
|
||||
if( info.error ) {
|
||||
throw {
|
||||
cmd: 'wkhtmltopdf',
|
||||
inner: info.error
|
||||
};
|
||||
}
|
||||
|
||||
// child.stdout.on('data', function(chunk) {
|
||||
// // output will be here in chunks
|
||||
// });
|
||||
|
||||
// or if you want to send output elsewhere
|
||||
//child.stdout.pipe(dest);
|
||||
},
|
||||
|
||||
|
||||
|
||||
/**
|
||||
Generate a PDF from HTML using Phantom's CLI interface.
|
||||
Spawns a child process with `phantomjs <script> <source> <target>`. Phantom
|
||||
must be installed and path-accessible.
|
||||
TODO: If HTML generation has run, reuse that output
|
||||
TODO: Local web server to ease Phantom rendering
|
||||
*/
|
||||
phantom: function( markup, fOut ) {
|
||||
|
||||
// Save the markup to a temporary file
|
||||
var tempFile = fOut.replace(/\.pdf$/i, '.pdf.html');
|
||||
FS.writeFileSync( tempFile, markup, 'utf8' );
|
||||
|
||||
var scriptPath = SLASH( PATH.relative( process.cwd(),
|
||||
PATH.resolve( __dirname, '../utils/rasterize.js' ) ) );
|
||||
var sourcePath = SLASH( PATH.relative( process.cwd(), tempFile) );
|
||||
var destPath = SLASH( PATH.relative( process.cwd(), fOut) );
|
||||
|
||||
var spawn = require('child_process').spawnSync;
|
||||
var info = spawn('phantomjs', [ scriptPath, sourcePath, destPath ]);
|
||||
if( info.error ) {
|
||||
throw {
|
||||
cmd: 'phantomjs',
|
||||
inner: info.error
|
||||
};
|
||||
}
|
||||
|
||||
// child.stdout.on('data', function(chunk) {
|
||||
// // output will be here in chunks
|
||||
// });
|
||||
//
|
||||
// // or if you want to send output elsewhere
|
||||
// child.stdout.pipe(dest);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
}());
|
@ -4,25 +4,37 @@ Definition of the HtmlPngGenerator class.
|
||||
@module html-png-generator.js
|
||||
*/
|
||||
|
||||
|
||||
|
||||
(function() {
|
||||
|
||||
|
||||
|
||||
var TemplateGenerator = require('./template-generator')
|
||||
, FS = require('fs-extra')
|
||||
, HTML = require( 'html' );
|
||||
|
||||
|
||||
|
||||
/**
|
||||
An HTML-based PNG resume generator for HackMyResume.
|
||||
*/
|
||||
var HtmlPngGenerator = module.exports = TemplateGenerator.extend({
|
||||
|
||||
|
||||
|
||||
init: function() {
|
||||
this._super( 'png', 'html' );
|
||||
},
|
||||
|
||||
|
||||
|
||||
invoke: function( rez, themeMarkup, cssInfo, opts ) {
|
||||
//return YAML.stringify( JSON.parse( rez.stringify() ), Infinity, 2 );
|
||||
// TODO: Not currently called or callable.
|
||||
},
|
||||
|
||||
|
||||
|
||||
generate: function( rez, f, opts ) {
|
||||
var htmlResults = opts.targets.filter(function(t){
|
||||
return t.fmt.outFormat === 'html';
|
||||
@ -30,18 +42,25 @@ Definition of the HtmlPngGenerator class.
|
||||
var htmlFile = htmlResults[0].final.files.filter(function(fl){
|
||||
return fl.info.ext === 'html';
|
||||
});
|
||||
png(htmlFile[0].data, f);
|
||||
png( htmlFile[0].data, f );
|
||||
}
|
||||
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
/**
|
||||
Generate a PNG from HTML.
|
||||
*/
|
||||
function png( markup, fOut ) {
|
||||
// TODO: Which Webshot syntax?
|
||||
// require('webshot')( markup , { encoding: 'binary', siteType: 'html' } )
|
||||
// .pipe( FS.createWriteStream( fOut ) );
|
||||
require('webshot')( markup , fOut, { siteType: 'html' }, function(err) { } );
|
||||
}
|
||||
|
||||
|
||||
|
||||
}());
|
@ -1,5 +1,5 @@
|
||||
/**
|
||||
Definition of the TemplateGenerator class.
|
||||
Definition of the TemplateGenerator class. TODO: Refactor
|
||||
@license MIT. See LICENSE.md for details.
|
||||
@module template-generator.js
|
||||
*/
|
||||
@ -145,6 +145,10 @@ Definition of the TemplateGenerator class.
|
||||
|
||||
var thisFilePath;
|
||||
|
||||
if( theme.engine === 'jrs' ) {
|
||||
file.info.orgPath = '';
|
||||
}
|
||||
|
||||
if( file.info.action === 'transform' ) {
|
||||
thisFilePath = PATH.join( outFolder, file.info.orgPath );
|
||||
try {
|
||||
@ -175,7 +179,8 @@ Definition of the TemplateGenerator class.
|
||||
FS.copySync( file.info.path, thisFilePath );
|
||||
}
|
||||
catch(ex) {
|
||||
console.log(ex);
|
||||
ex.showStack = true;
|
||||
require('../core/error-handler').err( ex );
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -208,7 +213,7 @@ Definition of the TemplateGenerator class.
|
||||
single: function( json, jst, format, cssInfo, opts, theme ) {
|
||||
this.opts.freezeBreaks && ( jst = freeze(jst) );
|
||||
|
||||
var eng = require( '../eng/' + theme.engine + '-generator' );
|
||||
var eng = require( '../renderers/' + theme.engine + '-generator' );
|
||||
var result = eng.generate( json, jst, format, cssInfo, opts, theme );
|
||||
|
||||
this.opts.freezeBreaks && ( result = unfreeze(result) );
|
||||
@ -274,8 +279,8 @@ Definition of the TemplateGenerator class.
|
||||
theme );
|
||||
}
|
||||
catch(ex) {
|
||||
console.log(ex);
|
||||
throw ex;
|
||||
ex.showStack = true;
|
||||
require('../core/error-handler').err( ex );
|
||||
}
|
||||
}
|
||||
|
@ -1,24 +1,29 @@
|
||||
/**
|
||||
External API surface for HackMyResume.
|
||||
@license MIT. Copyright (c) 2015 James M. Devlin / FluentDesk.
|
||||
@license MIT. See LICENSE.md for details.
|
||||
@module hackmyapi.js
|
||||
*/
|
||||
|
||||
|
||||
|
||||
(function() {
|
||||
|
||||
var v = {
|
||||
build: require('./verbs/generate'),
|
||||
analyze: require('./verbs/analyze'),
|
||||
validate: require('./verbs/validate'),
|
||||
convert: require('./verbs/convert'),
|
||||
new: require('./verbs/create')
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
The formal HackMyResume API.
|
||||
*/
|
||||
var HackMyAPI = module.exports = {
|
||||
verbs: v,
|
||||
verbs: {
|
||||
build: require('./verbs/build'),
|
||||
analyze: require('./verbs/analyze'),
|
||||
validate: require('./verbs/validate'),
|
||||
convert: require('./verbs/convert'),
|
||||
new: require('./verbs/create')
|
||||
},
|
||||
alias: {
|
||||
generate: v.build,
|
||||
create: v.new
|
||||
generate: require('./verbs/build'),
|
||||
create: require('./verbs/create')
|
||||
},
|
||||
options: require('./core/default-options'),
|
||||
formats: require('./core/default-formats'),
|
||||
@ -28,16 +33,18 @@ External API surface for HackMyResume.
|
||||
FRESHTheme: require('./core/fresh-theme'),
|
||||
JRSTheme: require('./core/jrs-theme'),
|
||||
FluentDate: require('./core/fluent-date'),
|
||||
HtmlGenerator: require('./gen/html-generator'),
|
||||
TextGenerator: require('./gen/text-generator'),
|
||||
HtmlPdfGenerator: require('./gen/html-pdf-generator'),
|
||||
WordGenerator: require('./gen/word-generator'),
|
||||
MarkdownGenerator: require('./gen/markdown-generator'),
|
||||
JsonGenerator: require('./gen/json-generator'),
|
||||
YamlGenerator: require('./gen/yaml-generator'),
|
||||
JsonYamlGenerator: require('./gen/json-yaml-generator'),
|
||||
LaTeXGenerator: require('./gen/latex-generator'),
|
||||
HtmlPngGenerator: require('./gen/html-png-generator')
|
||||
HtmlGenerator: require('./generators/html-generator'),
|
||||
TextGenerator: require('./generators/text-generator'),
|
||||
HtmlPdfCliGenerator: require('./generators/html-pdf-cli-generator'),
|
||||
WordGenerator: require('./generators/word-generator'),
|
||||
MarkdownGenerator: require('./generators/markdown-generator'),
|
||||
JsonGenerator: require('./generators/json-generator'),
|
||||
YamlGenerator: require('./generators/yaml-generator'),
|
||||
JsonYamlGenerator: require('./generators/json-yaml-generator'),
|
||||
LaTeXGenerator: require('./generators/latex-generator'),
|
||||
HtmlPngGenerator: require('./generators/html-png-generator')
|
||||
};
|
||||
|
||||
|
||||
|
||||
}());
|
||||
|
24
src/index.js
24
src/index.js
@ -49,7 +49,8 @@ function main() {
|
||||
.option('-o --opts <optionsFile>', 'Path to a .hackmyrc options file')
|
||||
.option('-s --silent', 'Run in silent mode')
|
||||
.option('--no-color', 'Disable colors')
|
||||
.option('--color', 'Enable colors');
|
||||
.option('--color', 'Enable colors')
|
||||
.option('-d --debug', 'Enable diagnostics', false);
|
||||
//.usage('COMMAND <sources> [TO <targets>]');
|
||||
|
||||
// Create the NEW command
|
||||
@ -152,7 +153,8 @@ function initialize() {
|
||||
|
||||
// Override the .missingArgument behavior
|
||||
Command.prototype.missingArgument = function(name) {
|
||||
throw { fluenterror: HACKMYSTATUS.resumeNotFound };
|
||||
if( this.name() !== 'new' )
|
||||
throw { fluenterror: HACKMYSTATUS.resumeNotFound };
|
||||
};
|
||||
|
||||
// Override the .helpInformation behavior
|
||||
@ -171,6 +173,7 @@ Invoke a HackMyResume verb.
|
||||
*/
|
||||
function execVerb( src, dst, opts, log ) {
|
||||
loadOptions.call( this, opts );
|
||||
require('./core/error-handler').init( _opts.debug );
|
||||
HMR.verbs[ this.name() ].call( null, src, dst, _opts, log );
|
||||
}
|
||||
|
||||
@ -179,23 +182,24 @@ function execVerb( src, dst, opts, log ) {
|
||||
/**
|
||||
Initialize HackMyResume options.
|
||||
*/
|
||||
function loadOptions( opts ) {
|
||||
function loadOptions( o ) {
|
||||
|
||||
opts.opts = this.parent.opts;
|
||||
o.opts = this.parent.opts;
|
||||
|
||||
// Load the specified options file (if any) and apply options
|
||||
if( opts.opts && String.is( opts.opts )) {
|
||||
var json = safeLoadJSON( PATH.relative( process.cwd(), opts.opts ) );
|
||||
json && ( opts = EXTEND( true, opts, json ) );
|
||||
if( o.opts && String.is( o.opts )) {
|
||||
var json = safeLoadJSON( PATH.relative( process.cwd(), o.opts ) );
|
||||
json && ( o = EXTEND( true, o, json ) );
|
||||
if( !json ) {
|
||||
throw safeLoadJSON.error;
|
||||
}
|
||||
}
|
||||
|
||||
// Merge in command-line options
|
||||
opts = EXTEND( true, opts, this.opts() );
|
||||
opts.silent = this.parent.silent;
|
||||
_opts = opts;
|
||||
o = EXTEND( true, o, this.opts() );
|
||||
o.silent = this.parent.silent;
|
||||
o.debug = this.parent.debug;
|
||||
_opts = o;
|
||||
}
|
||||
|
||||
|
||||
|
@ -86,15 +86,19 @@ Employment gap analysis for HackMyResume.
|
||||
// When the reference count is > 0, the candidate is employed. When the
|
||||
// reference count reaches 2, the candidate is overlapped.
|
||||
|
||||
var num_gaps = 0, ref_count = 0, total_gap_days = 0, total_work_days = 0
|
||||
, gap_start;
|
||||
var num_gaps = 0, ref_count = 0, total_gap_days = 0, gap_start;
|
||||
|
||||
new_e.forEach( function(point) {
|
||||
|
||||
var inc = point[0] === 'start' ? 1 : -1;
|
||||
ref_count += inc;
|
||||
|
||||
// If the ref count just reached 0, start a new GAP
|
||||
if( ref_count === 0 ) {
|
||||
coverage.gaps.push( { start: point[1], end: null });
|
||||
}
|
||||
|
||||
// If the ref count reached 1 by rising, end the last GAP
|
||||
else if( ref_count === 1 && inc === 1 ) {
|
||||
var lastGap = _.last( coverage.gaps );
|
||||
if( lastGap ) {
|
||||
@ -103,9 +107,13 @@ Employment gap analysis for HackMyResume.
|
||||
total_gap_days += lastGap.duration;
|
||||
}
|
||||
}
|
||||
|
||||
// If the ref count reaches 2 by rising, start a new OVERLAP
|
||||
else if( ref_count === 2 && inc === 1 ) {
|
||||
coverage.overlaps.push( { start: point[1], end: null });
|
||||
}
|
||||
|
||||
// If the ref count reaches 1 by falling, end the last OVERLAP
|
||||
else if( ref_count === 1 && inc === -1 ) {
|
||||
var lastOver = _.last( coverage.overlaps );
|
||||
if( lastOver ) {
|
||||
@ -114,32 +122,39 @@ Employment gap analysis for HackMyResume.
|
||||
if( lastOver.duration === 0 ) {
|
||||
coverage.overlaps.pop();
|
||||
}
|
||||
total_work_days += lastOver.duration;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// It's possible that the last overlap didn't have an explicit .end date.
|
||||
// If so, set the end date to the present date and compute the overlap
|
||||
// It's possible that the last gap/overlap didn't have an explicit .end
|
||||
// date.If so, set the end date to the present date and compute the
|
||||
// duration normally.
|
||||
if( coverage.overlaps.length ) {
|
||||
if( !_.last( coverage.overlaps ).end ) {
|
||||
var l = _.last( coverage.overlaps );
|
||||
l.end = moment();
|
||||
l.duration = l.end.diff( l.start, 'days' );
|
||||
var o = _.last( coverage.overlaps );
|
||||
if( o && !o.end ) {
|
||||
o.end = moment();
|
||||
o.duration = o.end.diff( o.start, 'days' );
|
||||
}
|
||||
}
|
||||
if( coverage.gaps.length ) {
|
||||
var g = _.last( coverage.gaps );
|
||||
if( g && !g.end ) {
|
||||
g.end = moment();
|
||||
g.duration = g.end.diff( g.start, 'days' );
|
||||
}
|
||||
}
|
||||
|
||||
// Package data for return to the client
|
||||
var tdur = rez.duration('days');
|
||||
var dur = {
|
||||
total: rez.duration('days'),
|
||||
work: total_work_days,
|
||||
total: tdur,
|
||||
work: tdur - total_gap_days,
|
||||
gaps: total_gap_days
|
||||
};
|
||||
coverage.pct = ( dur.total > 0 && dur.work > 0 ) ?
|
||||
((((dur.total - dur.gaps) / dur.total) * 100)).toFixed(1) + '%' :
|
||||
'???';
|
||||
coverage.duration = dur;
|
||||
|
||||
return coverage;
|
||||
}
|
||||
|
||||
|
@ -41,7 +41,7 @@ Definition of the HandlebarsGenerator class.
|
||||
( format === 'doc' ) && (encData = json.xmlify());
|
||||
|
||||
// Compile and run the Handlebars template.
|
||||
var template = HANDLEBARS.compile(jst);
|
||||
var template = HANDLEBARS.compile(jst, { strict: false, assumeObjects: false });
|
||||
return template({
|
||||
r: encData,
|
||||
RAW: json,
|
||||
@ -61,30 +61,31 @@ Definition of the HandlebarsGenerator class.
|
||||
|
||||
|
||||
function registerPartials(format, theme) {
|
||||
if( format !== 'html' && format != 'doc' )
|
||||
return;
|
||||
if( format === 'html' || format === 'doc' ) {
|
||||
|
||||
// Locate the global partials folder
|
||||
var partialsFolder = PATH.join(
|
||||
parsePath( require.resolve('fresh-themes') ).dirname,
|
||||
'/partials/',
|
||||
format
|
||||
);
|
||||
// Locate the global partials folder
|
||||
var partialsFolder = PATH.join(
|
||||
parsePath( require.resolve('fresh-themes') ).dirname,
|
||||
'/partials/',
|
||||
format
|
||||
);
|
||||
|
||||
// Register global partials in the /partials folder
|
||||
// TODO: Only do this once per HMR invocation.
|
||||
_.each( READFILES( partialsFolder, function(error){ }), function( el ) {
|
||||
var pathInfo = parsePath( el );
|
||||
var name = SLASH( PATH.relative( partialsFolder, el )
|
||||
.replace(/\.html$|\.xml$/, '') );
|
||||
if( pathInfo.dirname.endsWith('section') ) {
|
||||
name = SLASH(name.replace(/\.html$|\.xml$/, ''));
|
||||
}
|
||||
var tplData = FS.readFileSync( el, 'utf8' );
|
||||
var compiledTemplate = HANDLEBARS.compile( tplData );
|
||||
HANDLEBARS.registerPartial( name, compiledTemplate );
|
||||
theme.partialsInitialized = true;
|
||||
});
|
||||
// Register global partials in the /partials folder
|
||||
// TODO: Only do this once per HMR invocation.
|
||||
_.each( READFILES( partialsFolder, function(error){ }), function( el ) {
|
||||
var pathInfo = parsePath( el );
|
||||
var name = SLASH( PATH.relative( partialsFolder, el )
|
||||
.replace(/\.html$|\.xml$/, '') );
|
||||
if( pathInfo.dirname.endsWith('section') ) {
|
||||
name = SLASH(name.replace(/\.html$|\.xml$/, ''));
|
||||
}
|
||||
var tplData = FS.readFileSync( el, 'utf8' );
|
||||
var compiledTemplate = HANDLEBARS.compile( tplData );
|
||||
HANDLEBARS.registerPartial( name, compiledTemplate );
|
||||
theme.partialsInitialized = true;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
// Register theme-specific partials
|
||||
_.each( theme.partials, function( el ) {
|
76
src/renderers/jrs-generator.js
Normal file
76
src/renderers/jrs-generator.js
Normal file
@ -0,0 +1,76 @@
|
||||
/**
|
||||
Definition of the JRSGenerator class.
|
||||
@license MIT. See LICENSE.md for details.
|
||||
@module jrs-generator.js
|
||||
*/
|
||||
|
||||
|
||||
|
||||
(function() {
|
||||
|
||||
|
||||
|
||||
var _ = require('underscore')
|
||||
, HANDLEBARS = require('handlebars')
|
||||
, FS = require('fs')
|
||||
, registerHelpers = require('./handlebars-helpers')
|
||||
, PATH = require('path')
|
||||
, parsePath = require('parse-filepath')
|
||||
, READFILES = require('recursive-readdir-sync')
|
||||
, SLASH = require('slash')
|
||||
, MD = require('marked');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
Perform template-based resume generation for JSON Resume themes.
|
||||
@class JRSGenerator
|
||||
*/
|
||||
var JRSGenerator = module.exports = {
|
||||
|
||||
|
||||
|
||||
|
||||
generate: function( json, jst, format, cssInfo, opts, theme ) {
|
||||
|
||||
// JSON Resume themes don't have a specific structure, so the safest thing
|
||||
// to do is copy all files from source to dest.
|
||||
// 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);
|
||||
// // }
|
||||
// });
|
||||
|
||||
// Disable JRS theme chatter (console.log, console.error, etc.)
|
||||
var off = ['log', 'error', 'dir'], org = off.map(function(c){
|
||||
var ret = console[c]; console[c] = function(){}; return ret;
|
||||
});
|
||||
|
||||
// Freeze and render
|
||||
var rezHtml = theme.render( json.harden() );
|
||||
|
||||
// Turn logging back on
|
||||
off.forEach(function(c, idx){ console[c] = org[idx]; });
|
||||
|
||||
// Unfreeze and apply Markdown
|
||||
rezHtml = rezHtml.replace( /@@@@~.*?~@@@@/gm, function(val){
|
||||
return MDIN( val.replace( /~@@@@/gm,'' ).replace( /@@@@~/gm,'' ) );
|
||||
});
|
||||
|
||||
return rezHtml;
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
function MDIN(txt) { // TODO: Move this
|
||||
return MD(txt || '' ).replace(/^\s*<p>|<\/p>\s*$/gi, '');
|
||||
}
|
||||
|
||||
|
||||
}());
|
56
src/utils/rasterize.js
Normal file
56
src/utils/rasterize.js
Normal file
@ -0,0 +1,56 @@
|
||||
// Exemplar script for generating documents with Phantom.js.
|
||||
// https://raw.githubusercontent.com/ariya/phantomjs/master/examples/rasterize.js
|
||||
|
||||
(function() {
|
||||
|
||||
"use strict";
|
||||
var page = require('webpage').create(),
|
||||
system = require('system'),
|
||||
address, output, size;
|
||||
|
||||
if (system.args.length < 3 || system.args.length > 5) {
|
||||
console.log('Usage: rasterize.js URL filename [paperwidth*paperheight|paperformat] [zoom]');
|
||||
console.log(' paper (pdf output) examples: "5in*7.5in", "10cm*20cm", "A4", "Letter"');
|
||||
console.log(' image (png/jpg output) examples: "1920px" entire page, window width 1920px');
|
||||
console.log(' "800px*600px" window, clipped to 800x600');
|
||||
phantom.exit(1);
|
||||
} else {
|
||||
address = system.args[1];
|
||||
output = system.args[2];
|
||||
page.viewportSize = { width: 600, height: 600 };
|
||||
if (system.args.length > 3 && system.args[2].substr(-4) === ".pdf") {
|
||||
size = system.args[3].split('*');
|
||||
page.paperSize = size.length === 2 ? { width: size[0], height: size[1], margin: '0px' }
|
||||
: { format: system.args[3], orientation: 'portrait', margin: '1cm' };
|
||||
} else if (system.args.length > 3 && system.args[3].substr(-2) === "px") {
|
||||
size = system.args[3].split('*');
|
||||
if (size.length === 2) {
|
||||
pageWidth = parseInt(size[0], 10);
|
||||
pageHeight = parseInt(size[1], 10);
|
||||
page.viewportSize = { width: pageWidth, height: pageHeight };
|
||||
page.clipRect = { top: 0, left: 0, width: pageWidth, height: pageHeight };
|
||||
} else {
|
||||
console.log("size:", system.args[3]);
|
||||
pageWidth = parseInt(system.args[3], 10);
|
||||
pageHeight = parseInt(pageWidth * 3/4, 10); // it's as good an assumption as any
|
||||
console.log ("pageHeight:",pageHeight);
|
||||
page.viewportSize = { width: pageWidth, height: pageHeight };
|
||||
}
|
||||
}
|
||||
if (system.args.length > 4) {
|
||||
page.zoomFactor = system.args[4];
|
||||
}
|
||||
page.open(address, function (status) {
|
||||
if (status !== 'success') {
|
||||
console.log('Unable to load the address!');
|
||||
phantom.exit(1);
|
||||
} else {
|
||||
window.setTimeout(function () {
|
||||
page.render(output);
|
||||
phantom.exit();
|
||||
}, 200);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}());
|
@ -15,6 +15,7 @@ Implementation of the 'generate' verb for HackMyResume.
|
||||
, MD = require('marked')
|
||||
, MKDIRP = require('mkdirp')
|
||||
, EXTEND = require('../utils/extend')
|
||||
, HACKMYSTATUS = require('../core/status-codes')
|
||||
, parsePath = require('parse-filepath')
|
||||
, _opts = require('../core/default-options')
|
||||
, FluentTheme = require('../core/fresh-theme')
|
||||
@ -29,15 +30,6 @@ Implementation of the 'generate' verb for HackMyResume.
|
||||
|
||||
|
||||
|
||||
/**
|
||||
Handle an exception.
|
||||
*/
|
||||
function error( ex ) {
|
||||
throw ex;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
Given a source resume in FRESH or JRS format, a destination resume path, and a
|
||||
theme file, generate 0..N resumes in the desired formats.
|
||||
@ -48,29 +40,18 @@ Implementation of the 'generate' verb for HackMyResume.
|
||||
*/
|
||||
function build( src, dst, opts, logger, errHandler ) {
|
||||
|
||||
// Housekeeping
|
||||
//_opts = extend( true, _opts, opts );
|
||||
_log = logger || console.log;
|
||||
_err = errHandler || error;
|
||||
_opts.theme = (opts.theme && opts.theme.toLowerCase().trim())|| 'modern';
|
||||
_opts.prettify = opts.prettify === true ? _opts.prettify : false;
|
||||
_opts.css = opts.css || 'embed';
|
||||
_opts.pdf = opts.pdf;
|
||||
_opts.wrap = opts.wrap || 60;
|
||||
_opts.stitles = opts.sectionTitles;
|
||||
_opts.tips = opts.tips;
|
||||
//_opts.noTips = opts.noTips;
|
||||
|
||||
// If two or more files are passed to the GENERATE command and the TO
|
||||
// keyword is omitted, the last file specifies the output file.
|
||||
if( src.length > 1 && ( !dst || !dst.length ) ) {
|
||||
dst.push( src.pop() );
|
||||
}
|
||||
prep( src, dst, opts, logger, errHandler );
|
||||
|
||||
// Load the theme...we do this first because the theme choice (FRESH or
|
||||
// JSON Resume) determines what format we'll convert the resume to.
|
||||
var tFolder = verify_theme( _opts.theme );
|
||||
var theme = load_theme( tFolder );
|
||||
var tFolder = verifyTheme( _opts.theme );
|
||||
var theme = loadTheme( tFolder );
|
||||
|
||||
// Check for invalid outputs
|
||||
var inv = verifyOutputs( dst, theme );
|
||||
if( inv && inv.length ) {
|
||||
throw {fluenterror: HACKMYSTATUS.invalidFormat, data: inv, theme: theme};
|
||||
}
|
||||
|
||||
// Load input resumes...
|
||||
if( !src || !src.length ) { throw { fluenterror: 3 }; }
|
||||
@ -134,46 +115,73 @@ Implementation of the 'generate' verb for HackMyResume.
|
||||
|
||||
|
||||
|
||||
/**
|
||||
Prepare for a BUILD run.
|
||||
*/
|
||||
function prep( src, dst, opts, logger, errHandler ) {
|
||||
|
||||
// Housekeeping
|
||||
_log = logger || console.log;
|
||||
_err = errHandler || error;
|
||||
|
||||
// Cherry-pick options //_opts = extend( true, _opts, opts );
|
||||
_opts.theme = (opts.theme && opts.theme.toLowerCase().trim()) || 'modern';
|
||||
_opts.prettify = opts.prettify === true;
|
||||
_opts.css = opts.css || 'embed';
|
||||
_opts.pdf = opts.pdf;
|
||||
_opts.wrap = opts.wrap || 60;
|
||||
_opts.stitles = opts.sectionTitles;
|
||||
_opts.tips = opts.tips;
|
||||
_opts.noTips = opts.noTips;
|
||||
_opts.debug = opts.debug;
|
||||
|
||||
// If two or more files are passed to the GENERATE command and the TO
|
||||
// keyword is omitted, the last file specifies the output file.
|
||||
( src.length > 1 && ( !dst || !dst.length ) ) && dst.push( src.pop() );
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
Generate a single target resume such as "out/rez.html" or "out/rez.doc".
|
||||
TODO: Refactor.
|
||||
@param targInfo Information for the target resume.
|
||||
@param theme A FRESHTheme or JRSTheme object.
|
||||
@returns
|
||||
*/
|
||||
function single( targInfo, theme, finished ) {
|
||||
|
||||
function MDIN(txt) { // TODO: Move this
|
||||
return MD(txt || '' ).replace(/^\s*<p>|<\/p>\s*$/gi, '');
|
||||
}
|
||||
|
||||
try {
|
||||
if( !targInfo.fmt ) {
|
||||
return;
|
||||
}
|
||||
var f = targInfo.file
|
||||
, fType = targInfo.fmt.outFormat
|
||||
, fName = PATH.basename(f, '.' + fType)
|
||||
, theFormat;
|
||||
|
||||
var suffix = '';
|
||||
if( targInfo.fmt.outFormat === 'pdf' ) {
|
||||
if( _opts.pdf ) {
|
||||
if( _opts.pdf !== 'none' ) {
|
||||
suffix = chalk.green(' (with ' + _opts.pdf + ')');
|
||||
}
|
||||
else {
|
||||
_log( chalk.gray('Skipping ') +
|
||||
chalk.white.bold(
|
||||
pad(targInfo.fmt.outFormat.toUpperCase(),4,null,pad.RIGHT)) +
|
||||
chalk.gray(' resume') + suffix + chalk.green(': ') +
|
||||
chalk.white( PATH.relative(process.cwd(), f )) );
|
||||
return;
|
||||
}
|
||||
var suffix = '';
|
||||
if( targInfo.fmt.outFormat === 'pdf' ) {
|
||||
if( _opts.pdf ) {
|
||||
if( _opts.pdf !== 'none' ) {
|
||||
suffix = chalk.green(' (with ' + _opts.pdf + ')');
|
||||
}
|
||||
else {
|
||||
_log( chalk.gray('Skipping ') +
|
||||
chalk.white.bold(
|
||||
pad(targInfo.fmt.outFormat.toUpperCase(),4,null,pad.RIGHT)) +
|
||||
chalk.gray(' resume') + suffix + chalk.green(': ') +
|
||||
chalk.white( PATH.relative(process.cwd(), f )) );
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_log( chalk.green('Generating ') +
|
||||
chalk.green.bold(
|
||||
pad(targInfo.fmt.outFormat.toUpperCase(),4,null,pad.RIGHT)) +
|
||||
chalk.green(' resume') + suffix + chalk.green(': ') +
|
||||
chalk.green.bold( PATH.relative(process.cwd(), f )) );
|
||||
_log( chalk.green('Generating ') +
|
||||
chalk.green.bold(
|
||||
pad(targInfo.fmt.outFormat.toUpperCase(),4,null,pad.RIGHT)) +
|
||||
chalk.green(' resume') + suffix + chalk.green(': ') +
|
||||
chalk.green.bold( PATH.relative(process.cwd(), f )) );
|
||||
|
||||
// If targInfo.fmt.files exists, this format is backed by a document.
|
||||
// Fluent/FRESH themes are handled here.
|
||||
@ -185,8 +193,8 @@ Implementation of the 'generate' verb for HackMyResume.
|
||||
return theFormat.gen.generate( rez, f, _opts );
|
||||
}
|
||||
|
||||
// Otherwise this is either a) a JSON Resume theme or b) an ad-hoc format
|
||||
// (JSON, YML, or PNG) that every theme gets "for free".
|
||||
//Otherwise this is an ad-hoc format (JSON, YML, or PNG) that every theme
|
||||
// gets "for free".
|
||||
else {
|
||||
|
||||
theFormat = _fmts.filter( function(fmt) {
|
||||
@ -196,43 +204,7 @@ Implementation of the 'generate' verb for HackMyResume.
|
||||
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( /@@@@~.*?~@@@@/gm, 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 );
|
||||
}
|
||||
return theFormat.gen.generate( rez, f, _opts );
|
||||
}
|
||||
}
|
||||
catch( ex ) {
|
||||
@ -242,6 +214,27 @@ Implementation of the 'generate' verb for HackMyResume.
|
||||
|
||||
|
||||
|
||||
/**
|
||||
Ensure that user-specified outputs/targets are valid.
|
||||
*/
|
||||
function verifyOutputs( targets, theme ) {
|
||||
|
||||
return _.reject(
|
||||
targets.map( function( t ) {
|
||||
var pathInfo = parsePath( t );
|
||||
return {
|
||||
format: pathInfo.extname.substr(1)
|
||||
};
|
||||
}),
|
||||
function(t) {
|
||||
return t.format === 'all' || theme.hasFormat( t.format );
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
Expand output files. For example, "foo.all" should be expanded to
|
||||
["foo.html", "foo.doc", "foo.pdf", "etc"].
|
||||
@ -301,10 +294,11 @@ Implementation of the 'generate' verb for HackMyResume.
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
Verify the specified theme name/path.
|
||||
*/
|
||||
function verify_theme( themeNameOrPath ) {
|
||||
function verifyTheme( themeNameOrPath ) {
|
||||
var tFolder = PATH.join(
|
||||
parsePath ( require.resolve('fresh-themes') ).dirname,
|
||||
'/themes/',
|
||||
@ -323,9 +317,10 @@ Implementation of the 'generate' verb for HackMyResume.
|
||||
|
||||
|
||||
/**
|
||||
Load the specified theme.
|
||||
Load the specified theme, which could be either a FRESH theme or a JSON Resume
|
||||
theme.
|
||||
*/
|
||||
function load_theme( tFolder ) {
|
||||
function loadTheme( tFolder ) {
|
||||
|
||||
// Create a FRESH or JRS theme object
|
||||
var theTheme = _opts.theme.indexOf('jsonresume-theme-') > -1 ?
|
||||
@ -339,6 +334,21 @@ Implementation of the 'generate' verb for HackMyResume.
|
||||
|
||||
|
||||
|
||||
/**
|
||||
Handle an exception. Placeholder.
|
||||
*/
|
||||
function error( ex ) {
|
||||
throw ex;
|
||||
}
|
||||
|
||||
|
||||
|
||||
function MDIN(txt) { // TODO: Move this
|
||||
return MD(txt || '' ).replace(/^\s*<p>|<\/p>\s*$/gi, '');
|
||||
}
|
||||
|
||||
|
||||
|
||||
module.exports = build;
|
||||
|
||||
|
@ -8,14 +8,15 @@ Implementation of the 'create' verb for HackMyResume.
|
||||
|
||||
var MKDIRP = require('mkdirp')
|
||||
, PATH = require('path')
|
||||
, chalk = require('chalk');
|
||||
, chalk = require('chalk')
|
||||
, HACKMYSTATUS = require('../core/status-codes');
|
||||
|
||||
/**
|
||||
Create a new empty resume in either FRESH or JRS format.
|
||||
*/
|
||||
module.exports = function create( src, dst, opts, logger ) {
|
||||
var _log = logger || console.log;
|
||||
if( !src || !src.length ) throw { fluenterror: 8 };
|
||||
if( !src || !src.length ) throw { fluenterror: HACKMYSTATUS.createNameMissing };
|
||||
src.forEach( function( t ) {
|
||||
var safeFormat = opts.format.toUpperCase();
|
||||
_log(chalk.green('Creating new ') + chalk.green.bold(safeFormat) +
|
||||
|
@ -1,3 +1,6 @@
|
||||
/**
|
||||
@module test-cli.js
|
||||
*/
|
||||
|
||||
var chai = require('chai')
|
||||
, expect = chai.expect
|
||||
@ -6,7 +9,8 @@ var chai = require('chai')
|
||||
, _ = require('underscore')
|
||||
, FRESHResume = require('../src/core/fresh-resume')
|
||||
, FCMD = require( '../src/hackmyapi')
|
||||
, validator = require('is-my-json-valid');
|
||||
, validator = require('is-my-json-valid')
|
||||
, EXTEND = require('../src/utils/extend');
|
||||
|
||||
chai.config.includeStack = false;
|
||||
|
||||
@ -14,9 +18,6 @@ describe('Testing CLI interface', function () {
|
||||
|
||||
var _sheet;
|
||||
|
||||
function logMsg() {
|
||||
|
||||
}
|
||||
|
||||
var opts = {
|
||||
format: 'FRESH',
|
||||
@ -31,46 +32,63 @@ describe('Testing CLI interface', function () {
|
||||
silent: true
|
||||
};
|
||||
|
||||
run( 'new', ['test/sandbox/new-fresh-resume.json'], [], opts, ' (FRESH format)' );
|
||||
run( 'new', ['test/sandbox/new-jrs-resume.json'], [], opts2, ' (JRS format)' );
|
||||
run( 'new', ['test/sandbox/new-1.json', 'test/sandbox/new-2.json', 'test/sandbox/new-3.json'], [], opts, ' (multiple FRESH resumes)' );
|
||||
run( 'new', ['test/sandbox/new-jrs-1.json', 'test/sandbox/new-jrs-2.json', 'test/sandbox/new-jrs-3.json'], [], opts, ' (multiple JRS resumes)' );
|
||||
fail( 'new', [], [], opts, " (when a filename isn't specified)" );
|
||||
var sb = 'test/sandbox/';
|
||||
var ft = 'node_modules/fresh-test-resumes/src/';
|
||||
|
||||
run( 'validate', ['node_modules/fresh-test-resumes/src/jane-fullstacker.fresh.json'], [], opts, ' (jane-q-fullstacker|FRESH)' );
|
||||
run( 'validate', ['node_modules/fresh-test-resumes/src/johnny-trouble.fresh.json'], [], opts, ' (johnny-trouble|FRESH)' );
|
||||
fail( 'validate', ['test/sandbox/new-fresh-resume.json'], [], opts, ' (new-fresh-resume|FRESH)' );
|
||||
run( 'validate', ['test/resumes/jrs-0.0.0/richard-hendriks.json'], [], opts2, ' (richard-hendriks.json|JRS)' );
|
||||
run( 'validate', ['test/resumes/jrs-0.0.0/jane-incomplete.json'], [], opts2, ' (jane-incomplete.json|JRS)' );
|
||||
fail( 'validate', ['test/sandbox/new-1.json','test/sandbox/new-jrs-resume.json','test/sandbox/new-1.json', 'test/sandbox/new-2.json', 'test/sandbox/new-3.json'], [], opts, ' (5|BOTH)' );
|
||||
[
|
||||
|
||||
run( 'analyze', ['node_modules/fresh-test-resumes/src/jane-fullstacker.json'], [], opts, ' (jane-q-fullstacker|FRESH)' );
|
||||
run( 'analyze', ['test/resumes/jrs-0.0.0/richard-hendriks.json'], [], opts2, ' (richard-hendriks|JRS)' );
|
||||
[ 'new', [sb + 'new-fresh-resume.json'], [], opts, ' (FRESH format)' ],
|
||||
[ 'new', [sb + 'new-jrs-resume.json'], [], opts2, ' (JRS format)'],
|
||||
[ 'new', [sb + 'new-1.json', sb + 'new-2.json', sb + 'new-3.json'], [], opts, ' (multiple FRESH resumes)' ],
|
||||
[ 'new', [sb + 'new-jrs-1.json', sb + 'new-jrs-2.json', sb + 'new-jrs-3.json'], [], opts, ' (multiple JRS resumes)' ],
|
||||
[ '!new', [], [], opts, " (when a filename isn't specified)" ],
|
||||
|
||||
[ 'validate', [ft + 'jane-fullstacker.fresh.json'], [], opts, ' (jane-q-fullstacker|FRESH)' ],
|
||||
[ 'validate', [ft + 'johnny-trouble.fresh.json'], [], opts, ' (johnny-trouble|FRESH)' ],
|
||||
[ 'validate', [sb + 'new-fresh-resume.json'], [], opts, ' (new-fresh-resume|FRESH)' ],
|
||||
[ 'validate', ['test/resumes/jrs-0.0.0/richard-hendriks.json'], [], opts2, ' (richard-hendriks.json|JRS)' ],
|
||||
[ 'validate', ['test/resumes/jrs-0.0.0/jane-incomplete.json'], [], opts2, ' (jane-incomplete.json|JRS)' ],
|
||||
[ 'validate', [sb + 'new-1.json', sb + 'new-jrs-resume.json', sb + 'new-1.json', sb + 'new-2.json', sb + 'new-3.json'], [], opts, ' (5|BOTH)' ],
|
||||
|
||||
[ 'analyze', [ft + 'jane-fullstacker.fresh.json'], [], opts, ' (jane-q-fullstacker|FRESH)' ],
|
||||
[ 'analyze', ['test/resumes/jrs-0.0.0/richard-hendriks.json'], [], opts2, ' (richard-hendriks|JRS)' ],
|
||||
|
||||
[ 'build', [ ft + 'jane-fullstacker.fresh.json', ft + 'override/jane-fullstacker-override.fresh.json' ], [ sb + 'merged/jane-fullstacker-gamedev.fresh.all'], opts, ' (jane-q-fullstacker w/ override|FRESH)' ],
|
||||
[ '!build', [ ft + 'jane-fullstacker.fresh.json'], [ sb + 'shouldnt-exist.pdf' ], EXTEND(true, opts, { theme: 'awesome' }), ' (jane-q-fullstacker + Awesome + PDF|FRESH)' ]
|
||||
|
||||
].forEach( function(a) {
|
||||
|
||||
run.apply( /* The players of */ null, a );
|
||||
|
||||
});
|
||||
|
||||
run( 'build',
|
||||
[ 'node_modules/fresh-test-resumes/src/jane-fullstacker.fresh.json',
|
||||
'node_modules/fresh-test-resumes/src/override/jane-fullstacker-override.fresh.json' ],
|
||||
[ 'test/sandbox/merged/jane-fullstacker-gamedev.fresh.all'], opts, ' (jane-q-fullstacker w/ override|FRESH)'
|
||||
);
|
||||
|
||||
function run( verb, src, dst, opts, msg ) {
|
||||
msg = msg || '.';
|
||||
it( 'The ' + verb.toUpperCase() + ' command should SUCCEED' + msg, function () {
|
||||
var shouldSucceed = true;
|
||||
if( verb[0] === '!' ) {
|
||||
verb = verb.substr(1);
|
||||
shouldSucceed = false;
|
||||
}
|
||||
it( 'The ' + verb.toUpperCase() + ' command should ' + (shouldSucceed ? ' SUCCEED' : ' FAIL') + msg, function () {
|
||||
function runIt() {
|
||||
FCMD.verbs[verb]( src, dst, opts, opts.silent ? logMsg : function(msg){ msg = msg || ''; console.log(msg); } );
|
||||
try {
|
||||
FCMD.verbs[verb]( src, dst, opts, opts.silent ?
|
||||
function(){} : function(msg){ msg = msg || ''; console.log(msg); } );
|
||||
}
|
||||
catch(ex) {
|
||||
console.error(ex);
|
||||
console.error(ex.stack);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
runIt.should.not.Throw();
|
||||
if( shouldSucceed )
|
||||
runIt.should.not.Throw();
|
||||
else
|
||||
runIt.should.Throw();
|
||||
});
|
||||
}
|
||||
|
||||
function fail( verb, src, dst, opts, msg ) {
|
||||
msg = msg || '.';
|
||||
it( 'The ' + verb.toUpperCase() + ' command should FAIL' + msg, function () {
|
||||
function runIt() {
|
||||
FCMD.verbs[verb]( src, dst, opts, logMsg );
|
||||
}
|
||||
runIt.should.Throw();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
|
Reference in New Issue
Block a user