mirror of
				https://github.com/JuanCanham/HackMyResume.git
				synced 2025-11-03 22:37:27 +00: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