mirror of
https://github.com/JuanCanham/HackMyResume.git
synced 2025-05-15 01:57:08 +01:00
Compare commits
41 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 |
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,
|
1. **Generate** HTML, Markdown, LaTeX, MS Word, PDF, plain text, JSON, XML,
|
||||||
YAML, print, smoke signal, carrier pigeon, and other arbitrary-format resumes
|
YAML, print, smoke signal, carrier pigeon, and other arbitrary-format resumes
|
||||||
and CVs, from a single source of truth—without violating DRY.
|
and CVs, from a single source of truth—without violating DRY.
|
||||||
2. **Convert** resumes between [FRESH][fresca] and [JSON Resume][6] formats.
|
2. **Analyze** your resume for keyword density, gaps/overlaps, and other
|
||||||
3. **Validate** resumes against either format.
|
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,
|
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
|
## Features
|
||||||
|
|
||||||
- OS X, Linux, and Windows.
|
- OS X, Linux, and Windows.
|
||||||
- Choose from dozens of FRESH or JSON Resume themes.
|
- Choose from dozens of FRESH or JSON Resume themes.
|
||||||
- Private, local-only resume authoring and analysis.
|
- 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.
|
- Store your resume data as a durable, versionable JSON or YAML document.
|
||||||
- Generate polished resumes in multiple formats without violating [DRY][dry].
|
- Generate polished resumes in multiple formats without violating [DRY][dry].
|
||||||
- Output to HTML, Markdown, LaTeX, PDF, MS Word, JSON, YAML, plain text, or XML.
|
- Output to HTML, Markdown, LaTeX, PDF, MS Word, JSON, YAML, plain text, or XML.
|
||||||
- Validate resumes against the FRESH or JSON Resume schema.
|
- Validate resumes against the FRESH or JSON Resume schema.
|
||||||
- Support for multiple input and output resumes.
|
- Support for multiple input and output resumes.
|
||||||
|
- Convert between FRESH and JSON Resume 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 / weekly. Contributions welcome.
|
- Updated daily / weekly. Contributions are [welcome](CONTRIBUTING.md).
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
@ -46,13 +50,22 @@ Install the latest stable version of HackMyResume with NPM:
|
|||||||
[sudo] npm install hackmyresume -g
|
[sudo] npm install hackmyresume -g
|
||||||
```
|
```
|
||||||
|
|
||||||
To install the latest bleeding-edge version (updated daily):
|
Alternately, install the latest bleeding-edge version (updated daily):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
[sudo] npm install hacksalot/hackmyresume#dev -g
|
[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
|
## 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.doc`, `out/resume.html`, `out/resume.txt`, `out/resume.pdf`, and
|
||||||
`out/resume.json`.
|
`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
|
### Validating
|
||||||
|
|
||||||
HackMyResume can also validate your resumes against either the [FRESH /
|
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
|
autodetect the format (FRESH or JRS) of each input resume and convert it to the
|
||||||
other format (JRS or FRESH).
|
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
|
### Prettifying
|
||||||
|
|
||||||
HackMyResume applies [js-beautify][10]-style HTML prettification by default to
|
HackMyResume applies [js-beautify][10]-style HTML prettification by default to
|
||||||
HTML-formatted resumes. To disable prettification, the `--nopretty` or `-n` flag
|
HTML-formatted resumes. To disable prettification, the `--no-prettify` or `-n`
|
||||||
can be used:
|
flag can be used:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
hackmyresume BUILD resume.json out.all --nopretty
|
hackmyresume BUILD resume.json out.all --no-prettify
|
||||||
```
|
```
|
||||||
|
|
||||||
### Silent Mode
|
### Silent Mode
|
||||||
@ -318,6 +442,16 @@ hackmyresume BUILD resume.json -o someFile.all -s
|
|||||||
hackmyresume BUILD resume.json -o someFile.all --silent
|
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
|
## Contributing
|
||||||
|
|
||||||
HackMyResume is a community-driven free and open source project under the MIT
|
HackMyResume is a community-driven free and open source project under the MIT
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "hackmyresume",
|
"name": "hackmyresume",
|
||||||
"version": "1.4.1",
|
"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.",
|
"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",
|
||||||
@ -65,7 +65,6 @@
|
|||||||
"moment": "^2.10.6",
|
"moment": "^2.10.6",
|
||||||
"parse-filepath": "^0.6.3",
|
"parse-filepath": "^0.6.3",
|
||||||
"path-exists": "^2.1.0",
|
"path-exists": "^2.1.0",
|
||||||
"phantom": "^0.8.4",
|
|
||||||
"recursive-readdir-sync": "^1.0.6",
|
"recursive-readdir-sync": "^1.0.6",
|
||||||
"simple-html-tokenizer": "^0.2.0",
|
"simple-html-tokenizer": "^0.2.0",
|
||||||
"slash": "^1.0.0",
|
"slash": "^1.0.0",
|
||||||
@ -73,7 +72,6 @@
|
|||||||
"string.prototype.startswith": "^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",
|
|
||||||
"word-wrap": "^1.1.0",
|
"word-wrap": "^1.1.0",
|
||||||
"xml-escape": "^1.0.0",
|
"xml-escape": "^1.0.0",
|
||||||
"yamljs": "^0.2.4"
|
"yamljs": "^0.2.4"
|
||||||
|
@ -6,15 +6,15 @@
|
|||||||
|
|
||||||
module.exports = [
|
module.exports = [
|
||||||
|
|
||||||
{ name: 'html', ext: 'html', gen: new (require('../gen/html-generator'))() },
|
{ name: 'html', ext: 'html', gen: new (require('../generators/html-generator'))() },
|
||||||
{ name: 'txt', ext: 'txt', gen: new (require('../gen/text-generator'))() },
|
{ name: 'txt', ext: 'txt', gen: new (require('../generators/text-generator'))() },
|
||||||
{ name: 'doc', ext: 'doc', fmt: 'xml', gen: new (require('../gen/word-generator'))() },
|
{ name: 'doc', ext: 'doc', fmt: 'xml', gen: new (require('../generators/word-generator'))() },
|
||||||
{ name: 'pdf', ext: 'pdf', fmt: 'html', is: false, gen: new (require('../gen/html-pdf-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('../gen/html-png-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('../gen/markdown-generator'))() },
|
{ name: 'md', ext: 'md', fmt: 'txt', gen: new (require('../generators/markdown-generator'))() },
|
||||||
{ name: 'json', ext: 'json', gen: new (require('../gen/json-generator'))() },
|
{ name: 'json', ext: 'json', gen: new (require('../generators/json-generator'))() },
|
||||||
{ name: 'yml', ext: 'yml', fmt: 'yml', gen: new (require('../gen/json-yaml-generator'))() },
|
{ name: 'yml', ext: 'yml', fmt: 'yml', gen: new (require('../generators/json-yaml-generator'))() },
|
||||||
{ name: 'latex', ext: 'tex', fmt: 'latex', gen: new (require('../gen/latex-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
|
@module error-handler.js
|
||||||
@license MIT. See LICENSE.md for details.
|
@license MIT. See LICENSE.md for details.
|
||||||
*/
|
*/
|
||||||
|
// TODO: Logging library
|
||||||
|
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
@ -15,6 +15,7 @@ Error-handling routines for HackMyResume.
|
|||||||
, FS = require('fs')
|
, FS = require('fs')
|
||||||
, FCMD = require('../hackmyapi')
|
, FCMD = require('../hackmyapi')
|
||||||
, PATH = require('path')
|
, PATH = require('path')
|
||||||
|
, WRAP = require('word-wrap')
|
||||||
, chalk = require('chalk');
|
, chalk = require('chalk');
|
||||||
|
|
||||||
|
|
||||||
@ -25,104 +26,149 @@ Error-handling routines for HackMyResume.
|
|||||||
*/
|
*/
|
||||||
var ErrorHandler = module.exports = {
|
var ErrorHandler = module.exports = {
|
||||||
|
|
||||||
|
init: function( debug ) {
|
||||||
|
this.debug = debug;
|
||||||
|
},
|
||||||
|
|
||||||
err: function( ex, shouldExit ) {
|
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( ex.handled ) {
|
||||||
if( shouldExit )
|
if( shouldExit )
|
||||||
process.exit( exitCode );
|
process.exit( exitCode );
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get an error message -- either a HackMyResume error message or the
|
||||||
|
// exception's associated error message
|
||||||
if( ex.fluenterror ){
|
if( ex.fluenterror ){
|
||||||
|
var errInfo = get_error_msg( ex );
|
||||||
switch( ex.fluenterror ) {
|
msg = errInfo.msg;
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
exitCode = ex.fluenterror;
|
exitCode = ex.fluenterror;
|
||||||
|
showStack = errInfo.showStack;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
msg = ex.toString();
|
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.
|
// Log non-HackMyResume-handled errors in red with ERROR prefix. Log HMR
|
||||||
var idx = msg.indexOf('Error: ');
|
// errors as-is.
|
||||||
var trimmed = idx === -1 ? msg : msg.substring( idx + 7 );
|
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,
|
// Selectively show the stack trace
|
||||||
// output the error message and stack.
|
if( (ex.stack || (ex.inner && ex.inner.stack)) &&
|
||||||
if( !ex.fluenterror || ex.fluenterror < 3 ) { // TODO: magic #s
|
((showStack && ex.code !== 'ENOENT' ) || (this.debug) ))
|
||||||
console.log( chalk.red.bold('ERROR: ' + trimmed.toString()) );
|
log( chalk.red( ex.stack || ex.stack.inner ) );
|
||||||
if( ex.code !== 'ENOENT' ) // Don't emit stack for common stuff
|
|
||||||
console.log( chalk.gray(ex.stack) );
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
console.log( trimmed.toString() );
|
|
||||||
}
|
|
||||||
|
|
||||||
// Let the error code be the process's return code.
|
// Let the error code be the process's return code.
|
||||||
if( shouldExit || ex.shouldExit )
|
( shouldExit || ex.shouldExit ) && process.exit( exitCode );
|
||||||
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 || '');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}());
|
}());
|
||||||
|
@ -274,7 +274,8 @@ Definition of the JRSResume class.
|
|||||||
*latest end date of all jobs in the work history*. This last condition is for
|
*latest end date of all jobs in the work history*. This last condition is for
|
||||||
sheets that have overlapping jobs.
|
sheets that have overlapping jobs.
|
||||||
*/
|
*/
|
||||||
JRSResume.prototype.duration = function() {
|
JRSResume.prototype.duration = function( unit ) {
|
||||||
|
unit = unit || 'years';
|
||||||
if( this.work && this.work.length ) {
|
if( this.work && this.work.length ) {
|
||||||
var careerStart = this.work[ this.work.length - 1].safeStartDate;
|
var careerStart = this.work[ this.work.length - 1].safeStartDate;
|
||||||
if ((typeof careerStart === 'string' || careerStart instanceof String) &&
|
if ((typeof careerStart === 'string' || careerStart instanceof String) &&
|
||||||
@ -283,7 +284,7 @@ Definition of the JRSResume class.
|
|||||||
var careerLast = _.max( this.work, function( w ) {
|
var careerLast = _.max( this.work, function( w ) {
|
||||||
return w.safeEndDate.unix();
|
return w.safeEndDate.unix();
|
||||||
}).safeEndDate;
|
}).safeEndDate;
|
||||||
return careerLast.diff( careerStart, 'years' );
|
return careerLast.diff( careerStart, unit );
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
};
|
};
|
||||||
|
@ -16,8 +16,7 @@ Definition of the JRSTheme class.
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
The JRSTheme class is a representation of a JSON Resume
|
The JRSTheme class is a representation of a JSON Resume theme asset.
|
||||||
theme asset. See also: FRESHTheme.
|
|
||||||
@class JRSTheme
|
@class JRSTheme
|
||||||
*/
|
*/
|
||||||
function JRSTheme() {
|
function JRSTheme() {
|
||||||
@ -41,16 +40,40 @@ Definition of the JRSTheme class.
|
|||||||
// Open and parse the theme's package.json file.
|
// Open and parse the theme's package.json file.
|
||||||
var pkgJsonPath = PATH.join( thFolder, 'package.json' );
|
var pkgJsonPath = PATH.join( thFolder, 'package.json' );
|
||||||
if( pathExists( pkgJsonPath )) {
|
if( pathExists( pkgJsonPath )) {
|
||||||
|
|
||||||
var thApi = require( thFolder )
|
var thApi = require( thFolder )
|
||||||
, thPkg = require( pkgJsonPath );
|
, thPkg = require( pkgJsonPath );
|
||||||
|
|
||||||
this.name = thPkg.name;
|
this.name = thPkg.name;
|
||||||
this.render = (thApi && thApi.render) || undefined;
|
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 = {
|
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 {
|
else {
|
||||||
throw { fluenterror: 10 };
|
throw { fluenterror: HACKMYSTATUS.missingPackageJSON };
|
||||||
}
|
}
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
|
@ -116,8 +116,10 @@ Definition of the ResumeFactory class.
|
|||||||
ex.handled = true;
|
ex.handled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if( opts.throw ) throw ex;
|
// FS.readFileSync failed
|
||||||
else return {
|
if( !rawData || opts.throw ) throw ex;
|
||||||
|
|
||||||
|
return {
|
||||||
error: ex,
|
error: ex,
|
||||||
raw: rawData,
|
raw: rawData,
|
||||||
file: fileName
|
file: fileName
|
||||||
|
@ -16,9 +16,11 @@ Status codes for HackMyResume.
|
|||||||
resumeNotFoundAlt: 6,
|
resumeNotFoundAlt: 6,
|
||||||
inputOutputParity: 7,
|
inputOutputParity: 7,
|
||||||
createNameMissing: 8,
|
createNameMissing: 8,
|
||||||
wkhtmltopdf: 9,
|
pdfgeneration: 9,
|
||||||
missingPackageJSON: 10,
|
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
|
@module html-png-generator.js
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var TemplateGenerator = require('./template-generator')
|
var TemplateGenerator = require('./template-generator')
|
||||||
, FS = require('fs-extra')
|
, FS = require('fs-extra')
|
||||||
, HTML = require( 'html' );
|
, HTML = require( 'html' );
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
An HTML-based PNG resume generator for HackMyResume.
|
An HTML-based PNG resume generator for HackMyResume.
|
||||||
*/
|
*/
|
||||||
var HtmlPngGenerator = module.exports = TemplateGenerator.extend({
|
var HtmlPngGenerator = module.exports = TemplateGenerator.extend({
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
init: function() {
|
init: function() {
|
||||||
this._super( 'png', 'html' );
|
this._super( 'png', 'html' );
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
invoke: function( rez, themeMarkup, cssInfo, opts ) {
|
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 ) {
|
generate: function( rez, f, opts ) {
|
||||||
var htmlResults = opts.targets.filter(function(t){
|
var htmlResults = opts.targets.filter(function(t){
|
||||||
return t.fmt.outFormat === 'html';
|
return t.fmt.outFormat === 'html';
|
||||||
@ -30,18 +42,25 @@ Definition of the HtmlPngGenerator class.
|
|||||||
var htmlFile = htmlResults[0].final.files.filter(function(fl){
|
var htmlFile = htmlResults[0].final.files.filter(function(fl){
|
||||||
return fl.info.ext === 'html';
|
return fl.info.ext === 'html';
|
||||||
});
|
});
|
||||||
png(htmlFile[0].data, f);
|
png( htmlFile[0].data, f );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Generate a PNG from HTML.
|
Generate a PNG from HTML.
|
||||||
*/
|
*/
|
||||||
function png( markup, fOut ) {
|
function png( markup, fOut ) {
|
||||||
|
// TODO: Which Webshot syntax?
|
||||||
// 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) { } );
|
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.
|
@license MIT. See LICENSE.md for details.
|
||||||
@module template-generator.js
|
@module template-generator.js
|
||||||
*/
|
*/
|
||||||
@ -145,6 +145,10 @@ Definition of the TemplateGenerator class.
|
|||||||
|
|
||||||
var thisFilePath;
|
var thisFilePath;
|
||||||
|
|
||||||
|
if( theme.engine === 'jrs' ) {
|
||||||
|
file.info.orgPath = '';
|
||||||
|
}
|
||||||
|
|
||||||
if( file.info.action === 'transform' ) {
|
if( file.info.action === 'transform' ) {
|
||||||
thisFilePath = PATH.join( outFolder, file.info.orgPath );
|
thisFilePath = PATH.join( outFolder, file.info.orgPath );
|
||||||
try {
|
try {
|
||||||
@ -175,7 +179,8 @@ Definition of the TemplateGenerator class.
|
|||||||
FS.copySync( file.info.path, thisFilePath );
|
FS.copySync( file.info.path, thisFilePath );
|
||||||
}
|
}
|
||||||
catch(ex) {
|
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 ) {
|
single: function( json, jst, format, cssInfo, opts, theme ) {
|
||||||
this.opts.freezeBreaks && ( jst = freeze(jst) );
|
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 );
|
var result = eng.generate( json, jst, format, cssInfo, opts, theme );
|
||||||
|
|
||||||
this.opts.freezeBreaks && ( result = unfreeze(result) );
|
this.opts.freezeBreaks && ( result = unfreeze(result) );
|
||||||
@ -274,8 +279,8 @@ Definition of the TemplateGenerator class.
|
|||||||
theme );
|
theme );
|
||||||
}
|
}
|
||||||
catch(ex) {
|
catch(ex) {
|
||||||
console.log(ex);
|
ex.showStack = true;
|
||||||
throw ex;
|
require('../core/error-handler').err( ex );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,24 +1,29 @@
|
|||||||
/**
|
/**
|
||||||
External API surface for HackMyResume.
|
External API surface for HackMyResume.
|
||||||
@license MIT. Copyright (c) 2015 James M. Devlin / FluentDesk.
|
@license MIT. See LICENSE.md for details.
|
||||||
@module hackmyapi.js
|
@module hackmyapi.js
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
(function() {
|
(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 = {
|
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: {
|
alias: {
|
||||||
generate: v.build,
|
generate: require('./verbs/build'),
|
||||||
create: v.new
|
create: require('./verbs/create')
|
||||||
},
|
},
|
||||||
options: require('./core/default-options'),
|
options: require('./core/default-options'),
|
||||||
formats: require('./core/default-formats'),
|
formats: require('./core/default-formats'),
|
||||||
@ -28,16 +33,18 @@ External API surface for HackMyResume.
|
|||||||
FRESHTheme: require('./core/fresh-theme'),
|
FRESHTheme: require('./core/fresh-theme'),
|
||||||
JRSTheme: require('./core/jrs-theme'),
|
JRSTheme: require('./core/jrs-theme'),
|
||||||
FluentDate: require('./core/fluent-date'),
|
FluentDate: require('./core/fluent-date'),
|
||||||
HtmlGenerator: require('./gen/html-generator'),
|
HtmlGenerator: require('./generators/html-generator'),
|
||||||
TextGenerator: require('./gen/text-generator'),
|
TextGenerator: require('./generators/text-generator'),
|
||||||
HtmlPdfGenerator: require('./gen/html-pdf-generator'),
|
HtmlPdfCliGenerator: require('./generators/html-pdf-cli-generator'),
|
||||||
WordGenerator: require('./gen/word-generator'),
|
WordGenerator: require('./generators/word-generator'),
|
||||||
MarkdownGenerator: require('./gen/markdown-generator'),
|
MarkdownGenerator: require('./generators/markdown-generator'),
|
||||||
JsonGenerator: require('./gen/json-generator'),
|
JsonGenerator: require('./generators/json-generator'),
|
||||||
YamlGenerator: require('./gen/yaml-generator'),
|
YamlGenerator: require('./generators/yaml-generator'),
|
||||||
JsonYamlGenerator: require('./gen/json-yaml-generator'),
|
JsonYamlGenerator: require('./generators/json-yaml-generator'),
|
||||||
LaTeXGenerator: require('./gen/latex-generator'),
|
LaTeXGenerator: require('./generators/latex-generator'),
|
||||||
HtmlPngGenerator: require('./gen/html-png-generator')
|
HtmlPngGenerator: require('./generators/html-png-generator')
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}());
|
}());
|
||||||
|
21
src/index.js
21
src/index.js
@ -49,7 +49,8 @@ function main() {
|
|||||||
.option('-o --opts <optionsFile>', 'Path to a .hackmyrc options file')
|
.option('-o --opts <optionsFile>', 'Path to a .hackmyrc options file')
|
||||||
.option('-s --silent', 'Run in silent mode')
|
.option('-s --silent', 'Run in silent mode')
|
||||||
.option('--no-color', 'Disable colors')
|
.option('--no-color', 'Disable colors')
|
||||||
.option('--color', 'Enable colors');
|
.option('--color', 'Enable colors')
|
||||||
|
.option('-d --debug', 'Enable diagnostics', false);
|
||||||
//.usage('COMMAND <sources> [TO <targets>]');
|
//.usage('COMMAND <sources> [TO <targets>]');
|
||||||
|
|
||||||
// Create the NEW command
|
// Create the NEW command
|
||||||
@ -172,6 +173,7 @@ Invoke a HackMyResume verb.
|
|||||||
*/
|
*/
|
||||||
function execVerb( src, dst, opts, log ) {
|
function execVerb( src, dst, opts, log ) {
|
||||||
loadOptions.call( this, opts );
|
loadOptions.call( this, opts );
|
||||||
|
require('./core/error-handler').init( _opts.debug );
|
||||||
HMR.verbs[ this.name() ].call( null, src, dst, _opts, log );
|
HMR.verbs[ this.name() ].call( null, src, dst, _opts, log );
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -180,23 +182,24 @@ function execVerb( src, dst, opts, log ) {
|
|||||||
/**
|
/**
|
||||||
Initialize HackMyResume options.
|
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
|
// Load the specified options file (if any) and apply options
|
||||||
if( opts.opts && String.is( opts.opts )) {
|
if( o.opts && String.is( o.opts )) {
|
||||||
var json = safeLoadJSON( PATH.relative( process.cwd(), opts.opts ) );
|
var json = safeLoadJSON( PATH.relative( process.cwd(), o.opts ) );
|
||||||
json && ( opts = EXTEND( true, opts, json ) );
|
json && ( o = EXTEND( true, o, json ) );
|
||||||
if( !json ) {
|
if( !json ) {
|
||||||
throw safeLoadJSON.error;
|
throw safeLoadJSON.error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge in command-line options
|
// Merge in command-line options
|
||||||
opts = EXTEND( true, opts, this.opts() );
|
o = EXTEND( true, o, this.opts() );
|
||||||
opts.silent = this.parent.silent;
|
o.silent = this.parent.silent;
|
||||||
_opts = opts;
|
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
|
// When the reference count is > 0, the candidate is employed. When the
|
||||||
// reference count reaches 2, the candidate is overlapped.
|
// reference count reaches 2, the candidate is overlapped.
|
||||||
|
|
||||||
var num_gaps = 0, ref_count = 0, total_gap_days = 0, total_work_days = 0
|
var num_gaps = 0, ref_count = 0, total_gap_days = 0, gap_start;
|
||||||
, gap_start;
|
|
||||||
|
|
||||||
new_e.forEach( function(point) {
|
new_e.forEach( function(point) {
|
||||||
|
|
||||||
var inc = point[0] === 'start' ? 1 : -1;
|
var inc = point[0] === 'start' ? 1 : -1;
|
||||||
ref_count += inc;
|
ref_count += inc;
|
||||||
|
|
||||||
|
// If the ref count just reached 0, start a new GAP
|
||||||
if( ref_count === 0 ) {
|
if( ref_count === 0 ) {
|
||||||
coverage.gaps.push( { start: point[1], end: null });
|
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 ) {
|
else if( ref_count === 1 && inc === 1 ) {
|
||||||
var lastGap = _.last( coverage.gaps );
|
var lastGap = _.last( coverage.gaps );
|
||||||
if( lastGap ) {
|
if( lastGap ) {
|
||||||
@ -103,9 +107,13 @@ Employment gap analysis for HackMyResume.
|
|||||||
total_gap_days += lastGap.duration;
|
total_gap_days += lastGap.duration;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the ref count reaches 2 by rising, start a new OVERLAP
|
||||||
else if( ref_count === 2 && inc === 1 ) {
|
else if( ref_count === 2 && inc === 1 ) {
|
||||||
coverage.overlaps.push( { start: point[1], end: null });
|
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 ) {
|
else if( ref_count === 1 && inc === -1 ) {
|
||||||
var lastOver = _.last( coverage.overlaps );
|
var lastOver = _.last( coverage.overlaps );
|
||||||
if( lastOver ) {
|
if( lastOver ) {
|
||||||
@ -114,32 +122,39 @@ Employment gap analysis for HackMyResume.
|
|||||||
if( lastOver.duration === 0 ) {
|
if( lastOver.duration === 0 ) {
|
||||||
coverage.overlaps.pop();
|
coverage.overlaps.pop();
|
||||||
}
|
}
|
||||||
total_work_days += lastOver.duration;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// It's possible that the last overlap didn't have an explicit .end date.
|
// It's possible that the last gap/overlap didn't have an explicit .end
|
||||||
// If so, set the end date to the present date and compute the overlap
|
// date.If so, set the end date to the present date and compute the
|
||||||
// duration normally.
|
// duration normally.
|
||||||
if( coverage.overlaps.length ) {
|
if( coverage.overlaps.length ) {
|
||||||
if( !_.last( coverage.overlaps ).end ) {
|
var o = _.last( coverage.overlaps );
|
||||||
var l = _.last( coverage.overlaps );
|
if( o && !o.end ) {
|
||||||
l.end = moment();
|
o.end = moment();
|
||||||
l.duration = l.end.diff( l.start, 'days' );
|
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 = {
|
var dur = {
|
||||||
total: rez.duration('days'),
|
total: tdur,
|
||||||
work: total_work_days,
|
work: tdur - total_gap_days,
|
||||||
gaps: total_gap_days
|
gaps: total_gap_days
|
||||||
};
|
};
|
||||||
coverage.pct = ( dur.total > 0 && dur.work > 0 ) ?
|
coverage.pct = ( dur.total > 0 && dur.work > 0 ) ?
|
||||||
((((dur.total - dur.gaps) / dur.total) * 100)).toFixed(1) + '%' :
|
((((dur.total - dur.gaps) / dur.total) * 100)).toFixed(1) + '%' :
|
||||||
'???';
|
'???';
|
||||||
coverage.duration = dur;
|
coverage.duration = dur;
|
||||||
|
|
||||||
return coverage;
|
return coverage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@ Definition of the HandlebarsGenerator class.
|
|||||||
( format === 'doc' ) && (encData = json.xmlify());
|
( format === 'doc' ) && (encData = json.xmlify());
|
||||||
|
|
||||||
// Compile and run the Handlebars template.
|
// Compile and run the Handlebars template.
|
||||||
var template = HANDLEBARS.compile(jst);
|
var template = HANDLEBARS.compile(jst, { strict: false, assumeObjects: false });
|
||||||
return template({
|
return template({
|
||||||
r: encData,
|
r: encData,
|
||||||
RAW: json,
|
RAW: json,
|
||||||
@ -61,30 +61,31 @@ Definition of the HandlebarsGenerator class.
|
|||||||
|
|
||||||
|
|
||||||
function registerPartials(format, theme) {
|
function registerPartials(format, theme) {
|
||||||
if( format !== 'html' && format != 'doc' )
|
if( format === 'html' || format === 'doc' ) {
|
||||||
return;
|
|
||||||
|
|
||||||
// Locate the global partials folder
|
// Locate the global partials folder
|
||||||
var partialsFolder = PATH.join(
|
var partialsFolder = PATH.join(
|
||||||
parsePath( require.resolve('fresh-themes') ).dirname,
|
parsePath( require.resolve('fresh-themes') ).dirname,
|
||||||
'/partials/',
|
'/partials/',
|
||||||
format
|
format
|
||||||
);
|
);
|
||||||
|
|
||||||
// Register global partials in the /partials folder
|
// Register global partials in the /partials folder
|
||||||
// TODO: Only do this once per HMR invocation.
|
// TODO: Only do this once per HMR invocation.
|
||||||
_.each( READFILES( partialsFolder, function(error){ }), function( el ) {
|
_.each( READFILES( partialsFolder, function(error){ }), function( el ) {
|
||||||
var pathInfo = parsePath( el );
|
var pathInfo = parsePath( el );
|
||||||
var name = SLASH( PATH.relative( partialsFolder, el )
|
var name = SLASH( PATH.relative( partialsFolder, el )
|
||||||
.replace(/\.html$|\.xml$/, '') );
|
.replace(/\.html$|\.xml$/, '') );
|
||||||
if( pathInfo.dirname.endsWith('section') ) {
|
if( pathInfo.dirname.endsWith('section') ) {
|
||||||
name = SLASH(name.replace(/\.html$|\.xml$/, ''));
|
name = SLASH(name.replace(/\.html$|\.xml$/, ''));
|
||||||
}
|
}
|
||||||
var tplData = FS.readFileSync( el, 'utf8' );
|
var tplData = FS.readFileSync( el, 'utf8' );
|
||||||
var compiledTemplate = HANDLEBARS.compile( tplData );
|
var compiledTemplate = HANDLEBARS.compile( tplData );
|
||||||
HANDLEBARS.registerPartial( name, compiledTemplate );
|
HANDLEBARS.registerPartial( name, compiledTemplate );
|
||||||
theme.partialsInitialized = true;
|
theme.partialsInitialized = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// Register theme-specific partials
|
// Register theme-specific partials
|
||||||
_.each( theme.partials, function( el ) {
|
_.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')
|
, MD = require('marked')
|
||||||
, MKDIRP = require('mkdirp')
|
, MKDIRP = require('mkdirp')
|
||||||
, EXTEND = require('../utils/extend')
|
, EXTEND = require('../utils/extend')
|
||||||
|
, HACKMYSTATUS = require('../core/status-codes')
|
||||||
, parsePath = require('parse-filepath')
|
, parsePath = require('parse-filepath')
|
||||||
, _opts = require('../core/default-options')
|
, _opts = require('../core/default-options')
|
||||||
, FluentTheme = require('../core/fresh-theme')
|
, 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
|
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.
|
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 ) {
|
function build( src, dst, opts, logger, errHandler ) {
|
||||||
|
|
||||||
// Housekeeping
|
prep( src, dst, opts, logger, errHandler );
|
||||||
//_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() );
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load the theme...we do this first because the theme choice (FRESH or
|
// Load the theme...we do this first because the theme choice (FRESH or
|
||||||
// JSON Resume) determines what format we'll convert the resume to.
|
// JSON Resume) determines what format we'll convert the resume to.
|
||||||
var tFolder = verify_theme( _opts.theme );
|
var tFolder = verifyTheme( _opts.theme );
|
||||||
var theme = load_theme( tFolder );
|
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...
|
// Load input resumes...
|
||||||
if( !src || !src.length ) { throw { fluenterror: 3 }; }
|
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".
|
Generate a single target resume such as "out/rez.html" or "out/rez.doc".
|
||||||
|
TODO: Refactor.
|
||||||
@param targInfo Information for the target resume.
|
@param targInfo Information for the target resume.
|
||||||
@param theme A FRESHTheme or JRSTheme object.
|
@param theme A FRESHTheme or JRSTheme object.
|
||||||
@returns
|
|
||||||
*/
|
*/
|
||||||
function single( targInfo, theme, finished ) {
|
function single( targInfo, theme, finished ) {
|
||||||
|
|
||||||
function MDIN(txt) { // TODO: Move this
|
|
||||||
return MD(txt || '' ).replace(/^\s*<p>|<\/p>\s*$/gi, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
if( !targInfo.fmt ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
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;
|
||||||
|
|
||||||
var suffix = '';
|
var suffix = '';
|
||||||
if( targInfo.fmt.outFormat === 'pdf' ) {
|
if( targInfo.fmt.outFormat === 'pdf' ) {
|
||||||
if( _opts.pdf ) {
|
if( _opts.pdf ) {
|
||||||
if( _opts.pdf !== 'none' ) {
|
if( _opts.pdf !== 'none' ) {
|
||||||
suffix = chalk.green(' (with ' + _opts.pdf + ')');
|
suffix = chalk.green(' (with ' + _opts.pdf + ')');
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
_log( chalk.gray('Skipping ') +
|
_log( chalk.gray('Skipping ') +
|
||||||
chalk.white.bold(
|
chalk.white.bold(
|
||||||
pad(targInfo.fmt.outFormat.toUpperCase(),4,null,pad.RIGHT)) +
|
pad(targInfo.fmt.outFormat.toUpperCase(),4,null,pad.RIGHT)) +
|
||||||
chalk.gray(' resume') + suffix + chalk.green(': ') +
|
chalk.gray(' resume') + suffix + chalk.green(': ') +
|
||||||
chalk.white( PATH.relative(process.cwd(), f )) );
|
chalk.white( PATH.relative(process.cwd(), f )) );
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_log( chalk.green('Generating ') +
|
_log( chalk.green('Generating ') +
|
||||||
chalk.green.bold(
|
chalk.green.bold(
|
||||||
pad(targInfo.fmt.outFormat.toUpperCase(),4,null,pad.RIGHT)) +
|
pad(targInfo.fmt.outFormat.toUpperCase(),4,null,pad.RIGHT)) +
|
||||||
chalk.green(' resume') + suffix + chalk.green(': ') +
|
chalk.green(' resume') + suffix + chalk.green(': ') +
|
||||||
chalk.green.bold( PATH.relative(process.cwd(), f )) );
|
chalk.green.bold( PATH.relative(process.cwd(), f )) );
|
||||||
|
|
||||||
// If targInfo.fmt.files exists, this format is backed by a document.
|
// If targInfo.fmt.files exists, this format is backed by a document.
|
||||||
// Fluent/FRESH themes are handled here.
|
// Fluent/FRESH themes are handled here.
|
||||||
@ -185,8 +193,8 @@ Implementation of the 'generate' verb for HackMyResume.
|
|||||||
return theFormat.gen.generate( rez, f, _opts );
|
return theFormat.gen.generate( rez, f, _opts );
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise this is either a) a JSON Resume theme or b) an ad-hoc format
|
//Otherwise this is an ad-hoc format (JSON, YML, or PNG) that every theme
|
||||||
// (JSON, YML, or PNG) that every theme gets "for free".
|
// gets "for free".
|
||||||
else {
|
else {
|
||||||
|
|
||||||
theFormat = _fmts.filter( function(fmt) {
|
theFormat = _fmts.filter( function(fmt) {
|
||||||
@ -196,43 +204,7 @@ Implementation of the 'generate' verb for HackMyResume.
|
|||||||
var outFolder = PATH.dirname( f );
|
var outFolder = PATH.dirname( f );
|
||||||
MKDIRP.sync( outFolder ); // Ensure dest folder exists;
|
MKDIRP.sync( outFolder ); // Ensure dest folder exists;
|
||||||
|
|
||||||
// JSON Resume themes have a 'render' method that needs to be called
|
return theFormat.gen.generate( rez, f, _opts );
|
||||||
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 );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch( ex ) {
|
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
|
Expand output files. For example, "foo.all" should be expanded to
|
||||||
["foo.html", "foo.doc", "foo.pdf", "etc"].
|
["foo.html", "foo.doc", "foo.pdf", "etc"].
|
||||||
@ -301,10 +294,11 @@ Implementation of the 'generate' verb for HackMyResume.
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Verify the specified theme name/path.
|
Verify the specified theme name/path.
|
||||||
*/
|
*/
|
||||||
function verify_theme( themeNameOrPath ) {
|
function verifyTheme( themeNameOrPath ) {
|
||||||
var tFolder = PATH.join(
|
var tFolder = PATH.join(
|
||||||
parsePath ( require.resolve('fresh-themes') ).dirname,
|
parsePath ( require.resolve('fresh-themes') ).dirname,
|
||||||
'/themes/',
|
'/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
|
// Create a FRESH or JRS theme object
|
||||||
var theTheme = _opts.theme.indexOf('jsonresume-theme-') > -1 ?
|
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;
|
module.exports = build;
|
||||||
|
|
||||||
|
|
@ -1,3 +1,6 @@
|
|||||||
|
/**
|
||||||
|
@module test-cli.js
|
||||||
|
*/
|
||||||
|
|
||||||
var chai = require('chai')
|
var chai = require('chai')
|
||||||
, expect = chai.expect
|
, expect = chai.expect
|
||||||
@ -6,7 +9,8 @@ var chai = require('chai')
|
|||||||
, _ = require('underscore')
|
, _ = require('underscore')
|
||||||
, FRESHResume = require('../src/core/fresh-resume')
|
, FRESHResume = require('../src/core/fresh-resume')
|
||||||
, FCMD = require( '../src/hackmyapi')
|
, 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;
|
chai.config.includeStack = false;
|
||||||
|
|
||||||
@ -14,9 +18,6 @@ describe('Testing CLI interface', function () {
|
|||||||
|
|
||||||
var _sheet;
|
var _sheet;
|
||||||
|
|
||||||
function logMsg() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
var opts = {
|
var opts = {
|
||||||
format: 'FRESH',
|
format: 'FRESH',
|
||||||
@ -31,46 +32,63 @@ describe('Testing CLI interface', function () {
|
|||||||
silent: true
|
silent: true
|
||||||
};
|
};
|
||||||
|
|
||||||
run( 'new', ['test/sandbox/new-fresh-resume.json'], [], opts, ' (FRESH format)' );
|
var sb = 'test/sandbox/';
|
||||||
run( 'new', ['test/sandbox/new-jrs-resume.json'], [], opts2, ' (JRS format)' );
|
var ft = 'node_modules/fresh-test-resumes/src/';
|
||||||
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)" );
|
|
||||||
|
|
||||||
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)' );
|
|
||||||
run( '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)' );
|
|
||||||
run( 'validate', ['test/sandbox/new-1.json','test/sandbox/new-jrs-resume.json','test/sandbox/new-1.json', 'test/sandbox/new-2.json', 'test/sandbox/new-3.json'], [], opts, ' (5|BOTH)' );
|
|
||||||
|
|
||||||
run( 'analyze', ['node_modules/fresh-test-resumes/src/jane-fullstacker.json'], [], opts, ' (jane-q-fullstacker|FRESH)' );
|
[ 'new', [sb + 'new-fresh-resume.json'], [], opts, ' (FRESH format)' ],
|
||||||
run( 'analyze', ['test/resumes/jrs-0.0.0/richard-hendriks.json'], [], opts2, ' (richard-hendriks|JRS)' );
|
[ '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 ) {
|
function run( verb, src, dst, opts, msg ) {
|
||||||
msg = 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() {
|
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