mirror of
https://github.com/JuanCanham/HackMyResume.git
synced 2025-05-15 01:57:08 +01:00
Compare commits
42 Commits
Author | SHA1 | Date | |
---|---|---|---|
64db1a654e | |||
31830ee759 | |||
1c05846a4f | |||
1db9c2e420 | |||
c966f6766c | |||
65b6359fd8 | |||
a54476eede | |||
7c0354046c | |||
43cd1c7e52 | |||
f80c333361 | |||
cdbb264093 | |||
87b3bbe785 | |||
b92cf7298a | |||
93456b5f40 | |||
72f29bf402 | |||
f6fc384466 | |||
c5ab3fdfae | |||
78c5081a29 | |||
d0c181ee8c | |||
80c6bb6e8b | |||
786b3fd3b2 | |||
f0a22be731 | |||
ade60022fd | |||
7daba910ed | |||
a016d6d91a | |||
fcaa97ed35 | |||
bb7373a229 | |||
759dcc30e7 | |||
0e47f02a33 | |||
5fe90517e7 | |||
92128da381 | |||
1441fe3ae5 | |||
b0bc71cd66 | |||
e908e8bb34 | |||
d708a6c6d8 | |||
a630741098 | |||
01d148e47c | |||
dbd41ec439 | |||
fc9cbab974 | |||
36f8010ebc | |||
e80d8fb5c8 | |||
eabab26eef |
@ -17,6 +17,8 @@ module.exports = function (grunt) {
|
|||||||
all: { src: ['tests/*.js'] }
|
all: { src: ['tests/*.js'] }
|
||||||
},
|
},
|
||||||
|
|
||||||
|
clean: ['tests/sandbox'],
|
||||||
|
|
||||||
yuidoc: {
|
yuidoc: {
|
||||||
compile: {
|
compile: {
|
||||||
name: '<%= pkg.name %>',
|
name: '<%= pkg.name %>',
|
||||||
@ -46,10 +48,11 @@ module.exports = function (grunt) {
|
|||||||
grunt.loadNpmTasks('grunt-simple-mocha');
|
grunt.loadNpmTasks('grunt-simple-mocha');
|
||||||
grunt.loadNpmTasks('grunt-contrib-yuidoc');
|
grunt.loadNpmTasks('grunt-contrib-yuidoc');
|
||||||
grunt.loadNpmTasks('grunt-contrib-jshint');
|
grunt.loadNpmTasks('grunt-contrib-jshint');
|
||||||
|
grunt.loadNpmTasks('grunt-contrib-clean');
|
||||||
|
|
||||||
grunt.registerTask('test', 'Test the FluentCV library.',
|
grunt.registerTask('test', 'Test the HackMyResume library.',
|
||||||
function( config ) { grunt.task.run( ['simplemocha:all'] ); });
|
function( config ) { grunt.task.run( ['clean','simplemocha:all'] ); });
|
||||||
grunt.registerTask('document', 'Generate FluentCV library documentation.',
|
grunt.registerTask('document', 'Generate HackMyResume library documentation.',
|
||||||
function( config ) { grunt.task.run( ['yuidoc'] ); });
|
function( config ) { grunt.task.run( ['yuidoc'] ); });
|
||||||
grunt.registerTask('default', [ 'jshint', 'test', 'yuidoc' ]);
|
grunt.registerTask('default', [ 'jshint', 'test', 'yuidoc' ]);
|
||||||
|
|
||||||
|
170
README.md
170
README.md
@ -1,12 +1,14 @@
|
|||||||
fluentCV
|
HackMyResume
|
||||||
========
|
============
|
||||||
*Create polished technical résumés and CVs in multiple formats from your command
|
*Create polished résumés and CVs in multiple formats from your command line or
|
||||||
line or shell. See [FluentCV Desktop][7] for the desktop version. OS X ~ Windows
|
shell. Author in clean Markdown and JSON, export to Word, HTML, PDF, LaTeX,
|
||||||
~ Linux.*
|
plain text, and other arbitrary formats. Fight the power, save trees. Compatible
|
||||||
|
with [FRESH][fresca] and [JRS][6] resumes.*
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
FluentCV is a dev-friendly Swiss Army knife for resumes and CVs. Use it to:
|
HackMyResume is a dev-friendly, local-only Swiss Army knife for resumes and CVs.
|
||||||
|
Use it to:
|
||||||
|
|
||||||
1. **Generate** HTML, Markdown, LaTeX, MS Word, PDF, plain text, JSON, XML,
|
1. **Generate** HTML, Markdown, LaTeX, MS Word, PDF, plain text, JSON, XML,
|
||||||
YAML, print, smoke signal, carrier pigeon, and other arbitrary-format resumes
|
YAML, print, smoke signal, carrier pigeon, and other arbitrary-format resumes
|
||||||
@ -14,7 +16,8 @@ and CVs, from a single source of truth—without violating DRY.
|
|||||||
2. **Convert** resumes between [FRESH][fresca] and [JSON Resume][6] formats.
|
2. **Convert** resumes between [FRESH][fresca] and [JSON Resume][6] formats.
|
||||||
3. **Validate** resumes against either format.
|
3. **Validate** resumes against either format.
|
||||||
|
|
||||||
FluentCV supports both the [FRESH][fresca] and [JSON Resume][6] source formats.
|
HackMyResume is built with Node.js and runs on recent versions of OS X, Linux,
|
||||||
|
or Windows.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
@ -22,8 +25,6 @@ FluentCV supports both the [FRESH][fresca] and [JSON Resume][6] source formats.
|
|||||||
- Store your resume data as a durable, versionable JSON or YAML document.
|
- Store your resume data as a durable, versionable JSON or YAML document.
|
||||||
- Generate polished resumes in multiple formats without violating [DRY][dry].
|
- Generate polished resumes in multiple formats without violating [DRY][dry].
|
||||||
- Output to HTML, Markdown, LaTeX, PDF, MS Word, JSON, YAML, plain text, or XML.
|
- Output to HTML, Markdown, LaTeX, PDF, MS Word, JSON, YAML, plain text, or XML.
|
||||||
- Compatible with [FRESH][fresh], [JSON Resume][6], [FRESCA][fresca], and
|
|
||||||
[FCV Desktop][7].
|
|
||||||
- Validate resumes against the FRESH or JSON Resume schema.
|
- Validate resumes against the FRESH or JSON Resume schema.
|
||||||
- Support for multiple input and output resumes.
|
- Support for multiple input and output resumes.
|
||||||
- Use from your command line or [desktop][7].
|
- Use from your command line or [desktop][7].
|
||||||
@ -31,10 +32,10 @@ FluentCV supports both the [FRESH][fresca] and [JSON Resume][6] source formats.
|
|||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
Install FluentCV with NPM:
|
Install HackMyResume with NPM:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
[sudo] npm install fluentcv -g
|
[sudo] npm install hackmyresume -g
|
||||||
```
|
```
|
||||||
|
|
||||||
Note: for PDF generation you'll need to install a copy of [wkhtmltopdf][3] for
|
Note: for PDF generation you'll need to install a copy of [wkhtmltopdf][3] for
|
||||||
@ -43,49 +44,52 @@ access to `xelatex` and similar.
|
|||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
To use FluentCV you'll need to create a valid resume in either [FRESH][fresca]
|
To use HackMyResume you'll need to create a valid resume in either
|
||||||
or [JSON Resume][6] format. Then you can start using the command line tool.
|
[FRESH][fresca] or [JSON Resume][6] format. Then you can start using the command
|
||||||
There are four basic commands you should be aware of:
|
line tool. There are four basic commands you should be aware of:
|
||||||
|
|
||||||
- `build` generates resumes in HTML, Word, Markdown, PDF, and other formats. Use
|
- **build** generates resumes in HTML, Word, Markdown, PDF, and other formats.
|
||||||
it when you need to submit, upload, print, or email resumes in specific formats.
|
Use it when you need to submit, upload, print, or email resumes in specific
|
||||||
|
formats.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# fluentcv BUILD <INPUTS> TO <OUTPUTS> [-t THEME]
|
# hackmyresume BUILD <INPUTS> TO <OUTPUTS> [-t THEME]
|
||||||
fluentcv BUILD resume.json TO out/resume.all
|
hackmyresume BUILD resume.json TO out/resume.all
|
||||||
fluentcv BUILD r1.json r2.json TO out/rez.html out/rez.md foo/rez.all
|
hackmyresume BUILD r1.json r2.json TO out/rez.html out/rez.md foo/rez.all
|
||||||
```
|
```
|
||||||
|
|
||||||
- `new` creates a new resume in FRESH or JSON Resume format.
|
- **new** creates a new resume in FRESH or JSON Resume format.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# fluentcv NEW <OUTPUTS> [-f <FORMAT>]
|
# hackmyresume NEW <OUTPUTS> [-f <FORMAT>]
|
||||||
fluentcv NEW resume.json
|
hackmyresume NEW resume.json
|
||||||
fluentcv NEW resume.json -f fresh
|
hackmyresume NEW resume.json -f fresh
|
||||||
fluentcv NEW r1.json r2.json -f jrs
|
hackmyresume NEW r1.json r2.json -f jrs
|
||||||
```
|
```
|
||||||
|
|
||||||
- `convert` converts your source resume between FRESH and JSON Resume formats.
|
- **convert** converts your source resume between FRESH and JSON Resume
|
||||||
Use it to convert between the two formats to take advantage of tools and services.
|
formats.
|
||||||
|
Use it to convert between the two formats to take advantage of tools and
|
||||||
|
services.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# fluentcv CONVERT <INPUTS> TO <OUTPUTS>
|
# hackmyresume CONVERT <INPUTS> TO <OUTPUTS>
|
||||||
fluentcv CONVERT resume.json TO resume-jrs.json
|
hackmyresume CONVERT resume.json TO resume-jrs.json
|
||||||
fluentcv CONVERT 1.json 2.json 3.json TO out/1.json out/2.json out/3.json
|
hackmyresume CONVERT 1.json 2.json 3.json TO out/1.json out/2.json out/3.json
|
||||||
```
|
```
|
||||||
|
|
||||||
- `validate` validates the specified resume against either the FRESH or JSON
|
- **validate** validates the specified resume against either the FRESH or JSON
|
||||||
Resume schema. Use it to make sure your resume data is sufficient and complete.
|
Resume schema. Use it to make sure your resume data is sufficient and complete.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# fluentcv VALIDATE <INPUTS>
|
# hackmyresume VALIDATE <INPUTS>
|
||||||
fluentcv VALIDATE resume.json
|
hackmyresume VALIDATE resume.json
|
||||||
fluentcv VALIDATE r1.json r2.json r3.json
|
hackmyresume VALIDATE r1.json r2.json r3.json
|
||||||
```
|
```
|
||||||
|
|
||||||
## Supported Output Formats
|
## Supported Output Formats
|
||||||
|
|
||||||
FluentCV supports these output formats:
|
HackMyResume supports these output formats:
|
||||||
|
|
||||||
Output Format | Ext | Notes
|
Output Format | Ext | Notes
|
||||||
------------- | --- | -----
|
------------- | --- | -----
|
||||||
@ -103,11 +107,11 @@ image | .png, .bmp | Forthcoming.
|
|||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
FluentCV requires a recent version of [Node.js][4] and [NPM][5]. Then:
|
HackMyResume requires a recent version of [Node.js][4] and [NPM][5]. Then:
|
||||||
|
|
||||||
1. Install the latest official [wkhtmltopdf][3] binary for your platform.
|
1. Install the latest official [wkhtmltopdf][3] binary for your platform.
|
||||||
2. Optionally install an updated LaTeX environment (LaTeX resumes only).
|
2. Optionally install an updated LaTeX environment (LaTeX resumes only).
|
||||||
2. Install **fluentCV** with `[sudo] npm install fluentcv -g`.
|
2. Install **HackMyResume** with `[sudo] npm install hackmyresume -g`.
|
||||||
3. You're ready to go.
|
3. You're ready to go.
|
||||||
|
|
||||||
## Use
|
## Use
|
||||||
@ -116,7 +120,7 @@ Assuming you've got a JSON-formatted resume handy, generating resumes in
|
|||||||
different formats and combinations easy. Just run:
|
different formats and combinations easy. Just run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
fluentcv BUILD <INPUTS> <OUTPUTS> [-t theme].
|
hackmyresume BUILD <INPUTS> <OUTPUTS> [-t theme].
|
||||||
```
|
```
|
||||||
|
|
||||||
Where `<INPUTS>` is one or more .json resume files, separated by spaces;
|
Where `<INPUTS>` is one or more .json resume files, separated by spaces;
|
||||||
@ -125,25 +129,25 @@ theme (default to Modern). For example:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Generate all resume formats (HTML, PDF, DOC, TXT, YML, etc.)
|
# Generate all resume formats (HTML, PDF, DOC, TXT, YML, etc.)
|
||||||
fluentcv build resume.json -o out/resume.all -t modern
|
hackmyresume build resume.json -o out/resume.all -t modern
|
||||||
|
|
||||||
# Generate a specific resume format
|
# Generate a specific resume format
|
||||||
fluentcv build resume.json TO out/resume.html
|
hackmyresume build resume.json TO out/resume.html
|
||||||
fluentcv build resume.json TO out/resume.pdf
|
hackmyresume build resume.json TO out/resume.pdf
|
||||||
fluentcv build resume.json TO out/resume.md
|
hackmyresume build resume.json TO out/resume.md
|
||||||
fluentcv build resume.json TO out/resume.doc
|
hackmyresume build resume.json TO out/resume.doc
|
||||||
fluentcv build resume.json TO out/resume.json
|
hackmyresume build resume.json TO out/resume.json
|
||||||
fluentcv build resume.json TO out/resume.txt
|
hackmyresume build resume.json TO out/resume.txt
|
||||||
fluentcv build resume.json TO out/resume.yml
|
hackmyresume build resume.json TO out/resume.yml
|
||||||
|
|
||||||
# Specify 2 inputs and 3 outputs
|
# Specify 2 inputs and 3 outputs
|
||||||
fluentcv build in1.json in2.json TO out.html out.doc out.pdf
|
hackmyresume build in1.json in2.json TO out.html out.doc out.pdf
|
||||||
```
|
```
|
||||||
|
|
||||||
You should see something to the effect of:
|
You should see something to the effect of:
|
||||||
|
|
||||||
```
|
```
|
||||||
*** FluentCV v0.9.0 ***
|
*** HackMyResume v0.9.0 ***
|
||||||
Reading JSON resume: foo/resume.json
|
Reading JSON resume: foo/resume.json
|
||||||
Applying MODERN Theme (7 formats)
|
Applying MODERN Theme (7 formats)
|
||||||
Generating HTML resume: out/resume.html
|
Generating HTML resume: out/resume.html
|
||||||
@ -159,28 +163,37 @@ Generating YAML resume: out/resume.yml
|
|||||||
|
|
||||||
### Applying a theme
|
### Applying a theme
|
||||||
|
|
||||||
You can specify a predefined or custom theme via the optional `-t` parameter. For a predefined theme, include the theme name. For a custom theme, include the path to the custom theme's folder.
|
You can specify a predefined or custom theme via the optional `-t` parameter.
|
||||||
|
For a predefined theme, include the theme name. For a custom theme, include the
|
||||||
|
path to the custom theme's folder.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
fluentcv build resume.json -t modern
|
hackmyresume build resume.json -t modern
|
||||||
fluentcv build resume.json -t ~/foo/bar/my-custom-theme/
|
hackmyresume build resume.json -t ~/foo/bar/my-custom-theme/
|
||||||
```
|
```
|
||||||
|
|
||||||
As of v0.9.0, available predefined themes are `modern`, `minimist`, and `hello-world`, and `compact`.
|
As of v1.0.0, available predefined themes are `positive`, `modern`, `compact`,
|
||||||
|
`minimist`, and `hello-world`.
|
||||||
|
|
||||||
### Merging resumes
|
### Merging resumes
|
||||||
|
|
||||||
You can **merge multiple resumes together** by specifying them in order from most generic to most specific:
|
You can **merge multiple resumes together** by specifying them in order from
|
||||||
|
most generic to most specific:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Merge specific.json onto base.json and generate all formats
|
# Merge specific.json onto base.json and generate all formats
|
||||||
fluentcv build base.json specific.json -o resume.all
|
hackmyresume build base.json specific.json -o resume.all
|
||||||
```
|
```
|
||||||
|
|
||||||
This can be useful for overriding a base (generic) resume with information from a specific (targeted) resume. For example, you might override your generic catch-all "software developer" resume with specific details from your targeted "game developer" resume, or combine two partial resumes into a "complete" resume. Merging follows conventional [extend()][9]-style behavior and there's no arbitrary limit to how many resumes you can merge:
|
This can be useful for overriding a base (generic) resume with information from
|
||||||
|
a specific (targeted) resume. For example, you might override your generic
|
||||||
|
catch-all "software developer" resume with specific details from your targeted
|
||||||
|
"game developer" resume, or combine two partial resumes into a "complete"
|
||||||
|
resume. Merging follows conventional [extend()][9]-style behavior and there's
|
||||||
|
no arbitrary limit to how many resumes you can merge:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
fluentcv build in1.json in2.json in3.json in4.json TO out.html out.doc
|
hackmyresume build in1.json in2.json in3.json in4.json TO out.html out.doc
|
||||||
Reading JSON resume: in1.json
|
Reading JSON resume: in1.json
|
||||||
Reading JSON resume: in2.json
|
Reading JSON resume: in2.json
|
||||||
Reading JSON resume: in3.json
|
Reading JSON resume: in3.json
|
||||||
@ -192,72 +205,75 @@ Generating WORD resume: out.doc
|
|||||||
|
|
||||||
### Multiple targets
|
### Multiple targets
|
||||||
|
|
||||||
You can specify **multiple output targets** and FluentCV will build them:
|
You can specify **multiple output targets** and HackMyResume will build them:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Generate out1.doc, out1.pdf, and foo.txt from me.json.
|
# Generate out1.doc, out1.pdf, and foo.txt from me.json.
|
||||||
fluentcv build me.json -o out1.doc -o out1.pdf -o foo.txt
|
hackmyresume build me.json -o out1.doc -o out1.pdf -o foo.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also omit the output file(s) and/or theme completely:
|
You can also omit the output file(s) and/or theme completely:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Equivalent to "fluentcv resume.json resume.all -t modern"
|
# Equivalent to "hackmyresume resume.json resume.all -t modern"
|
||||||
fluentcv build resume.json
|
hackmyresume build resume.json
|
||||||
```
|
```
|
||||||
|
|
||||||
### Using .all
|
### Using .all
|
||||||
|
|
||||||
The special `.all` extension tells FluentCV to generate all supported output formats for the given resume. For example, this...
|
The special `.all` extension tells HackMyResume to generate all supported output
|
||||||
|
formats for the given resume. For example, this...
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Generate all resume formats (HTML, PDF, DOC, TXT, etc.)
|
# Generate all resume formats (HTML, PDF, DOC, TXT, etc.)
|
||||||
fluentcv build me.json -o out/resume.all
|
hackmyresume build me.json -o out/resume.all
|
||||||
```
|
```
|
||||||
|
|
||||||
..tells FluentCV to read `me.json` and generate `out/resume.md`, `out/resume.doc`, `out/resume.html`, `out/resume.txt`, `out/resume.pdf`, and `out/resume.json`.
|
..tells HackMyResume to read `me.json` and generate `out/resume.md`,
|
||||||
|
`out/resume.doc`, `out/resume.html`, `out/resume.txt`, `out/resume.pdf`, and
|
||||||
|
`out/resume.json`.
|
||||||
|
|
||||||
### Validating
|
### Validating
|
||||||
|
|
||||||
FluentCV can also validate your resumes against either the [FRESH /
|
HackMyResume can also validate your resumes against either the [FRESH /
|
||||||
FRESCA][fresca] or [JSON Resume][6] formats. To validate one or more existing
|
FRESCA][fresca] or [JSON Resume][6] formats. To validate one or more existing
|
||||||
resumes, use the `validate` command:
|
resumes, use the `validate` command:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Validate myresume.json against either the FRESH or JSON Resume schema.
|
# Validate myresume.json against either the FRESH or JSON Resume schema.
|
||||||
fluentcv validate resumeA.json resumeB.json
|
hackmyresume validate resumeA.json resumeB.json
|
||||||
```
|
```
|
||||||
|
|
||||||
FluentCV will validate each specified resume in turn:
|
HackMyResume will validate each specified resume in turn:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
*** FluentCV v0.9.0 ***
|
*** HackMyResume v0.9.0 ***
|
||||||
Validating JSON resume: resumeA.json (INVALID)
|
Validating JSON resume: resumeA.json (INVALID)
|
||||||
Validating JSON resume: resumeB.json (VALID)
|
Validating JSON resume: resumeB.json (VALID)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Converting
|
### Converting
|
||||||
|
|
||||||
FluentCV can convert between the [FRESH][fresca] and [JSON Resume][6] formats.
|
HackMyResume can convert between the [FRESH][fresca] and [JSON Resume][6]
|
||||||
Just run:
|
formats. Just run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
fluentcv CONVERT <INPUTS> <OUTPUTS>
|
hackmyresume CONVERT <INPUTS> <OUTPUTS>
|
||||||
```
|
```
|
||||||
|
|
||||||
where <INPUTS> is one or more resumes in FRESH or JSON Resume format, and
|
where <INPUTS> is one or more resumes in FRESH or JSON Resume format, and
|
||||||
<OUTPUTS> is a corresponding list of output file names. FluentCV will autodetect
|
<OUTPUTS> is a corresponding list of output file names. HackMyResume will
|
||||||
the format (FRESH or JRS) of each input resume and convert it to the other
|
autodetect the format (FRESH or JRS) of each input resume and convert it to the
|
||||||
format (JRS or FRESH).
|
other format (JRS or FRESH).
|
||||||
|
|
||||||
### Prettifying
|
### Prettifying
|
||||||
|
|
||||||
FluentCV applies [js-beautify][10]-style HTML prettification by default to
|
HackMyResume applies [js-beautify][10]-style HTML prettification by default to
|
||||||
HTML-formatted resumes. To disable prettification, the `--nopretty` or `-n` flag
|
HTML-formatted resumes. To disable prettification, the `--nopretty` or `-n` flag
|
||||||
can be used:
|
can be used:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
fluentcv generate resume.json out.all --nopretty
|
hackmyresume generate resume.json out.all --nopretty
|
||||||
```
|
```
|
||||||
|
|
||||||
### Silent Mode
|
### Silent Mode
|
||||||
@ -265,8 +281,8 @@ fluentcv generate resume.json out.all --nopretty
|
|||||||
Use `-s` or `--silent` to run in silent mode:
|
Use `-s` or `--silent` to run in silent mode:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
fluentcv generate resume.json -o someFile.all -s
|
hackmyresume generate resume.json -o someFile.all -s
|
||||||
fluentcv generate resume.json -o someFile.all --silent
|
hackmyresume generate resume.json -o someFile.all --silent
|
||||||
```
|
```
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 53 KiB |
BIN
assets/hackmyresume_cli.png
Normal file
BIN
assets/hackmyresume_cli.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 27 KiB |
BIN
assets/resume-bouqet.png
Normal file
BIN
assets/resume-bouqet.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 170 KiB |
29
package.json
29
package.json
@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"name": "fluentcv",
|
"name": "hackmyresume",
|
||||||
"version": "0.10.2",
|
"version": "1.2.0",
|
||||||
"description": "Generate polished résumés and CVs in HTML, Markdown, LaTeX, MS Word, PDF, plain text, JSON, XML, YAML, smoke signal, and carrier pigeon.",
|
"description": "Generate polished résumés and CVs in HTML, Markdown, LaTeX, MS Word, PDF, plain text, JSON, XML, YAML, smoke signal, and carrier pigeon.",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/fluentdesk/fluentcv.git"
|
"url": "https://github.com/hacksalot/HackMyResume.git"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"resume",
|
"resume",
|
||||||
@ -18,23 +18,27 @@
|
|||||||
"PDF",
|
"PDF",
|
||||||
"YAML",
|
"YAML",
|
||||||
"HTML",
|
"HTML",
|
||||||
"CLI"
|
"LaTeX",
|
||||||
|
"CLI",
|
||||||
|
"Handlebars",
|
||||||
|
"Underscore",
|
||||||
|
"template"
|
||||||
],
|
],
|
||||||
"author": "James M. Devlin",
|
"author": "hacksalot <hacksalot@fluentdesk.com> (https://github.com/hacksalot)",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"preferGlobal": "true",
|
"preferGlobal": "true",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/fluentdesk/fluentcv/issues"
|
"url": "https://github.com/hacksalot/HackMyResume/issues"
|
||||||
},
|
},
|
||||||
"main": "src/fluentcmd.js",
|
"main": "src/hackmycmd.js",
|
||||||
"bin": {
|
"bin": {
|
||||||
"fluentcv": "src/index.js"
|
"hackmyresume": "src/index.js"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/fluentdesk/fluentcv",
|
"homepage": "https://github.com/hacksalot/HackMyResume",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"colors": "^1.1.2",
|
"colors": "^1.1.2",
|
||||||
"fluent-themes": "~0.6.2-beta",
|
"fluent-themes": "~0.7.0-beta",
|
||||||
"fresca": "~0.2.1",
|
"fresca": "~0.2.2",
|
||||||
"fs-extra": "^0.24.0",
|
"fs-extra": "^0.24.0",
|
||||||
"handlebars": "^4.0.5",
|
"handlebars": "^4.0.5",
|
||||||
"html": "0.0.10",
|
"html": "0.0.10",
|
||||||
@ -54,10 +58,11 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"chai": "*",
|
"chai": "*",
|
||||||
"grunt": "*",
|
"grunt": "*",
|
||||||
|
"grunt-contrib-clean": "^0.7.0",
|
||||||
"grunt-contrib-jshint": "^0.11.3",
|
"grunt-contrib-jshint": "^0.11.3",
|
||||||
"grunt-contrib-yuidoc": "^0.10.0",
|
"grunt-contrib-yuidoc": "^0.10.0",
|
||||||
"grunt-simple-mocha": "*",
|
"grunt-simple-mocha": "*",
|
||||||
"is-my-json-valid": "^2.12.2",
|
"jane-q-fullstacker": "fluentdesk/jane-q-fullstacker",
|
||||||
"mocha": "*",
|
"mocha": "*",
|
||||||
"resample": "fluentdesk/resample"
|
"resample": "fluentdesk/resample"
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
FRESH to JSON Resume conversion routiens.
|
FRESH to JSON Resume conversion routiens.
|
||||||
@license MIT. Copyright (c) 2015 James M. Devlin / FluentDesk
|
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
|
||||||
|
@module convert.js
|
||||||
*/
|
*/
|
||||||
|
|
||||||
(function(){
|
(function(){
|
||||||
|
19
src/core/default-formats.js
Normal file
19
src/core/default-formats.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
(function(){
|
||||||
|
|
||||||
|
var FLUENT = require('../hackmyapi');
|
||||||
|
|
||||||
|
/**
|
||||||
|
Supported resume formats.
|
||||||
|
*/
|
||||||
|
module.exports = [
|
||||||
|
{ name: 'html', ext: 'html', gen: new FLUENT.HtmlGenerator() },
|
||||||
|
{ name: 'txt', ext: 'txt', gen: new FLUENT.TextGenerator() },
|
||||||
|
{ name: 'doc', ext: 'doc', fmt: 'xml', gen: new FLUENT.WordGenerator() },
|
||||||
|
{ name: 'pdf', ext: 'pdf', fmt: 'html', is: false, gen: new FLUENT.HtmlPdfGenerator() },
|
||||||
|
{ name: 'md', ext: 'md', fmt: 'txt', gen: new FLUENT.MarkdownGenerator() },
|
||||||
|
{ name: 'json', ext: 'json', gen: new FLUENT.JsonGenerator() },
|
||||||
|
{ name: 'yml', ext: 'yml', fmt: 'yml', gen: new FLUENT.JsonYamlGenerator() },
|
||||||
|
{ name: 'latex', ext: 'tex', fmt: 'latex', gen: new FLUENT.LaTeXGenerator() }
|
||||||
|
];
|
||||||
|
|
||||||
|
}());
|
13
src/core/default-options.js
Normal file
13
src/core/default-options.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
(function(){
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
theme: 'modern',
|
||||||
|
prettify: { // ← See https://github.com/beautify-web/js-beautify#options
|
||||||
|
indent_size: 2,
|
||||||
|
unformatted: ['em','strong'],
|
||||||
|
max_char: 80, // ← See lib/html.js in above-linked repo
|
||||||
|
//wrap_line_length: 120, ← Don't use this
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}());
|
@ -1,6 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
The FluentCV date representation.
|
The HackMyResume date representation.
|
||||||
@license Copyright (c) 2015 by James M. Devlin. All rights reserved.
|
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
|
||||||
|
@module fluent-date.js
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var moment = require('moment');
|
var moment = require('moment');
|
||||||
@ -12,7 +13,7 @@ formats to be aware of here.
|
|||||||
2. The default "YYYY-MM-DD" format used in JSON Resume ("2015-02-10")
|
2. The default "YYYY-MM-DD" format used in JSON Resume ("2015-02-10")
|
||||||
3. Year-and-month only ("2015-04")
|
3. Year-and-month only ("2015-04")
|
||||||
4. Year-only "YYYY" ("2015")
|
4. Year-only "YYYY" ("2015")
|
||||||
5. The friendly FluentCV "mmm YYYY" format ("Mar 2015" or "Dec 2008")
|
5. The friendly HackMyResume "mmm YYYY" format ("Mar 2015" or "Dec 2008")
|
||||||
6. Empty dates ("", " ")
|
6. Empty dates ("", " ")
|
||||||
7. Any other date format that Moment.js can parse from
|
7. Any other date format that Moment.js can parse from
|
||||||
Note: Moment can transparently parse all or most of these, without requiring us
|
Note: Moment can transparently parse all or most of these, without requiring us
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
Definition of the FRESHResume class.
|
Definition of the FRESHResume class.
|
||||||
@license MIT. Copyright (c) 2015 James M. Devlin / FluentDesk
|
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
|
||||||
|
@module fresh-resume.js
|
||||||
*/
|
*/
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
Definition of the JRSResume class.
|
Definition of the JRSResume class.
|
||||||
@license MIT. Copyright (c) 2015 James M. Devlin / FluentDesk
|
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
|
||||||
|
@module jrs-resume.js
|
||||||
*/
|
*/
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
|
13
src/core/load-source-resumes.js
Normal file
13
src/core/load-source-resumes.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
(function(){
|
||||||
|
|
||||||
|
var FRESHResume = require('../core/fresh-resume');
|
||||||
|
|
||||||
|
module.exports = function loadSourceResumes( src, log, fn ) {
|
||||||
|
return src.map( function( res ) {
|
||||||
|
log( 'Reading '.info + 'SOURCE'.infoBold + ' resume: '.info +
|
||||||
|
res.cyan.bold );
|
||||||
|
return (fn && fn(res)) || (new FRESHResume()).open( res );
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
}());
|
@ -1,6 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
Abstract theme representation.
|
Definition of the Theme class.
|
||||||
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
|
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
|
||||||
|
@module theme.js
|
||||||
*/
|
*/
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
@ -15,7 +16,7 @@ Abstract theme representation.
|
|||||||
, RECURSIVE_READ_DIR = require('recursive-readdir-sync');
|
, RECURSIVE_READ_DIR = require('recursive-readdir-sync');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
The Theme class is a representation of a FluentCV theme asset.
|
The Theme class is a representation of a HackMyResume theme asset.
|
||||||
@class Theme
|
@class Theme
|
||||||
*/
|
*/
|
||||||
function Theme() {
|
function Theme() {
|
||||||
@ -99,6 +100,7 @@ Abstract theme representation.
|
|||||||
var outFmt = '', isMajor = false;
|
var outFmt = '', isMajor = false;
|
||||||
var portion = pathInfo.dir.replace(tplFolder,'');
|
var portion = pathInfo.dir.replace(tplFolder,'');
|
||||||
if( portion && portion.trim() ) {
|
if( portion && portion.trim() ) {
|
||||||
|
if( portion[1] === '_' ) return;
|
||||||
var reg = /^(?:\/|\\)(html|latex|doc|pdf|partials)(?:\/|\\)?/ig;
|
var reg = /^(?:\/|\\)(html|latex|doc|pdf|partials)(?:\/|\\)?/ig;
|
||||||
var res = reg.exec( portion );
|
var res = reg.exec( portion );
|
||||||
if( res ) {
|
if( res ) {
|
||||||
@ -132,7 +134,7 @@ Abstract theme representation.
|
|||||||
action: 'transform',
|
action: 'transform',
|
||||||
path: absPath,
|
path: absPath,
|
||||||
major: isMajor,
|
major: isMajor,
|
||||||
orgPath: PATH.relative(that.folder, absPath),
|
orgPath: PATH.relative(tplFolder, absPath),
|
||||||
ext: pathInfo.ext.slice(1),
|
ext: pathInfo.ext.slice(1),
|
||||||
title: friendlyName( outFmt ),
|
title: friendlyName( outFmt ),
|
||||||
pre: outFmt,
|
pre: outFmt,
|
||||||
@ -151,7 +153,7 @@ Abstract theme representation.
|
|||||||
.forEach(function( cssf ) {
|
.forEach(function( cssf ) {
|
||||||
// For each CSS file, get its corresponding HTML file
|
// For each CSS file, get its corresponding HTML file
|
||||||
var idx = _.findIndex(fmts, function( fmt ) {
|
var idx = _.findIndex(fmts, function( fmt ) {
|
||||||
return fmt.pre === cssf.pre && fmt.ext === 'html';
|
return fmt && fmt.pre === cssf.pre && fmt.ext === 'html';
|
||||||
});
|
});
|
||||||
cssf.action = null;
|
cssf.action = null;
|
||||||
fmts[ idx ].css = cssf.data;
|
fmts[ idx ].css = cssf.data;
|
||||||
|
169
src/eng/generic-helpers.js
Normal file
169
src/eng/generic-helpers.js
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
/**
|
||||||
|
Generic template helper definitions for FluentCV.
|
||||||
|
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
|
||||||
|
@module generic-helpers.js
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
|
||||||
|
var MD = require('marked')
|
||||||
|
, H2W = require('../utils/html-to-wpml')
|
||||||
|
, moment = require('moment')
|
||||||
|
, _ = require('underscore');
|
||||||
|
|
||||||
|
/**
|
||||||
|
Generic template helper function definitions.
|
||||||
|
@class GenericHelpers
|
||||||
|
*/
|
||||||
|
var GenericHelpers = module.exports = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
Convert the input date to a specified format through Moment.js.
|
||||||
|
@method formatDate
|
||||||
|
*/
|
||||||
|
formatDate: function(datetime, format) {
|
||||||
|
return moment ? moment( datetime ).format( format ) : datetime;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
Convert inline Markdown to inline WordProcessingML.
|
||||||
|
@method wpml
|
||||||
|
*/
|
||||||
|
wpml: function( txt, inline ) {
|
||||||
|
if(!txt) return '';
|
||||||
|
inline = (inline && !inline.hash) || false;
|
||||||
|
txt = inline ?
|
||||||
|
MD(txt.trim()).replace(/^\s*<p>|<\/p>\s*$/gi, '') :
|
||||||
|
MD(txt.trim());
|
||||||
|
txt = H2W( txt.trim() );
|
||||||
|
return txt;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
Emit a conditional link.
|
||||||
|
@method link
|
||||||
|
*/
|
||||||
|
link: function( text, url ) {
|
||||||
|
return url && url.trim() ?
|
||||||
|
('<a href="' + url + '">' + text + '</a>') : text;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
Return the last word of the specified text.
|
||||||
|
@method lastWord
|
||||||
|
*/
|
||||||
|
lastWord: function( txt ) {
|
||||||
|
return txt && txt.trim() ? _.last( txt.split(' ') ) : '';
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
Convert a skill level to an RGB color triplet.
|
||||||
|
@method skillColor
|
||||||
|
@param lvl Input skill level. Skill level can be expressed as a string
|
||||||
|
("beginner", "intermediate", etc.), as an integer (1,5,etc), as a string
|
||||||
|
integer ("1", "5", etc.), or as an RRGGBB color triplet ('#C00000',
|
||||||
|
'#FFFFAA').
|
||||||
|
*/
|
||||||
|
skillColor: function( lvl ) {
|
||||||
|
var idx = skillLevelToIndex( lvl );
|
||||||
|
var skillColors = (this.theme && this.theme.palette &&
|
||||||
|
this.theme.palette.skillLevels) ||
|
||||||
|
[ '#FFFFFF', '#5CB85C', '#F1C40F', '#428BCA', '#C00000' ];
|
||||||
|
return skillColors[idx];
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
Return an appropriate height.
|
||||||
|
@method lastWord
|
||||||
|
*/
|
||||||
|
skillHeight: function( lvl ) {
|
||||||
|
var idx = skillLevelToIndex( lvl );
|
||||||
|
return ['38.25', '30', '16', '8', '0'][idx];
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
Return all but the last word of the input text.
|
||||||
|
@method initialWords
|
||||||
|
*/
|
||||||
|
initialWords: function( txt ) {
|
||||||
|
return txt && txt.trim() ? _.initial( txt.split(' ') ).join(' ') : '';
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
Trim the protocol (http or https) from a URL/
|
||||||
|
@method trimURL
|
||||||
|
*/
|
||||||
|
trimURL: function( url ) {
|
||||||
|
return url && url.trim() ? url.trim().replace(/^https?:\/\//i, '') : '';
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
Convert text to lowercase.
|
||||||
|
@method toLower
|
||||||
|
*/
|
||||||
|
toLower: function( txt ) {
|
||||||
|
return txt && txt.trim() ? txt.toLowerCase() : '';
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
Return true if either value is truthy.
|
||||||
|
@method either
|
||||||
|
*/
|
||||||
|
either: function( lhs, rhs, options ) {
|
||||||
|
if (lhs || rhs) return options.fn(this);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
Perform a generic comparison.
|
||||||
|
See: http://doginthehat.com.au/2012/02/comparison-block-helper-for-handlebars-templates
|
||||||
|
@method compare
|
||||||
|
*/
|
||||||
|
compare: function(lvalue, rvalue, options) {
|
||||||
|
if (arguments.length < 3)
|
||||||
|
throw new Error("Handlerbars Helper 'compare' needs 2 parameters");
|
||||||
|
var operator = options.hash.operator || "==";
|
||||||
|
var operators = {
|
||||||
|
'==': function(l,r) { return l == r; },
|
||||||
|
'===': function(l,r) { return l === r; },
|
||||||
|
'!=': function(l,r) { return l != r; },
|
||||||
|
'<': function(l,r) { return l < r; },
|
||||||
|
'>': function(l,r) { return l > r; },
|
||||||
|
'<=': function(l,r) { return l <= r; },
|
||||||
|
'>=': function(l,r) { return l >= r; },
|
||||||
|
'typeof': function(l,r) { return typeof l == r; }
|
||||||
|
};
|
||||||
|
if (!operators[operator])
|
||||||
|
throw new Error("Handlerbars Helper 'compare' doesn't know the operator "+operator);
|
||||||
|
var result = operators[operator](lvalue,rvalue);
|
||||||
|
return result ? options.fn(this) : options.inverse(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
function skillLevelToIndex( lvl ) {
|
||||||
|
var idx = 0;
|
||||||
|
if( String.is( lvl ) ) {
|
||||||
|
lvl = lvl.trim().toLowerCase();
|
||||||
|
var intVal = parseInt( lvl );
|
||||||
|
if( isNaN( intVal ) ) {
|
||||||
|
switch( lvl ) {
|
||||||
|
case 'beginner': idx = 1; break;
|
||||||
|
case 'intermediate': idx = 2; break;
|
||||||
|
case 'advanced': idx = 3; break;
|
||||||
|
case 'master': idx = 4; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
idx = Math.min( intVal / 2, 4 );
|
||||||
|
idx = Math.max( 0, idx );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
idx = Math.min( lvl / 2, 4 );
|
||||||
|
idx = Math.max( 0, idx );
|
||||||
|
}
|
||||||
|
return idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
}());
|
@ -1,73 +1,50 @@
|
|||||||
/**
|
/**
|
||||||
Handlebars template generate for FluentCV.
|
Definition of the HandlebarsGenerator class.
|
||||||
@license MIT. Copyright (c) 2015 James M. Devlin / FluentDesk.
|
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
|
||||||
|
@module handlebars-generator.js
|
||||||
*/
|
*/
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
|
|
||||||
var _ = require('underscore');
|
|
||||||
var HANDLEBARS = require('handlebars');
|
|
||||||
var FS = require('fs');
|
|
||||||
var moment = require('moment');
|
|
||||||
var MD = require('marked');
|
|
||||||
var H2W = require('../utils/html-to-wpml');
|
|
||||||
|
|
||||||
module.exports = function( json, jst, format, cssInfo, opts, theme ) {
|
|
||||||
|
|
||||||
|
var _ = require('underscore')
|
||||||
|
, HANDLEBARS = require('handlebars')
|
||||||
|
, FS = require('fs')
|
||||||
|
, registerHelpers = require('./handlebars-helpers');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
Perform template-based resume generation using Handlebars.js.
|
||||||
|
@class HandlebarsGenerator
|
||||||
|
*/
|
||||||
|
var HandlebarsGenerator = module.exports = {
|
||||||
|
|
||||||
|
generate: function( json, jst, format, cssInfo, opts, theme ) {
|
||||||
|
|
||||||
|
// Pre-compile any partials present in the theme.
|
||||||
_.each( theme.partials, function( el ) {
|
_.each( theme.partials, function( el ) {
|
||||||
var tplData = FS.readFileSync( el.path, 'utf8' );
|
var tplData = FS.readFileSync( el.path, 'utf8' );
|
||||||
var compiledTemplate = HANDLEBARS.compile( tplData );
|
var compiledTemplate = HANDLEBARS.compile( tplData );
|
||||||
HANDLEBARS.registerPartial( el.name, compiledTemplate );
|
HANDLEBARS.registerPartial( el.name, compiledTemplate );
|
||||||
});
|
});
|
||||||
|
|
||||||
HANDLEBARS.registerHelper("formatDate", function(datetime, format) {
|
// Register necessary helpers.
|
||||||
if( moment ) {
|
registerHelpers( theme );
|
||||||
return moment( datetime ).format( format );
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return datetime;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
HANDLEBARS.registerHelper("wpml", function( txt, inline ) {
|
|
||||||
inline = (inline && !inline.hash) || false;
|
|
||||||
txt = inline ? MD(txt.trim()).replace(/^\s*<p>|<\/p>\s*$/gi, '') : MD(txt.trim());
|
|
||||||
txt = H2W( txt.trim() );
|
|
||||||
return txt;
|
|
||||||
});
|
|
||||||
|
|
||||||
// http://doginthehat.com.au/2012/02/comparison-block-helper-for-handlebars-templates/
|
|
||||||
HANDLEBARS.registerHelper('compare', function(lvalue, rvalue, options) {
|
|
||||||
|
|
||||||
if (arguments.length < 3)
|
|
||||||
throw new Error("Handlerbars Helper 'compare' needs 2 parameters");
|
|
||||||
|
|
||||||
var operator = options.hash.operator || "==";
|
|
||||||
var operators = {
|
|
||||||
'==': function(l,r) { return l == r; },
|
|
||||||
'===': function(l,r) { return l === r; },
|
|
||||||
'!=': function(l,r) { return l != r; },
|
|
||||||
'<': function(l,r) { return l < r; },
|
|
||||||
'>': function(l,r) { return l > r; },
|
|
||||||
'<=': function(l,r) { return l <= r; },
|
|
||||||
'>=': function(l,r) { return l >= r; },
|
|
||||||
'typeof': function(l,r) { return typeof l == r; }
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!operators[operator])
|
|
||||||
throw new Error("Handlerbars Helper 'compare' doesn't know the operator "+operator);
|
|
||||||
var result = operators[operator](lvalue,rvalue);
|
|
||||||
return result ? options.fn(this) : options.inverse(this);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
// Compile and run the Handlebars template.
|
||||||
var template = HANDLEBARS.compile(jst);
|
var template = HANDLEBARS.compile(jst);
|
||||||
return template({
|
return template({
|
||||||
r: json,
|
r: format === 'html' || format === 'pdf' ? json.markdownify() : json,
|
||||||
|
RAW: json,
|
||||||
filt: opts.filters,
|
filt: opts.filters,
|
||||||
cssInfo: cssInfo,
|
cssInfo: cssInfo,
|
||||||
headFragment: opts.headFragment || ''
|
headFragment: opts.headFragment || ''
|
||||||
});
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}());
|
}());
|
||||||
|
25
src/eng/handlebars-helpers.js
Normal file
25
src/eng/handlebars-helpers.js
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
Template helper definitions for Handlebars.
|
||||||
|
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
|
||||||
|
@module handlebars-helpers.js
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
|
||||||
|
var HANDLEBARS = require('handlebars')
|
||||||
|
, _ = require('underscore')
|
||||||
|
, helpers = require('./generic-helpers');
|
||||||
|
|
||||||
|
/**
|
||||||
|
Register useful Handlebars helpers.
|
||||||
|
@method registerHelpers
|
||||||
|
*/
|
||||||
|
module.exports = function( theme ) {
|
||||||
|
|
||||||
|
helpers.theme = theme;
|
||||||
|
HANDLEBARS.registerHelper( helpers );
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}());
|
@ -1,13 +1,24 @@
|
|||||||
/**
|
/**
|
||||||
Underscore template generate for FluentCV.
|
Definition of the UnderscoreGenerator class.
|
||||||
@license MIT. Copyright (c) 2015 James M. Devlin / FluentDesk.
|
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
|
||||||
|
@module underscore-generator.js
|
||||||
*/
|
*/
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var _ = require('underscore');
|
var _ = require('underscore');
|
||||||
|
|
||||||
module.exports = function( json, jst, format, cssInfo, opts, theme ) {
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
Perform template-based resume generation using Underscore.js.
|
||||||
|
@class UnderscoreGenerator
|
||||||
|
*/
|
||||||
|
var UnderscoreGenerator = module.exports = {
|
||||||
|
|
||||||
|
generate: function( json, jst, format, cssInfo, opts, theme ) {
|
||||||
|
|
||||||
// Tweak underscore's default template delimeters
|
// Tweak underscore's default template delimeters
|
||||||
var delims = (opts.themeObj && opts.themeObj.delimeters) || opts.template;
|
var delims = (opts.themeObj && opts.themeObj.delimeters) || opts.template;
|
||||||
@ -20,6 +31,7 @@ Underscore template generate for FluentCV.
|
|||||||
|
|
||||||
// Strip {# comments #}
|
// Strip {# comments #}
|
||||||
jst = jst.replace( delims.comment, '');
|
jst = jst.replace( delims.comment, '');
|
||||||
|
|
||||||
// Compile and run the template. TODO: avoid unnecessary recompiles.
|
// Compile and run the template. TODO: avoid unnecessary recompiles.
|
||||||
var compiled = _.template(jst);
|
var compiled = _.template(jst);
|
||||||
var ret = compiled({
|
var ret = compiled({
|
||||||
@ -31,7 +43,10 @@ Underscore template generate for FluentCV.
|
|||||||
headFragment: opts.headFragment || ''
|
headFragment: opts.headFragment || ''
|
||||||
});
|
});
|
||||||
return ret;
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}());
|
}());
|
||||||
|
348
src/fluentcmd.js
348
src/fluentcmd.js
@ -1,348 +0,0 @@
|
|||||||
/**
|
|
||||||
Internal resume generation logic for FluentCV.
|
|
||||||
@license MIT. Copyright (c) 2015 James M. Devlin / FluentDesk
|
|
||||||
@module fluentcmd.js
|
|
||||||
*/
|
|
||||||
|
|
||||||
(function() {
|
|
||||||
module.exports = function () {
|
|
||||||
|
|
||||||
var path = require( 'path' )
|
|
||||||
, extend = require( './utils/extend' )
|
|
||||||
, unused = require('./utils/string')
|
|
||||||
, FS = require('fs')
|
|
||||||
, _ = require('underscore')
|
|
||||||
, FLUENT = require('./fluentlib')
|
|
||||||
, PATH = require('path')
|
|
||||||
, MKDIRP = require('mkdirp')
|
|
||||||
//, COLORS = require('colors')
|
|
||||||
, rez, _log, _err;
|
|
||||||
|
|
||||||
/**
|
|
||||||
Given a source JSON resume, a destination resume path, and a theme file,
|
|
||||||
generate 0..N resumes in the desired formats.
|
|
||||||
@param src Path to the source JSON resume file: "rez/resume.json".
|
|
||||||
@param dst An array of paths to the target resume file(s).
|
|
||||||
@param theme Friendly name of the resume theme. Defaults to "modern".
|
|
||||||
@param logger Optional logging override.
|
|
||||||
*/
|
|
||||||
function generate( src, dst, opts, logger, errHandler ) {
|
|
||||||
|
|
||||||
_log = logger || console.log;
|
|
||||||
_err = errHandler || error;
|
|
||||||
|
|
||||||
//_opts = extend( true, _opts, opts );
|
|
||||||
_opts.theme = (opts.theme && opts.theme.toLowerCase().trim())|| 'modern';
|
|
||||||
_opts.prettify = opts.prettify === true ? _opts.prettify : false;
|
|
||||||
|
|
||||||
// Load input resumes...
|
|
||||||
if(!src || !src.length) { throw { fluenterror: 3 }; }
|
|
||||||
var sheets = loadSourceResumes( src );
|
|
||||||
|
|
||||||
// Merge input resumes...
|
|
||||||
var msg = '';
|
|
||||||
rez = _.reduceRight( sheets, function( a, b, idx ) {
|
|
||||||
msg += ((idx == sheets.length - 2) ?
|
|
||||||
'Merging '.gray+ a.imp.fileName : '') + ' onto '.gray + b.imp.fileName;
|
|
||||||
return extend( true, b, a );
|
|
||||||
});
|
|
||||||
msg && _log(msg);
|
|
||||||
|
|
||||||
// Verify the specified theme name/path
|
|
||||||
var relativeThemeFolder = '../node_modules/fluent-themes/themes';
|
|
||||||
var tFolder = PATH.resolve( __dirname, relativeThemeFolder, _opts.theme);
|
|
||||||
var exists = require('./utils/file-exists');
|
|
||||||
if (!exists( tFolder )) {
|
|
||||||
tFolder = PATH.resolve( _opts.theme );
|
|
||||||
if (!exists( tFolder )) {
|
|
||||||
throw { fluenterror: 1, data: _opts.theme };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load the theme
|
|
||||||
var theTheme = new FLUENT.Theme().open( tFolder );
|
|
||||||
_opts.themeObj = theTheme;
|
|
||||||
_log( 'Applying '.info + theTheme.name.toUpperCase().infoBold +
|
|
||||||
(' theme (' +Object.keys(theTheme.formats).length + ' formats)').info);
|
|
||||||
|
|
||||||
// Expand output resumes... (can't use map() here)
|
|
||||||
var targets = [], that = this;
|
|
||||||
( (dst && dst.length && dst) || ['resume.all'] ).forEach( function(t) {
|
|
||||||
|
|
||||||
var to = path.resolve(t),
|
|
||||||
pa = path.parse(to),
|
|
||||||
fmat = pa.ext || '.all';
|
|
||||||
|
|
||||||
targets.push.apply(targets, fmat === '.all' ?
|
|
||||||
Object.keys( theTheme.formats ).map(function(k){
|
|
||||||
var z = theTheme.formats[k];
|
|
||||||
return { file: to.replace(/all$/g,z.outFormat), fmt: z };
|
|
||||||
}) : [{ file: to, fmt: theTheme.getFormat( fmat.slice(1) ) }]);
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
// Run the transformation!
|
|
||||||
var finished = targets.map( function(t) { return single(t, theTheme); });
|
|
||||||
|
|
||||||
// Don't send the client back empty-handed
|
|
||||||
return { sheet: rez, targets: targets, processed: finished };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Generate a single resume of a specific format.
|
|
||||||
@param f Full path to the destination resume to generate, for example,
|
|
||||||
"/foo/bar/resume.pdf" or "c:\foo\bar\resume.txt".
|
|
||||||
*/
|
|
||||||
function single( targInfo, theme ) {
|
|
||||||
try {
|
|
||||||
var f = targInfo.file
|
|
||||||
, fType = targInfo.fmt.outFormat
|
|
||||||
, fName = path.basename(f, '.' + fType)
|
|
||||||
, theFormat;
|
|
||||||
|
|
||||||
// If targInfo.fmt.files exists, this theme has an explicit "files"
|
|
||||||
// section in its theme.json file.
|
|
||||||
if( targInfo.fmt.files && targInfo.fmt.files.length ) {
|
|
||||||
|
|
||||||
_log( 'Generating '.useful +
|
|
||||||
targInfo.fmt.outFormat.toUpperCase().useful.bold +
|
|
||||||
' resume: '.useful + path.relative(process.cwd(), f ).useful.bold);
|
|
||||||
|
|
||||||
theFormat = _fmts.filter(
|
|
||||||
function(fmt) { return fmt.name === targInfo.fmt.outFormat; })[0];
|
|
||||||
MKDIRP.sync( path.dirname( f ) ); // Ensure dest folder exists;
|
|
||||||
theFormat.gen.generate( rez, f, _opts );
|
|
||||||
|
|
||||||
// targInfo.fmt.files.forEach( function( form ) {
|
|
||||||
//
|
|
||||||
// if( form.action === 'transform' ) {
|
|
||||||
// var theFormat = _fmts.filter( function( fmt ) {
|
|
||||||
// return fmt.name === targInfo.fmt.outFormat;
|
|
||||||
// })[0];
|
|
||||||
// MKDIRP.sync( path.dirname( f ) ); // Ensure dest folder exists;
|
|
||||||
// theFormat.gen.generate( rez, f, _opts );
|
|
||||||
// }
|
|
||||||
// else if( form.action === null ) {
|
|
||||||
// // Copy the file
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// });
|
|
||||||
|
|
||||||
}
|
|
||||||
// Otherwise the theme has no files section
|
|
||||||
else {
|
|
||||||
_log( 'Generating '.useful +
|
|
||||||
targInfo.fmt.outFormat.toUpperCase().useful.bold +
|
|
||||||
' resume: '.useful + path.relative(process.cwd(), f ).useful.bold);
|
|
||||||
|
|
||||||
theFormat = _fmts.filter(
|
|
||||||
function(fmt) { return fmt.name === targInfo.fmt.outFormat; })[0];
|
|
||||||
MKDIRP.sync( path.dirname( f ) ); // Ensure dest folder exists;
|
|
||||||
theFormat.gen.generate( rez, f, _opts );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch( ex ) {
|
|
||||||
_err( ex );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Handle an exception.
|
|
||||||
*/
|
|
||||||
function error( ex ) {
|
|
||||||
throw ex;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Validate 1 to N resumes in either FRESH or JSON Resume format.
|
|
||||||
*/
|
|
||||||
function validate( src, unused, opts, logger ) {
|
|
||||||
_log = logger || console.log;
|
|
||||||
if( !src || !src.length ) { throw { fluenterror: 6 }; }
|
|
||||||
var isValid = true;
|
|
||||||
|
|
||||||
var validator = require('is-my-json-valid');
|
|
||||||
var schemas = {
|
|
||||||
fresh: require('FRESCA'),
|
|
||||||
jars: require('./core/resume.json')
|
|
||||||
};
|
|
||||||
|
|
||||||
// Load input resumes...
|
|
||||||
var sheets = loadSourceResumes(src, function( res ) {
|
|
||||||
try {
|
|
||||||
return {
|
|
||||||
file: res,
|
|
||||||
raw: FS.readFileSync( res, 'utf8' )
|
|
||||||
};
|
|
||||||
}
|
|
||||||
catch( ex ) {
|
|
||||||
throw ex;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
sheets.forEach( function( rep ) {
|
|
||||||
|
|
||||||
var rez;
|
|
||||||
try {
|
|
||||||
rez = JSON.parse( rep.raw );
|
|
||||||
}
|
|
||||||
catch( ex ) {
|
|
||||||
_log('Validating '.info + rep.file.infoBold +
|
|
||||||
' against FRESH/JRS schema: '.info + 'ERROR!'.error.bold);
|
|
||||||
|
|
||||||
if (ex instanceof SyntaxError) {
|
|
||||||
// Invalid JSON
|
|
||||||
_log( '--> '.bold.red + rep.file.toUpperCase().red +
|
|
||||||
' contains invalid JSON. Unable to validate.'.red );
|
|
||||||
_log( (' INTERNAL: ' + ex).red );
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
|
|
||||||
_log(('ERROR: ' + ex.toString()).red.bold);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var isValid = false;
|
|
||||||
var style = 'useful';
|
|
||||||
var errors = [];
|
|
||||||
var fmt = rez.meta &&
|
|
||||||
(rez.meta.format === 'FRESH@0.1.0') ? 'fresh':'jars';
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
var validate = validator( schemas[ fmt ], { // Note [1]
|
|
||||||
formats: {
|
|
||||||
date: /^\d{4}(?:-(?:0[0-9]{1}|1[0-2]{1})(?:-[0-9]{2})?)?$/
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
isValid = validate( rez );
|
|
||||||
if( !isValid ) {
|
|
||||||
style = 'warn';
|
|
||||||
errors = validate.errors;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
catch(ex) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
_log( 'Validating '.info + rep.file.infoBold + ' against '.info +
|
|
||||||
fmt.replace('jars','JSON Resume').toUpperCase().infoBold +
|
|
||||||
' schema: '.info + (isValid ? 'VALID!' : 'INVALID')[style].bold );
|
|
||||||
|
|
||||||
errors.forEach(function(err,idx) {
|
|
||||||
_log( '--> '.bold.yellow +
|
|
||||||
(err.field.replace('data.','resume.').toUpperCase() + ' ' +
|
|
||||||
err.message).yellow );
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Convert between FRESH and JRS formats.
|
|
||||||
*/
|
|
||||||
function convert( src, dst, opts, logger ) {
|
|
||||||
_log = logger || console.log;
|
|
||||||
if( !src || !src.length ) { throw { fluenterror: 6 }; }
|
|
||||||
if( !dst || !dst.length ) {
|
|
||||||
if( src.length === 1 ) { throw { fluenterror: 5 }; }
|
|
||||||
else if( src.length === 2 ) { dst = [ src[1] ]; src = [ src[0] ]; }
|
|
||||||
else { throw { fluenterror: 5 }; }
|
|
||||||
}
|
|
||||||
if( src && dst && src.length && dst.length && src.length !== dst.length ) {
|
|
||||||
throw { fluenterror: 7 };
|
|
||||||
}
|
|
||||||
var sheets = loadSourceResumes( src );
|
|
||||||
sheets.forEach(function(sheet, idx){
|
|
||||||
var sourceFormat = sheet.imp.orgFormat === 'JRS' ? 'JRS' : 'FRESH';
|
|
||||||
var targetFormat = sourceFormat === 'JRS' ? 'FRESH' : 'JRS';
|
|
||||||
_log( 'Converting '.useful + sheet.imp.fileName.useful.bold + (' (' +
|
|
||||||
sourceFormat + ') to ').useful + dst[0].useful.bold +
|
|
||||||
(' (' + targetFormat + ').').useful );
|
|
||||||
sheet.saveAs( dst[idx], targetFormat );
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Create a new empty resume in either FRESH or JRS format.
|
|
||||||
*/
|
|
||||||
function create( src, dst, opts, logger ) {
|
|
||||||
_log = logger || console.log;
|
|
||||||
dst = src || ['resume.json'];
|
|
||||||
dst.forEach( function( t ) {
|
|
||||||
var safeFormat = opts.format.toUpperCase();
|
|
||||||
_log('Creating new '.useful +safeFormat.useful.bold +
|
|
||||||
' resume: '.useful + t.useful.bold);
|
|
||||||
MKDIRP.sync( path.dirname( t ) ); // Ensure dest folder exists;
|
|
||||||
FLUENT[ safeFormat + 'Resume' ].default().save( t );
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Display help documentation.
|
|
||||||
*/
|
|
||||||
function help() {
|
|
||||||
console.log( FS.readFileSync( PATH.join(__dirname, 'use.txt'), 'utf8' )
|
|
||||||
.useful.bold );
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadSourceResumes( src, fn ) {
|
|
||||||
return src.map( function( res ) {
|
|
||||||
_log( 'Reading '.info + 'SOURCE'.infoBold + ' resume: '.info +
|
|
||||||
res.cyan.bold );
|
|
||||||
return (fn && fn(res)) || (new FLUENT.FRESHResume()).open( res );
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Supported resume formats.
|
|
||||||
*/
|
|
||||||
var _fmts = [
|
|
||||||
{ name: 'html', ext: 'html', gen: new FLUENT.HtmlGenerator() },
|
|
||||||
{ name: 'txt', ext: 'txt', gen: new FLUENT.TextGenerator() },
|
|
||||||
{ name: 'doc', ext: 'doc', fmt: 'xml', gen: new FLUENT.WordGenerator() },
|
|
||||||
{ name: 'pdf', ext: 'pdf', fmt: 'html', is: false, gen: new FLUENT.HtmlPdfGenerator() },
|
|
||||||
{ name: 'md', ext: 'md', fmt: 'txt', gen: new FLUENT.MarkdownGenerator() },
|
|
||||||
{ name: 'json', ext: 'json', gen: new FLUENT.JsonGenerator() },
|
|
||||||
{ name: 'yml', ext: 'yml', fmt: 'yml', gen: new FLUENT.JsonYamlGenerator() },
|
|
||||||
{ name: 'latex', ext: 'tex', fmt: 'latex', gen: new FLUENT.LaTeXGenerator() }
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
Default FluentCV options.
|
|
||||||
*/
|
|
||||||
var _opts = {
|
|
||||||
theme: 'modern',
|
|
||||||
prettify: { // ← See https://github.com/beautify-web/js-beautify#options
|
|
||||||
indent_size: 2,
|
|
||||||
unformatted: ['em','strong'],
|
|
||||||
max_char: 80, // ← See lib/html.js in above-linked repo
|
|
||||||
//wrap_line_length: 120, ← Don't use this
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
Internal module interface. Used by FCV Desktop and HMR.
|
|
||||||
*/
|
|
||||||
return {
|
|
||||||
verbs: {
|
|
||||||
build: generate,
|
|
||||||
validate: validate,
|
|
||||||
convert: convert,
|
|
||||||
new: create,
|
|
||||||
help: help
|
|
||||||
},
|
|
||||||
lib: require('./fluentlib'),
|
|
||||||
options: _opts,
|
|
||||||
formats: _fmts
|
|
||||||
};
|
|
||||||
|
|
||||||
}();
|
|
||||||
|
|
||||||
}());
|
|
||||||
|
|
||||||
// [1]: JSON.parse throws SyntaxError on invalid JSON. See:
|
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse
|
|
@ -1,6 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
Base resume generator for FluentCV.
|
Definition of the BaseGenerator class.
|
||||||
@license Copyright (c) 2015 | James M. Devlin
|
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
|
||||||
|
@module base-generator.js
|
||||||
*/
|
*/
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
HTML resume generator for FluentCV.
|
Definition of the HTMLGenerator class.
|
||||||
@license Copyright (c) 2015 James M. Devlin / FluentDesk
|
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
|
||||||
|
@module html-generator.js
|
||||||
*/
|
*/
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
@ -21,16 +22,6 @@ HTML resume generator for FluentCV.
|
|||||||
the HTML resume prior to saving.
|
the HTML resume prior to saving.
|
||||||
*/
|
*/
|
||||||
onBeforeSave: function( info ) {
|
onBeforeSave: function( info ) {
|
||||||
var cssSrc = PATH.join( info.theme.folder, 'src', '*.css' )
|
|
||||||
, outFolder = PATH.parse( info.outputFile ).dir, that = this;
|
|
||||||
|
|
||||||
info.theme.cssFiles.forEach( function( f ) {
|
|
||||||
var fi = PATH.parse( f.path );
|
|
||||||
FS.copySync( f.path, PATH.join( outFolder, fi.base ), { clobber: true }, function( e ) {
|
|
||||||
throw { fluenterror: that.codes.copyCss, data: [cssSrc,cssDst] };
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return this.opts.prettify ?
|
return this.opts.prettify ?
|
||||||
HTML.prettyPrint( info.mk, this.opts.prettify ) : info.mk;
|
HTML.prettyPrint( info.mk, this.opts.prettify ) : info.mk;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
Definition of the HtmlPdfGenerator class.
|
Definition of the HtmlPdfGenerator class.
|
||||||
@license Copyright (c) 2015 James M. Devlin / FluentDesk
|
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
|
||||||
|
@module html-pdf-generator.js
|
||||||
*/
|
*/
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
@ -10,7 +11,7 @@ Definition of the HtmlPdfGenerator class.
|
|||||||
, HTML = require( 'html' );
|
, HTML = require( 'html' );
|
||||||
|
|
||||||
/**
|
/**
|
||||||
An HTML-based PDF resume generator for FluentCV.
|
An HTML-based PDF resume generator for HackMyResume.
|
||||||
*/
|
*/
|
||||||
var HtmlPdfGenerator = module.exports = TemplateGenerator.extend({
|
var HtmlPdfGenerator = module.exports = TemplateGenerator.extend({
|
||||||
|
|
||||||
@ -22,8 +23,8 @@ Definition of the HtmlPdfGenerator class.
|
|||||||
Generate the binary PDF.
|
Generate the binary PDF.
|
||||||
*/
|
*/
|
||||||
onBeforeSave: function( info ) {
|
onBeforeSave: function( info ) {
|
||||||
pdf(info.mk, info.outputFile);
|
pdf( info.mk, info.outputFile );
|
||||||
return info.mk;
|
return null; // halt further processing
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
Definition of the JsonGenerator class.
|
Definition of the JsonGenerator class.
|
||||||
@license Copyright (c) 2015 | James M. Devlin
|
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
|
||||||
|
@module json-generator.js
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var BaseGenerator = require('./base-generator');
|
var BaseGenerator = require('./base-generator');
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
A JSON-driven YAML resume generator for FluentLib.
|
Definition of the JsonYamlGenerator class.
|
||||||
@module json-yaml-generator.js
|
@module json-yaml-generator.js
|
||||||
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk
|
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
LaTeX resume generator for FluentCV.
|
Definition of the LaTeXGenerator class.
|
||||||
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk
|
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
|
||||||
|
@module latex-generator.js
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var TemplateGenerator = require('./template-generator');
|
var TemplateGenerator = require('./template-generator');
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
Markdown resume generator for FluentCV.
|
Definition of the MarkdownGenerator class.
|
||||||
@license Copyright (c) 2015 by James M. Devlin. All rights reserved.
|
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
|
||||||
|
@module markdown-generator.js
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var TemplateGenerator = require('./template-generator');
|
var TemplateGenerator = require('./template-generator');
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
Template-based resume generator base for FluentCV.
|
Definition of the TemplateGenerator class.
|
||||||
@license MIT. Copyright (c) 2015 James M. Devlin / FluentDesk.
|
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
|
||||||
|
@module template-generator.js
|
||||||
*/
|
*/
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
@ -37,7 +38,9 @@ Template-based resume generator base for FluentCV.
|
|||||||
raw: function( txt ) { return txt; },
|
raw: function( txt ) { return txt; },
|
||||||
xml: function( txt ) { return XML(txt); },
|
xml: function( txt ) { return XML(txt); },
|
||||||
md: function( txt ) { return MD( txt || '' ); },
|
md: function( txt ) { return MD( txt || '' ); },
|
||||||
mdin: function( txt ) { return MD(txt || '' ).replace(/^\s*<p>|<\/p>\s*$/gi, ''); },
|
mdin: function( txt ) {
|
||||||
|
return MD(txt || '' ).replace(/^\s*<p>|<\/p>\s*$/gi, '');
|
||||||
|
},
|
||||||
lower: function( txt ) { return txt.toLowerCase(); },
|
lower: function( txt ) { return txt.toLowerCase(); },
|
||||||
link: function( name, url ) { return url ?
|
link: function( name, url ) { return url ?
|
||||||
'<a href="' + url + '">' + name + '</a>' : name; }
|
'<a href="' + url + '">' + name + '</a>' : name; }
|
||||||
@ -68,24 +71,14 @@ Template-based resume generator base for FluentCV.
|
|||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
invoke: function( rez, themeMarkup, cssInfo, opts ) {
|
|
||||||
this.opts = EXTEND( true, {}, _defaultOpts, opts );
|
|
||||||
mk = this.single( rez, themeMarkup, this.format, cssInfo, { } );
|
|
||||||
this.onBeforeSave && (mk = this.onBeforeSave( mk, themeFile, f ));
|
|
||||||
return mk;
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Default generation method for template-based generators.
|
String-based template generation method.
|
||||||
@method generate
|
@method invoke
|
||||||
@param rez A FreshResume object.
|
@param rez A FreshResume object.
|
||||||
@param f Full path to the output resume file to generate.
|
|
||||||
@param opts Generator options.
|
@param opts Generator options.
|
||||||
|
@returns An array of strings representing generated output files.
|
||||||
*/
|
*/
|
||||||
generate: function( rez, f, opts ) {
|
invoke: function( rez, opts ) {
|
||||||
|
|
||||||
// Carry over options
|
// Carry over options
|
||||||
this.opts = EXTEND( true, { }, _defaultOpts, opts );
|
this.opts = EXTEND( true, { }, _defaultOpts, opts );
|
||||||
@ -95,20 +88,81 @@ Template-based resume generator base for FluentCV.
|
|||||||
var theme = themeInfo.theme;
|
var theme = themeInfo.theme;
|
||||||
var tFolder = themeInfo.folder;
|
var tFolder = themeInfo.folder;
|
||||||
var tplFolder = PATH.join( tFolder, 'src' );
|
var tplFolder = PATH.join( tFolder, 'src' );
|
||||||
|
var curFmt = theme.getFormat( this.format );
|
||||||
|
var that = this;
|
||||||
|
|
||||||
|
// "Generate": process individual files within the theme
|
||||||
|
return {
|
||||||
|
files: curFmt.files.map( function( tplInfo ) {
|
||||||
|
return {
|
||||||
|
info: tplInfo,
|
||||||
|
data: tplInfo.action === 'transform' ?
|
||||||
|
transform.call( that, rez, tplInfo, theme ) : undefined
|
||||||
|
};
|
||||||
|
}).filter(function(item){ return item !== null; }),
|
||||||
|
themeInfo: themeInfo
|
||||||
|
};
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
File-based template generation method.
|
||||||
|
@method generate
|
||||||
|
@param rez A FreshResume object.
|
||||||
|
@param f Full path to the output resume file to generate.
|
||||||
|
@param opts Generator options.
|
||||||
|
*/
|
||||||
|
generate: function( rez, f, opts ) {
|
||||||
|
|
||||||
|
// Call the generation method
|
||||||
|
var genInfo = this.invoke( rez, opts );
|
||||||
|
|
||||||
|
// Carry over options
|
||||||
|
this.opts = EXTEND( true, { }, _defaultOpts, opts );
|
||||||
|
|
||||||
|
// Load the theme
|
||||||
|
var themeInfo = genInfo.themeInfo;
|
||||||
|
var theme = themeInfo.theme;
|
||||||
|
var tFolder = themeInfo.folder;
|
||||||
|
var tplFolder = PATH.join( tFolder, 'src' );
|
||||||
var outFolder = PATH.parse(f).dir;
|
var outFolder = PATH.parse(f).dir;
|
||||||
var curFmt = theme.getFormat( this.format );
|
var curFmt = theme.getFormat( this.format );
|
||||||
var that = this;
|
var that = this;
|
||||||
|
|
||||||
// "Generate": process individual files within the theme
|
// "Generate": process individual files within the theme
|
||||||
curFmt.files.forEach(function(tplInfo){
|
genInfo.files.forEach(function( file ){
|
||||||
if( tplInfo.action === 'transform' ) {
|
|
||||||
transform.call( that, rez, f, tplInfo, theme, outFolder );
|
var thisFilePath;
|
||||||
|
|
||||||
|
if( file.info.action === 'transform' ) {
|
||||||
|
thisFilePath = PATH.join( outFolder, file.info.orgPath );
|
||||||
|
try {
|
||||||
|
if( that.onBeforeSave ) {
|
||||||
|
file.data = that.onBeforeSave({
|
||||||
|
theme: theme,
|
||||||
|
outputFile: (file.info.major ? f : thisFilePath),
|
||||||
|
mk: file.data
|
||||||
|
});
|
||||||
|
if( !file.data ) return; // PDF etc
|
||||||
}
|
}
|
||||||
else if( tplInfo.action === null && theme.explicit ) {
|
var fileName = file.info.major ? f : thisFilePath;
|
||||||
var thisFilePath = PATH.join(outFolder, tplInfo.orgPath);
|
MKDIRP.sync( PATH.dirname( fileName ) );
|
||||||
|
FS.writeFileSync( fileName, file.data,
|
||||||
|
{ encoding: 'utf8', flags: 'w' } );
|
||||||
|
that.onAfterSave && that.onAfterSave(
|
||||||
|
{ outputFile: fileName, mk: file.data } );
|
||||||
|
}
|
||||||
|
catch(ex) {
|
||||||
|
console.log(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if( file.info.action === null/* && theme.explicit*/ ) {
|
||||||
|
thisFilePath = PATH.join( outFolder, file.info.orgPath );
|
||||||
try {
|
try {
|
||||||
MKDIRP.sync( PATH.dirname(thisFilePath) );
|
MKDIRP.sync( PATH.dirname(thisFilePath) );
|
||||||
FS.copySync( tplInfo.path, thisFilePath );
|
FS.copySync( file.info.path, thisFilePath );
|
||||||
}
|
}
|
||||||
catch(ex) {
|
catch(ex) {
|
||||||
console.log(ex);
|
console.log(ex);
|
||||||
@ -141,9 +195,10 @@ Template-based resume generator base for FluentCV.
|
|||||||
*/
|
*/
|
||||||
single: function( json, jst, format, cssInfo, opts, theme ) {
|
single: function( json, jst, format, cssInfo, opts, theme ) {
|
||||||
this.opts.freezeBreaks && ( jst = freeze(jst) );
|
this.opts.freezeBreaks && ( jst = freeze(jst) );
|
||||||
var eng = require( '../eng/' + ((opts.themeObj && opts.themeObj.engine) ||
|
|
||||||
opts.engine) + '-generator' );
|
var eng = require( '../eng/' + theme.engine + '-generator' );
|
||||||
var result = eng( json, jst, format, cssInfo, opts, theme );
|
var result = eng.generate( json, jst, format, cssInfo, opts, theme );
|
||||||
|
|
||||||
this.opts.freezeBreaks && ( result = unfreeze(result) );
|
this.opts.freezeBreaks && ( result = unfreeze(result) );
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@ -188,18 +243,15 @@ Template-based resume generator base for FluentCV.
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
function transform( rez, tplInfo, theme ) {
|
||||||
Transform a single subfile.
|
|
||||||
*/
|
|
||||||
function transform( rez, f, tplInfo, theme, outFolder ) {
|
|
||||||
var cssInfo = { file: tplInfo.css ? tplInfo.cssPath : null, data: tplInfo.css || null };
|
|
||||||
var mk = this.single( rez, tplInfo.data, this.format, cssInfo, this.opts, theme );
|
|
||||||
this.onBeforeSave && (mk = this.onBeforeSave( { mk: mk, theme: theme, outputFile: f } ));
|
|
||||||
var thisFilePath = PATH.join( outFolder, tplInfo.orgPath );
|
|
||||||
try {
|
try {
|
||||||
MKDIRP.sync( PATH.dirname( tplInfo.major ? f : thisFilePath) );
|
var cssInfo = {
|
||||||
FS.writeFileSync( tplInfo.major ? f : thisFilePath, mk, { encoding: 'utf8', flags: 'w' } );
|
file: tplInfo.css ? tplInfo.cssPath : null,
|
||||||
this.onAfterSave && (mk = this.onAfterSave( { outputFile: (tplInfo.major ? f : thisFilePath), mk: mk } ));
|
data: tplInfo.css || null
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.single( rez, tplInfo.data, this.format, cssInfo, this.opts,
|
||||||
|
theme );
|
||||||
}
|
}
|
||||||
catch(ex) {
|
catch(ex) {
|
||||||
console.log(ex);
|
console.log(ex);
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
Plain text resume generator for FluentCV.
|
Definition of the TextGenerator class.
|
||||||
@license Copyright (c) 2015 by James M. Devlin. All rights reserved.
|
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
|
||||||
|
@module text-generator.js
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var TemplateGenerator = require('./template-generator');
|
var TemplateGenerator = require('./template-generator');
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
MS Word resume generator for FluentCV.
|
Definition of the WordGenerator class.
|
||||||
@license Copyright (c) 2015 by James M. Devlin. All rights reserved.
|
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
|
||||||
|
@module word-generator.js
|
||||||
*/
|
*/
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
XML resume generator for FluentCV.
|
Definition of the XMLGenerator class.
|
||||||
@license Copyright (c) 2015 | James M. Devlin
|
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
|
||||||
|
@module xml-generator.js
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var BaseGenerator = require('./base-generator');
|
var BaseGenerator = require('./base-generator');
|
||||||
@ -8,7 +9,7 @@ var BaseGenerator = require('./base-generator');
|
|||||||
/**
|
/**
|
||||||
The XmlGenerator generates an XML resume via the TemplateGenerator.
|
The XmlGenerator generates an XML resume via the TemplateGenerator.
|
||||||
*/
|
*/
|
||||||
var XmlGenerator = module.exports = BaseGenerator.extend({
|
var XMLGenerator = module.exports = BaseGenerator.extend({
|
||||||
|
|
||||||
init: function(){
|
init: function(){
|
||||||
this._super( 'xml' );
|
this._super( 'xml' );
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
A YAML resume generator for FluentLib.
|
Definition of the YAMLGenerator class.
|
||||||
@module yaml-generator.js
|
@module yaml-generator.js
|
||||||
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk
|
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
@ -13,7 +13,7 @@ A YAML resume generator for FluentLib.
|
|||||||
YamlGenerator generates a YAML-formatted resume via TemplateGenerator.
|
YamlGenerator generates a YAML-formatted resume via TemplateGenerator.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var YamlGenerator = module.exports = TemplateGenerator.extend({
|
var YAMLGenerator = module.exports = TemplateGenerator.extend({
|
||||||
|
|
||||||
init: function(){
|
init: function(){
|
||||||
this._super( 'yml', 'yml' );
|
this._super( 'yml', 'yml' );
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
External API surface for FluentCV:CLI.
|
External API surface for HackMyResume.
|
||||||
@license MIT. Copyright (c) 2015 James M. Devlin / FluentDesk
|
@license MIT. Copyright (c) 2015 James M. Devlin / FluentDesk.
|
||||||
|
@module hackmyapi.js
|
||||||
*/
|
*/
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
45
src/hackmycmd.js
Normal file
45
src/hackmycmd.js
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
/**
|
||||||
|
Internal resume generation logic for HackMyResume.
|
||||||
|
@license MIT. Copyright (c) 2015 James M. Devlin / FluentDesk.
|
||||||
|
@module hackmycmd.js
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
module.exports = function () {
|
||||||
|
|
||||||
|
var unused = require('./utils/string')
|
||||||
|
, PATH = require('path');
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
Display help documentation.
|
||||||
|
*/
|
||||||
|
function help() {
|
||||||
|
console.log( FS.readFileSync( PATH.join(__dirname, 'use.txt'), 'utf8' )
|
||||||
|
.useful.bold );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Internal module interface. Used by FCV Desktop and HMR.
|
||||||
|
*/
|
||||||
|
return {
|
||||||
|
verbs: {
|
||||||
|
generate: require('./verbs/generate'),
|
||||||
|
build: require('./verbs/generate'),
|
||||||
|
validate: require('./verbs/validate'),
|
||||||
|
convert: require('./verbs/convert'),
|
||||||
|
create: require('./verbs/create'),
|
||||||
|
new: require('./verbs/create'),
|
||||||
|
help: help
|
||||||
|
},
|
||||||
|
lib: require('./hackmyapi'),
|
||||||
|
options: require('./core/default-options'),
|
||||||
|
formats: require('./core/default-formats')
|
||||||
|
};
|
||||||
|
|
||||||
|
}();
|
||||||
|
|
||||||
|
}());
|
||||||
|
|
||||||
|
// [1]: JSON.parse throws SyntaxError on invalid JSON. See:
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse
|
15
src/index.js
15
src/index.js
@ -1,18 +1,19 @@
|
|||||||
#! /usr/bin/env node
|
#! /usr/bin/env node
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Command-line interface (CLI) for FluentCV:CLI.
|
Command-line interface (CLI) for HackMyResume.
|
||||||
@license MIT. Copyright (c) 2015 James M. Devlin / FluentDesk.
|
@license MIT. Copyright (c) 2015 James M. Devlin / FluentDesk.
|
||||||
|
@module index.js
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var ARGS = require( 'minimist' )
|
var ARGS = require( 'minimist' )
|
||||||
, FCMD = require( './fluentcmd')
|
, FCMD = require( './hackmycmd')
|
||||||
, PKG = require('../package.json')
|
, PKG = require('../package.json')
|
||||||
, COLORS = require('colors')
|
, COLORS = require('colors')
|
||||||
, FS = require('fs')
|
, FS = require('fs')
|
||||||
, PATH = require('path')
|
, PATH = require('path')
|
||||||
, opts = { }
|
, opts = { }
|
||||||
, title = ('*** FluentCV v' + PKG.version + ' ***').bold.white
|
, title = ('\n*** HackMyResume v' + PKG.version + ' ***').bold.white
|
||||||
, _ = require('underscore');
|
, _ = require('underscore');
|
||||||
|
|
||||||
|
|
||||||
@ -46,7 +47,6 @@ function main() {
|
|||||||
opts = getOpts( a );
|
opts = getOpts( a );
|
||||||
logMsg( title );
|
logMsg( title );
|
||||||
|
|
||||||
|
|
||||||
// Get the action to be performed
|
// Get the action to be performed
|
||||||
var params = a._.map( function(p){ return p.toLowerCase().trim(); });
|
var params = a._.map( function(p){ return p.toLowerCase().trim(); });
|
||||||
var verb = params[0];
|
var verb = params[0];
|
||||||
@ -55,7 +55,7 @@ function main() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get source and dest params
|
// Find the TO keyword, if any
|
||||||
var splitAt = _.indexOf( params, 'to' );
|
var splitAt = _.indexOf( params, 'to' );
|
||||||
if( splitAt === a._.length - 1 ) {
|
if( splitAt === a._.length - 1 ) {
|
||||||
// 'TO' cannot be the last argument
|
// 'TO' cannot be the last argument
|
||||||
@ -65,8 +65,10 @@ function main() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Massage inputs and outputs
|
||||||
var src = a._.slice(1, splitAt === -1 ? undefined : splitAt );
|
var src = a._.slice(1, splitAt === -1 ? undefined : splitAt );
|
||||||
var dst = splitAt === -1 ? [] : a._.slice( splitAt + 1 );
|
var dst = splitAt === -1 ? [] : a._.slice( splitAt + 1 );
|
||||||
|
( splitAt === -1 ) && src.length > 1 && dst.push( src.pop() ); // Allow omitting TO keyword
|
||||||
var parms = [ src, dst, opts, logMsg ];
|
var parms = [ src, dst, opts, logMsg ];
|
||||||
|
|
||||||
// Invoke the action
|
// Invoke the action
|
||||||
@ -106,9 +108,10 @@ function handleError( ex ) {
|
|||||||
}).join(', '.guide) + ").\n\n".guide + FS.readFileSync( PATH.join(__dirname, 'use.txt'), 'utf8' ).info.bold;
|
}).join(', '.guide) + ").\n\n".guide + FS.readFileSync( PATH.join(__dirname, 'use.txt'), 'utf8' ).info.bold;
|
||||||
break;
|
break;
|
||||||
//case 4: msg = title + '\n' + ; break;
|
//case 4: msg = title + '\n' + ; break;
|
||||||
case 5: msg = 'Please '.guide + 'specify the output resume file'.guide.bold + ' that should be created in the new format.'.guide; break;
|
case 5: msg = 'Please '.guide + 'specify the output resume file'.guide.bold + ' that should be created.'.guide; break;
|
||||||
case 6: msg = 'Please '.guide + 'specify a valid input resume'.guide.bold + ' in either FRESH or JSON Resume format.'.guide; break;
|
case 6: msg = 'Please '.guide + 'specify a valid input resume'.guide.bold + ' in either FRESH or JSON Resume format.'.guide; break;
|
||||||
case 7: msg = 'Please '.guide + 'specify an output file name'.guide.bold + ' for every input file you wish to convert.'.guide; break;
|
case 7: msg = 'Please '.guide + 'specify an output file name'.guide.bold + ' for every input file you wish to convert.'.guide; break;
|
||||||
|
case 8: msg = 'Please '.guide + 'specify the filename of the resume'.guide.bold + ' to create.'.guide; break;
|
||||||
}
|
}
|
||||||
exitCode = ex.fluenterror;
|
exitCode = ex.fluenterror;
|
||||||
|
|
||||||
|
18
src/use.txt
18
src/use.txt
@ -1,6 +1,6 @@
|
|||||||
Usage:
|
Usage:
|
||||||
|
|
||||||
fluentcv <COMMAND> <SOURCES> [TO <TARGETS>] [-t <THEME>] [-f <FORMAT>]
|
hackmyresume <COMMAND> <SOURCES> [TO <TARGETS>] [-t <THEME>] [-f <FORMAT>]
|
||||||
|
|
||||||
<COMMAND> should be BUILD, NEW, CONVERT, VALIDATE, or HELP. <SOURCES> should
|
<COMMAND> should be BUILD, NEW, CONVERT, VALIDATE, or HELP. <SOURCES> should
|
||||||
be the path to one or more FRESH or JSON Resume format resumes. <TARGETS>
|
be the path to one or more FRESH or JSON Resume format resumes. <TARGETS>
|
||||||
@ -10,16 +10,16 @@ COMPACT, MINIMIST, MODERN, or HELLO-WORLD) or the relative path to a custom
|
|||||||
theme. <FORMAT> should be either FRESH (for a FRESH-format resume) or JRS
|
theme. <FORMAT> should be either FRESH (for a FRESH-format resume) or JRS
|
||||||
(for a JSON Resume-format resume).
|
(for a JSON Resume-format resume).
|
||||||
|
|
||||||
fluentcv BUILD resume.json TO out/resume.all
|
hackmyresume BUILD resume.json TO out/resume.all
|
||||||
fluentcv NEW resume.json
|
hackmyresume NEW resume.json
|
||||||
fluentcv CONVERT resume.json TO resume-jrs.json
|
hackmyresume CONVERT resume.json TO resume-jrs.json
|
||||||
fluentcv VALIDATE resume.json
|
hackmyresume VALIDATE resume.json
|
||||||
|
|
||||||
Both SOURCES and TARGETS can accept multiple files:
|
Both SOURCES and TARGETS can accept multiple files:
|
||||||
|
|
||||||
fluentCV BUILD r1.json r2.json TO out/resume.all out2/resume.html
|
hackmyresume BUILD r1.json r2.json TO out/resume.all out2/resume.html
|
||||||
fluentCV NEW r1.json r2.json r3.json
|
hackmyresume NEW r1.json r2.json r3.json
|
||||||
fluentCV VALIDATE resume.json resume2.json resume3.json
|
hackmyresume VALIDATE resume.json resume2.json resume3.json
|
||||||
|
|
||||||
See https://github.com/fluentdesk/fluentCV/blob/master/README.md for more
|
See https://github.com/hacksalot/hackmyresume/blob/master/README.md for more
|
||||||
information.
|
information.
|
||||||
|
@ -1,3 +1,8 @@
|
|||||||
|
/**
|
||||||
|
Definition of John Resig's `Class` class.
|
||||||
|
@module class.js
|
||||||
|
*/
|
||||||
|
|
||||||
/* Simple JavaScript Inheritance
|
/* Simple JavaScript Inheritance
|
||||||
* By John Resig http://ejohn.org/
|
* By John Resig http://ejohn.org/
|
||||||
* MIT Licensed.
|
* MIT Licensed.
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
Plain JavaScript replacement of jQuery .extend based on jQuery sources.
|
Definition of the `extend` method.
|
||||||
@license Copyright (c) 2015 by James M. Devlin. All rights reserved.
|
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
|
||||||
|
@module extend.js
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function _extend() {
|
function _extend() {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
File-exists checker for Node.js.
|
Definition of the `fileExists` method.
|
||||||
@license Copyright (c) 2015 | James M. Devlin
|
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
|
||||||
|
@module file-exists.js
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var FS = require('fs');
|
var FS = require('fs');
|
||||||
|
@ -1,3 +1,8 @@
|
|||||||
|
/**
|
||||||
|
Definition of the Markdown to WordProcessingML conversion routine.
|
||||||
|
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
|
||||||
|
@module html-to-wpml.js
|
||||||
|
*/
|
||||||
|
|
||||||
(function(){
|
(function(){
|
||||||
|
|
||||||
@ -6,49 +11,49 @@
|
|||||||
|
|
||||||
module.exports = function( html ) {
|
module.exports = function( html ) {
|
||||||
|
|
||||||
var final = '';
|
// Tokenize the HTML stream.
|
||||||
var is_bold = false, is_italic = false;
|
|
||||||
var depth = 0;
|
|
||||||
|
|
||||||
var tokens = HTML5Tokenizer.tokenize( html );
|
var tokens = HTML5Tokenizer.tokenize( html );
|
||||||
|
|
||||||
|
var final = '', is_bold, is_italic, is_link, link_url;
|
||||||
|
|
||||||
|
// Process <em>, <strong>, and <a> elements in the HTML stream, producing
|
||||||
|
// equivalent WordProcessingML that can be dumped into a <w:p> or other
|
||||||
|
// text container element.
|
||||||
_.each( tokens, function( tok ) {
|
_.each( tokens, function( tok ) {
|
||||||
|
|
||||||
switch( tok.type ) {
|
switch( tok.type ) {
|
||||||
|
|
||||||
case 'StartTag':
|
case 'StartTag':
|
||||||
switch( tok.tagName ) {
|
switch( tok.tagName ) {
|
||||||
case 'p':
|
case 'p': final += '<w:p>'; break;
|
||||||
final += '<w:p>';
|
case 'strong': is_bold = true; break;
|
||||||
break;
|
case 'em': is_italic = true; break;
|
||||||
case 'strong':
|
|
||||||
is_bold = true;
|
|
||||||
break;
|
|
||||||
case 'em':
|
|
||||||
is_italic = true;
|
|
||||||
break;
|
|
||||||
case 'a':
|
case 'a':
|
||||||
is_link = true;
|
is_link = true;
|
||||||
|
link_url = tok.attributes.filter(function(attr){
|
||||||
|
return attr[0] === 'href'; }
|
||||||
|
)[0][1];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'EndTag':
|
case 'EndTag':
|
||||||
switch( tok.tagName ) {
|
switch( tok.tagName ) {
|
||||||
case 'p':
|
case 'p': final += '</w:p>'; break;
|
||||||
final += '</w:p>';
|
case 'strong': is_bold = false; break;
|
||||||
break;
|
case 'em': is_italic = false; break;
|
||||||
case 'strong':
|
case 'a': is_link = false; break;
|
||||||
is_bold = false;
|
|
||||||
break;
|
|
||||||
case 'em':
|
|
||||||
is_italic = false;
|
|
||||||
break;
|
|
||||||
case 'a':
|
|
||||||
is_link = false;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'Chars':
|
case 'Chars':
|
||||||
var style = is_bold ? '<w:b/>' : '';
|
var style = is_bold ? '<w:b/>' : '';
|
||||||
style += is_italic ? '<w:i/>': '';
|
style += is_italic ? '<w:i/>': '';
|
||||||
final += '<w:r><w:rPr>' + style + '</w:rPr><w:t>' + tok.chars + '</w:t></w:r>';
|
style += is_link ? '<w:rStyle w:val="Hyperlink"/>' : '';
|
||||||
|
final +=
|
||||||
|
(is_link ? ('<w:hlink w:dest="' + link_url + '">') : '') +
|
||||||
|
'<w:r><w:rPr>' + style + '</w:rPr><w:t>' + tok.chars +
|
||||||
|
'</w:t></w:r>' + (is_link ? '</w:hlink>' : '');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
String utility functions.
|
Definitions of string utility functions.
|
||||||
@license Copyright (c) 2015 | James M. Devlin
|
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
|
||||||
|
@module string.js
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -16,3 +17,7 @@ String.isNullOrWhitespace = function( input ) {
|
|||||||
String.prototype.endsWith = function(suffix) {
|
String.prototype.endsWith = function(suffix) {
|
||||||
return this.indexOf(suffix, this.length - suffix.length) !== -1;
|
return this.indexOf(suffix, this.length - suffix.length) !== -1;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
String.is = function( val ) {
|
||||||
|
return typeof val === 'string' || val instanceof String;
|
||||||
|
};
|
||||||
|
30
src/verbs/convert.js
Normal file
30
src/verbs/convert.js
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
(function(){
|
||||||
|
|
||||||
|
var loadSourceResumes = require('../core/load-source-resumes');
|
||||||
|
|
||||||
|
/**
|
||||||
|
Convert between FRESH and JRS formats.
|
||||||
|
*/
|
||||||
|
module.exports = function convert( src, dst, opts, logger ) {
|
||||||
|
var _log = logger || console.log;
|
||||||
|
if( !src || !src.length ) { throw { fluenterror: 6 }; }
|
||||||
|
if( !dst || !dst.length ) {
|
||||||
|
if( src.length === 1 ) { throw { fluenterror: 5 }; }
|
||||||
|
else if( src.length === 2 ) { dst = [ src[1] ]; src = [ src[0] ]; }
|
||||||
|
else { throw { fluenterror: 5 }; }
|
||||||
|
}
|
||||||
|
if( src && dst && src.length && dst.length && src.length !== dst.length ) {
|
||||||
|
throw { fluenterror: 7 };
|
||||||
|
}
|
||||||
|
var sheets = loadSourceResumes( src, _log );
|
||||||
|
sheets.forEach(function(sheet, idx){
|
||||||
|
var sourceFormat = sheet.imp.orgFormat === 'JRS' ? 'JRS' : 'FRESH';
|
||||||
|
var targetFormat = sourceFormat === 'JRS' ? 'FRESH' : 'JRS';
|
||||||
|
_log( 'Converting '.useful + sheet.imp.fileName.useful.bold + (' (' +
|
||||||
|
sourceFormat + ') to ').useful + dst[0].useful.bold +
|
||||||
|
(' (' + targetFormat + ').').useful );
|
||||||
|
sheet.saveAs( dst[idx], targetFormat );
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
}());
|
22
src/verbs/create.js
Normal file
22
src/verbs/create.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
(function(){
|
||||||
|
|
||||||
|
var FLUENT = require('../hackmyapi')
|
||||||
|
, MKDIRP = require('mkdirp')
|
||||||
|
, PATH = require('path');
|
||||||
|
|
||||||
|
/**
|
||||||
|
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 };
|
||||||
|
src.forEach( function( t ) {
|
||||||
|
var safeFormat = opts.format.toUpperCase();
|
||||||
|
_log('Creating new '.useful +safeFormat.useful.bold +
|
||||||
|
' resume: '.useful + t.useful.bold);
|
||||||
|
MKDIRP.sync( PATH.dirname( t ) ); // Ensure dest folder exists;
|
||||||
|
FLUENT[ safeFormat + 'Resume' ].default().save( t );
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
}());
|
149
src/verbs/generate.js
Normal file
149
src/verbs/generate.js
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
(function() {
|
||||||
|
|
||||||
|
var PATH = require('path')
|
||||||
|
, MKDIRP = require('mkdirp')
|
||||||
|
, _opts = require('../core/default-options')
|
||||||
|
, FluentTheme = require('../core/theme')
|
||||||
|
, loadSourceResumes = require('../core/load-source-resumes')
|
||||||
|
, _ = require('underscore')
|
||||||
|
, _fmts = require('../core/default-formats')
|
||||||
|
, _err, _log, rez;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Handle an exception.
|
||||||
|
*/
|
||||||
|
function error( ex ) {
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports =
|
||||||
|
|
||||||
|
/**
|
||||||
|
Given a source JSON resume, a destination resume path, and a theme file,
|
||||||
|
generate 0..N resumes in the desired formats.
|
||||||
|
@param src Path to the source JSON resume file: "rez/resume.json".
|
||||||
|
@param dst An array of paths to the target resume file(s).
|
||||||
|
@param theme Friendly name of the resume theme. Defaults to "modern".
|
||||||
|
@param logger Optional logging override.
|
||||||
|
*/
|
||||||
|
function generate( src, dst, opts, logger, errHandler ) {
|
||||||
|
|
||||||
|
_log = logger || console.log;
|
||||||
|
_err = errHandler || error;
|
||||||
|
|
||||||
|
//_opts = extend( true, _opts, opts );
|
||||||
|
_opts.theme = (opts.theme && opts.theme.toLowerCase().trim())|| 'modern';
|
||||||
|
_opts.prettify = opts.prettify === true ? _opts.prettify : false;
|
||||||
|
|
||||||
|
// Load input resumes...
|
||||||
|
if( !src || !src.length ) { throw { fluenterror: 3 }; }
|
||||||
|
var sheets = loadSourceResumes( src, _log );
|
||||||
|
|
||||||
|
// Merge input resumes...
|
||||||
|
var msg = '';
|
||||||
|
rez = _.reduceRight( sheets, function( a, b, idx ) {
|
||||||
|
msg += ((idx == sheets.length - 2) ?
|
||||||
|
'Merging '.gray+ a.imp.fileName : '') + ' onto '.gray + b.imp.fileName;
|
||||||
|
return extend( true, b, a );
|
||||||
|
});
|
||||||
|
msg && _log(msg);
|
||||||
|
|
||||||
|
// Verify the specified theme name/path
|
||||||
|
var relativeThemeFolder = '../../node_modules/fluent-themes/themes';
|
||||||
|
var tFolder = PATH.resolve( __dirname, relativeThemeFolder, _opts.theme);
|
||||||
|
var exists = require('../utils/file-exists');
|
||||||
|
if (!exists( tFolder )) {
|
||||||
|
tFolder = PATH.resolve( _opts.theme );
|
||||||
|
if (!exists( tFolder )) {
|
||||||
|
throw { fluenterror: 1, data: _opts.theme };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the theme
|
||||||
|
var theTheme = new FluentTheme().open( tFolder );
|
||||||
|
_opts.themeObj = theTheme;
|
||||||
|
_log( 'Applying '.info + theTheme.name.toUpperCase().infoBold +
|
||||||
|
(' theme (' + Object.keys(theTheme.formats).length + ' formats)').info);
|
||||||
|
|
||||||
|
// Expand output resumes... (can't use map() here)
|
||||||
|
var targets = [], that = this;
|
||||||
|
( (dst && dst.length && dst) || ['resume.all'] ).forEach( function(t) {
|
||||||
|
|
||||||
|
var to = PATH.resolve(t),
|
||||||
|
pa = PATH.parse(to),
|
||||||
|
fmat = pa.ext || '.all';
|
||||||
|
|
||||||
|
targets.push.apply(targets, fmat === '.all' ?
|
||||||
|
Object.keys( theTheme.formats ).map(function(k){
|
||||||
|
var z = theTheme.formats[k];
|
||||||
|
return { file: to.replace(/all$/g,z.outFormat), fmt: z };
|
||||||
|
}) : [{ file: to, fmt: theTheme.getFormat( fmat.slice(1) ) }]);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
// Run the transformation!
|
||||||
|
var finished = targets.map( function(t) { return single(t, theTheme); });
|
||||||
|
|
||||||
|
// Don't send the client back empty-handed
|
||||||
|
return { sheet: rez, targets: targets, processed: finished };
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
Generate a single resume of a specific format.
|
||||||
|
@param f Full path to the destination resume to generate, for example,
|
||||||
|
"/foo/bar/resume.pdf" or "c:\foo\bar\resume.txt".
|
||||||
|
*/
|
||||||
|
function single( targInfo, theme ) {
|
||||||
|
try {
|
||||||
|
var f = targInfo.file
|
||||||
|
, fType = targInfo.fmt.outFormat
|
||||||
|
, fName = PATH.basename(f, '.' + fType)
|
||||||
|
, theFormat;
|
||||||
|
|
||||||
|
// If targInfo.fmt.files exists, this theme has an explicit "files"
|
||||||
|
// section in its theme.json file.
|
||||||
|
if( targInfo.fmt.files && targInfo.fmt.files.length ) {
|
||||||
|
|
||||||
|
_log( 'Generating '.useful +
|
||||||
|
targInfo.fmt.outFormat.toUpperCase().useful.bold +
|
||||||
|
' resume: '.useful + PATH.relative(process.cwd(), f ).replace(/\\/g,'/').useful.bold);
|
||||||
|
|
||||||
|
theFormat = _fmts.filter(
|
||||||
|
function(fmt) { return fmt.name === targInfo.fmt.outFormat; })[0];
|
||||||
|
MKDIRP.sync( PATH.dirname( f ) ); // Ensure dest folder exists;
|
||||||
|
theFormat.gen.generate( rez, f, _opts );
|
||||||
|
|
||||||
|
// targInfo.fmt.files.forEach( function( form ) {
|
||||||
|
//
|
||||||
|
// if( form.action === 'transform' ) {
|
||||||
|
// var theFormat = _fmts.filter( function( fmt ) {
|
||||||
|
// return fmt.name === targInfo.fmt.outFormat;
|
||||||
|
// })[0];
|
||||||
|
// MKDIRP.sync( PATH.dirname( f ) ); // Ensure dest folder exists;
|
||||||
|
// theFormat.gen.generate( rez, f, _opts );
|
||||||
|
// }
|
||||||
|
// else if( form.action === null ) {
|
||||||
|
// // Copy the file
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// });
|
||||||
|
|
||||||
|
}
|
||||||
|
// Otherwise the theme has no files section
|
||||||
|
else {
|
||||||
|
_log( 'Generating '.useful +
|
||||||
|
targInfo.fmt.outFormat.toUpperCase().useful.bold +
|
||||||
|
' resume: '.useful + PATH.relative(process.cwd(), f ).replace(/\\/g,'/').useful.bold);
|
||||||
|
|
||||||
|
theFormat = _fmts.filter(
|
||||||
|
function(fmt) { return fmt.name === targInfo.fmt.outFormat; })[0];
|
||||||
|
MKDIRP.sync( PATH.dirname( f ) ); // Ensure dest folder exists;
|
||||||
|
theFormat.gen.generate( rez, f, _opts );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch( ex ) {
|
||||||
|
_err( ex );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}());
|
97
src/verbs/validate.js
Normal file
97
src/verbs/validate.js
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
(function() {
|
||||||
|
|
||||||
|
var FS = require('fs');
|
||||||
|
var loadSourceResumes = require('../core/load-source-resumes');
|
||||||
|
|
||||||
|
module.exports =
|
||||||
|
|
||||||
|
/**
|
||||||
|
Validate 1 to N resumes in either FRESH or JSON Resume format.
|
||||||
|
*/
|
||||||
|
function validate( src, unused, opts, logger ) {
|
||||||
|
var _log = logger || console.log;
|
||||||
|
if( !src || !src.length ) { throw { fluenterror: 6 }; }
|
||||||
|
var isValid = true;
|
||||||
|
|
||||||
|
var validator = require('is-my-json-valid');
|
||||||
|
var schemas = {
|
||||||
|
fresh: require('FRESCA'),
|
||||||
|
jars: require('../core/resume.json')
|
||||||
|
};
|
||||||
|
|
||||||
|
// Load input resumes...
|
||||||
|
var sheets = loadSourceResumes(src, _log, function( res ) {
|
||||||
|
try {
|
||||||
|
return {
|
||||||
|
file: res,
|
||||||
|
raw: FS.readFileSync( res, 'utf8' )
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch( ex ) {
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
sheets.forEach( function( rep ) {
|
||||||
|
|
||||||
|
var rez;
|
||||||
|
try {
|
||||||
|
rez = JSON.parse( rep.raw );
|
||||||
|
}
|
||||||
|
catch( ex ) { // Note [1]
|
||||||
|
_log('Validating '.info + rep.file.infoBold +
|
||||||
|
' against FRESH/JRS schema: '.info + 'ERROR!'.error.bold);
|
||||||
|
|
||||||
|
if (ex instanceof SyntaxError) {
|
||||||
|
// Invalid JSON
|
||||||
|
_log( '--> '.bold.red + rep.file.toUpperCase().red +
|
||||||
|
' contains invalid JSON. Unable to validate.'.red );
|
||||||
|
_log( (' INTERNAL: ' + ex).red );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
|
||||||
|
_log(('ERROR: ' + ex.toString()).red.bold);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var isValid = false;
|
||||||
|
var style = 'useful';
|
||||||
|
var errors = [];
|
||||||
|
var fmt = rez.meta &&
|
||||||
|
(rez.meta.format === 'FRESH@0.1.0') ? 'fresh':'jars';
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
var validate = validator( schemas[ fmt ], { // Note [1]
|
||||||
|
formats: {
|
||||||
|
date: /^\d{4}(?:-(?:0[0-9]{1}|1[0-2]{1})(?:-[0-9]{2})?)?$/
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
isValid = validate( rez );
|
||||||
|
if( !isValid ) {
|
||||||
|
style = 'warn';
|
||||||
|
errors = validate.errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
catch(ex) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_log( 'Validating '.info + rep.file.infoBold + ' against '.info +
|
||||||
|
fmt.replace('jars','JSON Resume').toUpperCase().infoBold +
|
||||||
|
' schema: '.info + (isValid ? 'VALID!' : 'INVALID')[style].bold );
|
||||||
|
|
||||||
|
errors.forEach(function(err,idx) {
|
||||||
|
_log( '--> '.bold.yellow +
|
||||||
|
(err.field.replace('data.','resume.').toUpperCase() + ' ' +
|
||||||
|
err.message).yellow );
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
}());
|
@ -1,14 +1,14 @@
|
|||||||
{
|
{
|
||||||
"basics": {
|
"basics": {
|
||||||
"name": "Jane Doe",
|
"name": "Jane Q. Fullstacker",
|
||||||
"label": "Senior Developer / Code Ninja",
|
"label": "Senior Developer",
|
||||||
"summary": "**Full-stack software developer with 6+ years industry experience** specializing in scalable cloud architectures for this, that, and the other. A native of southern CA, Jane enjoys hiking, mystery novels, and the company of Rufus, her two-year-old beagle.",
|
"summary": "**Full-stack software developer with 6+ years industry experience** specializing in scalable cloud architectures for this, that, and the other. A native of southern CA, Jane enjoys hiking, mystery novels, and the company of Rufus, her two-year-old beagle.",
|
||||||
"website": "http://jane-doe.me",
|
"website": "http://janef.me/blog",
|
||||||
"phone": "1-650-999-7777",
|
"phone": "1-650-999-7777",
|
||||||
"email": "jdoe@onecoolstartup.io",
|
"email": "jdoe@onecoolstartup.io",
|
||||||
"picture": "jane_doe.png",
|
"picture": "jane_doe.png",
|
||||||
"location": {
|
"location": {
|
||||||
"address": "Jane Doe\n123 Somewhere Rd.\nMountain View, CA 94035",
|
"address": "Jane Fullstacker\n123 Somewhere Rd.\nMountain View, CA 94035",
|
||||||
"postalCode": "94035",
|
"postalCode": "94035",
|
||||||
"city": "Mountain View",
|
"city": "Mountain View",
|
||||||
"countryCode": "US",
|
"countryCode": "US",
|
||||||
@ -17,13 +17,13 @@
|
|||||||
"profiles": [
|
"profiles": [
|
||||||
{
|
{
|
||||||
"network": "GitHub",
|
"network": "GitHub",
|
||||||
"username": "jane-doe-was-here",
|
"username": "janef-was-here",
|
||||||
"url": "https://github.com/jane-doe-was-here"
|
"url": "https://github.com/janef-was-here"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"network": "Twitter",
|
"network": "Twitter",
|
||||||
"username": "jane-doe-was-here",
|
"username": "janef-was-here",
|
||||||
"url": "https://twitter.com/jane-doe-was-here"
|
"url": "https://twitter.com/janef-was-here"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -104,17 +104,55 @@
|
|||||||
],
|
],
|
||||||
"skills": [
|
"skills": [
|
||||||
{
|
{
|
||||||
"name": "Programming",
|
"name": "Web Dev",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"C++",
|
"JavaScript",
|
||||||
"Ruby",
|
"HTML 5",
|
||||||
"Xcode"
|
"CSS",
|
||||||
|
"LAMP",
|
||||||
|
"MVC",
|
||||||
|
"REST"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Project Management",
|
"name": "JavaScript",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"Agile"
|
"Node.js",
|
||||||
|
"Angular.js",
|
||||||
|
"jQuery",
|
||||||
|
"Bootstrap",
|
||||||
|
"React.js",
|
||||||
|
"Backbone.js"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Database",
|
||||||
|
"keywords": [
|
||||||
|
"MySQL",
|
||||||
|
"PostgreSQL",
|
||||||
|
"NoSQL",
|
||||||
|
"ORM",
|
||||||
|
"Hibernate"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Cloud",
|
||||||
|
"keywords": [
|
||||||
|
"AWS",
|
||||||
|
"EC2",
|
||||||
|
"RDS",
|
||||||
|
"S3",
|
||||||
|
"Azure",
|
||||||
|
"Dropbox"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Project",
|
||||||
|
"keywords": [
|
||||||
|
"Agile",
|
||||||
|
"TFS",
|
||||||
|
"Unified Process",
|
||||||
|
"MS Project"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@ -166,10 +204,10 @@
|
|||||||
"website": "http://codeproject.com/build-ui-electron-atom.aspx"
|
"website": "http://codeproject.com/build-ui-electron-atom.aspx"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Jane Doe Unplugged",
|
"name": "Jane Fullstacker's Blog",
|
||||||
"publisher": "self",
|
"publisher": "self",
|
||||||
"releaseDate": "2011",
|
"releaseDate": "2011",
|
||||||
"website": "http://jane-doe.me"
|
"website": "http://janef.me"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Teach Yourself GORFF in 21 Days",
|
"name": "Teach Yourself GORFF in 21 Days",
|
||||||
@ -218,7 +256,8 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"language": "Spanish",
|
"language": "Spanish",
|
||||||
"level": "Moderate"
|
"level": "Moderate",
|
||||||
|
"years": 10
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
76
tests/test-cli.js
Normal file
76
tests/test-cli.js
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
|
||||||
|
var chai = require('chai')
|
||||||
|
, expect = chai.expect
|
||||||
|
, should = chai.should()
|
||||||
|
, path = require('path')
|
||||||
|
, _ = require('underscore')
|
||||||
|
, FRESHResume = require('../src/core/fresh-resume')
|
||||||
|
, FCMD = require( '../src/hackmycmd')
|
||||||
|
, validator = require('is-my-json-valid')
|
||||||
|
, COLORS = require('colors');
|
||||||
|
|
||||||
|
chai.config.includeStack = false;
|
||||||
|
|
||||||
|
describe('Testing CLI interface', function () {
|
||||||
|
|
||||||
|
var _sheet;
|
||||||
|
|
||||||
|
function logMsg() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
COLORS.setTheme({
|
||||||
|
title: ['white','bold'],
|
||||||
|
info: process.platform === 'win32' ? 'gray' : ['white','dim'],
|
||||||
|
infoBold: ['white','dim'],
|
||||||
|
warn: 'yellow',
|
||||||
|
error: 'red',
|
||||||
|
guide: 'yellow',
|
||||||
|
status: 'gray',//['white','dim'],
|
||||||
|
useful: 'green',
|
||||||
|
});
|
||||||
|
|
||||||
|
var opts = {
|
||||||
|
//theme: 'compact',
|
||||||
|
format: 'FRESH',
|
||||||
|
prettify: true,
|
||||||
|
silent: true
|
||||||
|
};
|
||||||
|
|
||||||
|
var opts2 = {
|
||||||
|
format: 'JRS',
|
||||||
|
prettify: true,
|
||||||
|
silent: true
|
||||||
|
};
|
||||||
|
|
||||||
|
run( 'new', ['tests/sandbox/new-fresh-resume.json'], [], opts, ' (FRESH format)' );
|
||||||
|
run( 'new', ['tests/sandbox/new-jrs-resume.json'], [], opts2, ' (JRS format)' );
|
||||||
|
run( 'new', ['tests/sandbox/new-1.json', 'tests/sandbox/new-2.json', 'tests/sandbox/new-3.json'], [], opts, ' (multiple FRESH resumes)' );
|
||||||
|
run( 'new', ['tests/sandbox/new-jrs-1.json', 'tests/sandbox/new-jrs-2.json', 'tests/sandbox/new-jrs-3.json'], [], opts, ' (multiple JRS resumes)' );
|
||||||
|
run( 'new', ['tests/sandbox/new-jrs-resume.json'], [], opts2, ' (JRS format)' );
|
||||||
|
fail( 'new', [], [], opts, " (when a filename isn't specified)" );
|
||||||
|
|
||||||
|
run( 'validate', ['node_modules/jane-q-fullstacker/resume/jane-resume.json'], [], opts, ' (FRESH format)' );
|
||||||
|
run( 'validate', ['tests/sandbox/new-fresh-resume.json'], [], opts, ' (FRESH format)' );
|
||||||
|
|
||||||
|
function run( verb, src, dst, opts, msg ) {
|
||||||
|
msg = msg || '.';
|
||||||
|
it( 'The ' + verb.toUpperCase() + ' command should SUCCEED' + msg, function () {
|
||||||
|
function runIt() {
|
||||||
|
FCMD.verbs[verb]( src, dst, opts, opts.silent ? logMsg : null );
|
||||||
|
}
|
||||||
|
runIt.should.not.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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
@ -7,6 +7,7 @@ var chai = require('chai')
|
|||||||
, FRESHResume = require('../src/core/fresh-resume')
|
, FRESHResume = require('../src/core/fresh-resume')
|
||||||
, CONVERTER = require('../src/core/convert')
|
, CONVERTER = require('../src/core/convert')
|
||||||
, FS = require('fs')
|
, FS = require('fs')
|
||||||
|
, MKDIRP = require('mkdirp')
|
||||||
, _ = require('underscore');
|
, _ = require('underscore');
|
||||||
|
|
||||||
chai.config.includeStack = false;
|
chai.config.includeStack = false;
|
||||||
@ -21,6 +22,7 @@ describe('FRESH/JRS converter', function () {
|
|||||||
var fileB = path.join( __dirname, 'sandbox/richard-hendriks.json' );
|
var fileB = path.join( __dirname, 'sandbox/richard-hendriks.json' );
|
||||||
|
|
||||||
_sheet = new FRESHResume().open( fileA );
|
_sheet = new FRESHResume().open( fileA );
|
||||||
|
MKDIRP.sync( path.parse(fileB).dir );
|
||||||
_sheet.saveAs( fileB, 'JRS' );
|
_sheet.saveAs( fileB, 'JRS' );
|
||||||
|
|
||||||
var rawA = FS.readFileSync( fileA, 'utf8' );
|
var rawA = FS.readFileSync( fileA, 'utf8' );
|
||||||
|
@ -16,7 +16,7 @@ describe('jane-doe.json (FRESH)', function () {
|
|||||||
it('should open without throwing an exception', function () {
|
it('should open without throwing an exception', function () {
|
||||||
function tryOpen() {
|
function tryOpen() {
|
||||||
_sheet = new FRESHResume().open(
|
_sheet = new FRESHResume().open(
|
||||||
'node_modules/FRESCA/exemplar/jane-doe.json' );
|
'node_modules/jane-q-fullstacker/resume/jane-resume.json' );
|
||||||
}
|
}
|
||||||
tryOpen.should.not.Throw();
|
tryOpen.should.not.Throw();
|
||||||
});
|
});
|
||||||
@ -43,13 +43,13 @@ describe('jane-doe.json (FRESH)', function () {
|
|||||||
|
|
||||||
it('should save without throwing an exception', function(){
|
it('should save without throwing an exception', function(){
|
||||||
function trySave() {
|
function trySave() {
|
||||||
_sheet.save( 'tests/sandbox/jane-doe.json' );
|
_sheet.save( 'tests/sandbox/jane-q-fullstacker.json' );
|
||||||
}
|
}
|
||||||
trySave.should.not.Throw();
|
trySave.should.not.Throw();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not be modified after saving', function() {
|
it('should not be modified after saving', function() {
|
||||||
var savedSheet = new FRESHResume().open('tests/sandbox/jane-doe.json');
|
var savedSheet = new FRESHResume().open('tests/sandbox/jane-q-fullstacker.json');
|
||||||
_sheet.stringify().should.equal( savedSheet.stringify() )
|
_sheet.stringify().should.equal( savedSheet.stringify() )
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ describe('jane-doe.json (JRS)', function () {
|
|||||||
it('should open without throwing an exception', function () {
|
it('should open without throwing an exception', function () {
|
||||||
function tryOpen() {
|
function tryOpen() {
|
||||||
_sheet = new JRSResume().open(
|
_sheet = new JRSResume().open(
|
||||||
path.join( __dirname, 'resumes/jrs/jane-doe.json' ) );
|
path.join( __dirname, 'resumes/jrs/jane-q-fullstacker.json' ) );
|
||||||
}
|
}
|
||||||
tryOpen.should.not.Throw();
|
tryOpen.should.not.Throw();
|
||||||
});
|
});
|
||||||
@ -39,13 +39,13 @@ describe('jane-doe.json (JRS)', function () {
|
|||||||
|
|
||||||
it('should save without throwing an exception', function(){
|
it('should save without throwing an exception', function(){
|
||||||
function trySave() {
|
function trySave() {
|
||||||
_sheet.save( 'tests/sandbox/jane-doe.json' );
|
_sheet.save( 'tests/sandbox/jane-q-fullstacker.json' );
|
||||||
}
|
}
|
||||||
trySave.should.not.Throw();
|
trySave.should.not.Throw();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not be modified after saving', function() {
|
it('should not be modified after saving', function() {
|
||||||
var savedSheet = new JRSResume().open( 'tests/sandbox/jane-doe.json' );
|
var savedSheet = new JRSResume().open( 'tests/sandbox/jane-q-fullstacker.json' );
|
||||||
_sheet.stringify().should.equal( savedSheet.stringify() )
|
_sheet.stringify().should.equal( savedSheet.stringify() )
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ var chai = require('chai')
|
|||||||
, path = require('path')
|
, path = require('path')
|
||||||
, _ = require('underscore')
|
, _ = require('underscore')
|
||||||
, FRESHResume = require('../src/core/fresh-resume')
|
, FRESHResume = require('../src/core/fresh-resume')
|
||||||
, FCMD = require( '../src/fluentcmd')
|
, FCMD = require( '../src/hackmycmd')
|
||||||
, validator = require('is-my-json-valid')
|
, validator = require('is-my-json-valid')
|
||||||
, COLORS = require('colors');
|
, COLORS = require('colors');
|
||||||
|
|
||||||
@ -29,15 +29,15 @@ describe('Testing themes', function () {
|
|||||||
function genTheme( themeName ) {
|
function genTheme( themeName ) {
|
||||||
it( themeName.toUpperCase() + ' theme should generate without throwing an exception', function () {
|
it( themeName.toUpperCase() + ' theme should generate without throwing an exception', function () {
|
||||||
function tryOpen() {
|
function tryOpen() {
|
||||||
var src = ['node_modules/FRESCA/exemplar/jane-doe.json'];
|
var src = ['node_modules/jane-q-fullstacker/resume/jane-resume.json'];
|
||||||
var dst = ['tests/sandbox/hello-world/resume.all'];
|
var dst = ['tests/sandbox/' + themeName + '/resume.all'];
|
||||||
var opts = {
|
var opts = {
|
||||||
theme: themeName,
|
theme: themeName,
|
||||||
format: 'FRESH',
|
format: 'FRESH',
|
||||||
prettify: true,
|
prettify: true,
|
||||||
silent: false
|
silent: true
|
||||||
};
|
};
|
||||||
FCMD.verbs.build( src, dst, opts );
|
FCMD.verbs.build( src, dst, opts, function() { } );
|
||||||
}
|
}
|
||||||
tryOpen.should.not.Throw();
|
tryOpen.should.not.Throw();
|
||||||
});
|
});
|
||||||
@ -48,5 +48,6 @@ describe('Testing themes', function () {
|
|||||||
genTheme('modern');
|
genTheme('modern');
|
||||||
genTheme('minimist');
|
genTheme('minimist');
|
||||||
genTheme('awesome');
|
genTheme('awesome');
|
||||||
|
genTheme('positive');
|
||||||
|
|
||||||
});
|
});
|
||||||
|
Reference in New Issue
Block a user