1
0
mirror of https://github.com/JuanCanham/HackMyResume.git synced 2025-05-12 00:27:08 +01:00

Compare commits

...

157 Commits

Author SHA1 Message Date
b077ff42e4 chore: add shrinkwrap 2018-02-12 07:33:02 -05:00
1bc3485812 chore: set HackMyResume version to 1.9.0-beta 2018-02-12 07:11:28 -05:00
7597eda198 docs: merge enhancements 2018-02-12 07:05:49 -05:00
3badb46ae4 docs: update README images 2018-02-12 06:48:26 -05:00
30affe351d chore: upate dot files 2018-02-12 05:28:02 -05:00
407f9f8cd7 style: fix eslint violations 2018-02-12 04:01:00 -05:00
922c1968ca chore: add build-time eslint support 2018-02-12 03:34:55 -05:00
093a2c6a56 docs: update CHANGELOG for 1.9.0 2018-02-12 02:47:03 -05:00
031b666b0a Merge branch 'dev' into docs-2.0 2018-02-12 00:35:45 -05:00
033b29fd3a test: remove Node 4 and 5 from Travis 2018-02-12 00:16:51 -05:00
c4f7350528 chore: update project dependencies 2018-02-12 00:05:29 -05:00
7144126175 feat: improve help behavior 2018-02-11 08:13:13 -05:00
a5739f337f chore: move use.txt to help/ folder 2018-02-10 13:28:42 -05:00
98f20c368c feat: introduce HELP command
Add support for a "HELP" verb in the HackMyResume CLI, allowing separate help
pages for each HackMyResume command per #205. The following command invocations
are recognized.

    hackmyresume help
    hackmyresume help build
    hackmyresume help new
    hackmyresume help convert
    hackmyresume help analyze
    hackmyresume help validate
    hackmyresume help peek
    hackmyresume help help
2018-02-10 13:03:52 -05:00
2281b4ea7f chore: bump fresh-jrs-converter to 1.0.0 2018-02-10 03:02:22 -05:00
7196bff27c feat: support JSON Resume edge schema 2018-02-10 01:10:20 -05:00
7cfdb95a04 feat: convert: support multiple JRS versions 2018-02-09 21:34:24 -05:00
58fe46dc83 feat: introduce FRESH version regex 2018-02-09 21:32:44 -05:00
17e5c6c172 style: notate an issue in skills coalescing func 2018-02-09 00:17:10 -05:00
ea3c72845e chore: bump fresh-test-resumes to 0.9.2 explicit 2018-02-09 00:08:41 -05:00
06805578a2 chore: introduce fresh-resume-validator dependency 2018-02-09 00:07:37 -05:00
20815d7eff fix: add missing require() 2018-02-09 00:06:07 -05:00
8648befcdd feat: introduce stringOrObject & linkMD helpers 2018-02-07 23:35:05 -05:00
c08c5f0fa3 feat: introduce two skill-related helpers 2018-02-07 05:55:27 -05:00
38a073b09a feat: improve template helper wiring 2018-02-07 05:49:02 -05:00
2346562428 fix: remove extraneous log statement 2018-02-06 08:24:18 -05:00
2bf5bb72cf fix: prevent broken XML in Word docs 2018-02-06 08:22:31 -05:00
7262578c81 feat: allow standalone FRESH themes to inherit 2018-02-05 23:43:38 -05:00
66f3cb15c9 style: remove unused var 2018-02-05 23:41:40 -05:00
6f07141b0d test: remove image files from freeze test 2018-02-05 00:06:14 -05:00
dc88073bcc test: reactivate Awesome theme and ice check 2018-02-04 23:35:42 -05:00
8c81a54565 fix: resolve issues around @@@@ characters in dates
Simplify resume freezing; avoid transformations on foreign fields. Fixes #198
but needs followup to allow users to specify how and when freezing, encoding,
and transformations occur.
2018-02-04 23:06:34 -05:00
8dca5b76e7 refactor: remove AbstractResume base class
(1) AbstractResume adds complexity without contributing utility. There's not
really a clean "class" abstraction in JavaScript to begin with; CoffeeScript
classes, as nice as they are syntactically, occlude the issue even further.

(2) AbstractResume currently functions as a container for exactly two functions
which arguably should live outside the resume class anyway.
2018-02-04 22:49:58 -05:00
2767b16b47 test: use direct dependency for fresh-resume-underscore 2018-02-04 05:34:47 -05:00
f1343add71 test: remove hard-coded submodule path 2018-02-04 04:44:47 -05:00
81d9d5f157 test: add fresh-theme-underscore to suite 2018-02-04 04:06:49 -05:00
caca653666 style: remove unnecessary expression 2018-02-04 01:43:51 -05:00
55196c2766 fix: prevent weird characters in date fields 2018-02-04 01:13:02 -05:00
00067d012a fix: correctly replace frozen fields in JRS-themed resumes 2018-02-03 16:15:17 -05:00
9da69c3310 Merge branch 'dev' into docs-2.0 2018-02-03 04:36:56 -05:00
02f0af1ff8 chore: update HackMyResume version to 1.9.0 2018-02-03 04:35:07 -05:00
b1515fc656 docs: update fresh-resume-schema link 2018-02-03 04:31:22 -05:00
ba719166f7 test: replace fresca with fresh-resume-schema 2018-02-02 04:49:26 -05:00
db6ec47dcc chore: update stale JavaScript 2018-02-02 04:48:28 -05:00
f53c316ecb chore: replace fresca with fresh-resume-schema 2018-02-02 03:42:50 -05:00
e889481ad8 chore: bump HackMyResume version to 2.0.0
With this upcoming release we've introduced potentially breaking functionality
so a major version bump is indicated. Additionally, it's been over a year since
the last HackMyResume release; a major version bump allows us to accept some
fruitful breakage, giving us a clean basis as we move forward on new features.
2018-02-02 02:56:59 -05:00
2b31f5bb58 docs: capture readme & changelog updates (interim) 2018-02-02 02:33:13 -05:00
7912ec9ef5 chore: add fresh-test-themes dependency 2018-02-01 22:02:50 -05:00
e6e0b135ed style: clean up comments in jrs-theme.coffee 2018-02-01 19:22:17 -05:00
54d056c4b7 fix: exclude theme.json from interrogation 2018-02-01 17:56:43 -05:00
157a2a6145 chore: bump fresh-themes to 0.16.0-beta 2018-02-01 09:37:20 -05:00
688767d415 feat: improve ad hoc theme loading 2018-02-01 07:20:12 -05:00
1dbb78c53f feat: improve custom theme helper registration 2018-02-01 07:00:59 -05:00
9c096541ce feat: allow FRESH themes to specify base folder
Currently, FRESH themes contain a `src` folder that contains theme artifacts.
This commit allows the theme to specify a different folder (including "." or
""), supporting arbitrary folder structures.
2018-02-01 06:52:06 -05:00
5161a3a823 feat: include private fields during convert 2018-02-01 06:44:07 -05:00
76a386c9df chore: bump fresh-test-resumes version to 0.9.1 2018-02-01 06:22:53 -05:00
7d78deec5f test: replace 'resumes/' folder with fresh-test-resumes 2018-02-01 05:58:35 -05:00
a265fb633d chore: add 'glob' dependency 2018-01-31 22:01:28 -05:00
069506e86d feat: support custom theme helpers 2018-01-31 21:11:21 -05:00
7f656175f0 chore: bump jrs-fresh-converter to 0.2.3 2018-01-31 16:19:03 -05:00
94fc54174c refactor: remove unnecessary var 2018-01-31 16:17:46 -05:00
231357badc [fix] Replace legacy theme detection code. 2018-01-31 16:00:09 -05:00
fde2146a0b [fix] Private fields: resolve off-by-one error [2]. 2018-01-31 15:22:15 -05:00
c6adab7f9e [fix] Private fields: Resolve off-by-one error. 2018-01-31 00:10:37 -05:00
7f7c936897 [chore] Test: Increase resume generation timeout. 2018-01-30 13:04:08 -05:00
a9e35203c2 [style] Tighten syntax in html-pdf-cli-generator.coffee|js. 2018-01-30 12:40:17 -05:00
c913de4bf7 CONVERT: Improve command consistency. 2018-01-30 02:34:58 -05:00
6b125ed907 Support no-escape option for Handlebars themes. 2018-01-29 05:21:46 -05:00
17259cedbf Detect bad option files supplied via --options. 2018-01-29 02:04:00 -05:00
12a14dadeb Merge branch 'master' into dev 2018-01-28 22:38:08 -05:00
35fb2f5dac Fix Travis build issues. (#204) 2018-01-28 22:34:05 -05:00
097e81caf8 Merge pull request #191 from ryneeverett/theme-helpers
Register handlebars helpers in themes. Fix #158.
2018-01-27 17:44:36 -05:00
6adf195281 Travis: Update Node.js versions. 2018-01-26 10:02:26 -05:00
79c304a08b Update text exemplars. 2018-01-25 15:04:49 -05:00
394d5cf821 Merge branch 'master' into dev 2018-01-25 14:48:35 -05:00
6092f985f2 Merge branch 'dev' of https://github.com/hacksalot/HackMyResume into dev 2018-01-25 14:48:14 -05:00
2c26c21144 Bump fresh resume versions. 2018-01-25 14:47:22 -05:00
6bd0b817af Merge pull request #177 from ael-code/pdf-margins
instruct wkhtmltopdf to insert top/bottom margins
2018-01-25 13:05:35 -05:00
97fe171775 Merge branch 'master' into pdf-margins 2018-01-25 13:05:24 -05:00
9718c652ab Merge pull request #164 from jonathanGB/master
little fix in the "Use" base case
2018-01-25 12:51:06 -05:00
9aad6d6138 Merge pull request #183 from peternowee/fix-issue165
Update fresh-resume-starter dependancy to 0.3.x
2018-01-25 12:44:34 -05:00
d25f8d0f97 Merge pull request #186 from ryneeverett/same-value-dateRange
When date ranges are identical, only show one.
2018-01-25 12:43:48 -05:00
d2d9039abb Merge pull request #189 from ryneeverett/weasyprint
Add WeasyPrint pdf generator support.
2018-01-25 12:10:25 -05:00
3dc6ff2158 Merge pull request #184 from ryneeverett/noescape
Don't do html escaping. Fix #157.
2018-01-25 02:27:32 -05:00
36f5f46753 Merge pull request #192 from peternowee/fix-example-options-file
Mark options file example JSON and remove comments
2018-01-24 23:46:50 -05:00
8cc6334cd1 Bump FRESCA dependency to 0.6.1. 2018-01-24 21:30:19 -05:00
b7ef40709e Update copyright notice. 2018-01-24 21:29:47 -05:00
efe97ad793 Update tests.
Knock the dust off the HackMyResume test suite.
2018-01-24 21:29:24 -05:00
a243354044 Change admin email to admin@fluentdesk.com. 2018-01-24 21:26:45 -05:00
92c477e139 Mark options file example JSON and remove comments
The options file is JSON, not JavaScript, and JSON does not allow
comments. An options file with comments as shown in the current
example in README.md will fail to load.

This commit removes the comments. They do not seem important enough to
place them elsewhere.
2017-02-17 12:28:56 +01:00
ec591b9432 Register handlebars helpers in themes. Fix #158.
Try to register all javascript files found in themes as handlebars
helpers.

Note that, unlike all other theme files currently, format directories
are ignored. I don't think there's a use case for format-specific
helpers, and this gives theme developers the flexibility to put them
either in top level files or organize them in subdirectories however
they see fit.

Note also that the theme format seems to be primarily documented in
<https://github.com/fresh-standard/fresh-themes>. This newly recognized
theme file type should be documented there should this branch be merged.
2017-01-20 22:53:16 -05:00
419c935d82 Add weasyprint pdf generator support.
Note: This is based off
<https://github.com/hacksalot/HackMyResume/pull/185> because I changed
the generator expected arguments in that branch.
2017-01-16 00:15:04 -05:00
d31f6caf50 When date ranges are identical, only show one. 2017-01-06 22:37:08 -05:00
7e2a3c3e7e Add option to pass wkhtmltopdf options. Fix #176.
It seems that some time in the last couple years wkhtmltopdf's default
margins were changed from '10mm' to zero. As an alternative to #177,
this PR adds an option to pass in arbitrary wkhtmltopdf long arguments
and sets the default top and bottom margin to '10mm'.
2017-01-06 19:10:27 -05:00
406d3358eb Don't do html escaping. Fix #157. 2016-12-14 21:14:45 -05:00
98ef625d7b Update fresh-resume-starter dependancy to 0.3.x
Fixes issue 165 (Specifying JRS as format resolves in undefined).
2016-11-26 07:29:44 +01:00
be8a7a8361 instruct wkhtmltopdf to insert top/bottom margins
added `--margin-top 10` and `-margin-bottom 10` options in the invocation of `wkhtmltopdf`.
2016-10-21 20:03:54 +02:00
37720677f0 little fix in the "Use" base case 2016-06-27 22:04:57 -04:00
0cd59416b8 Remove unused dependency. 2016-04-02 15:01:28 -04:00
abdeeb4385 Introduce placeholder logo (interim).
Add quick 'n dirty .AI and .PNG assets for interim logo; link through
LFS.
2016-02-16 21:08:35 -05:00
bb7944bee7 Update resume exemplars w/ --private. 2016-02-15 16:56:00 -05:00
9de1156144 Update contributors.
CC @daniele-rapagnani.
2016-02-15 16:48:05 -05:00
9ae2703eeb Bump version to 1.9.0. 2016-02-15 16:39:37 -05:00
a3ed56dd15 Update JS.
Keep dist/ updated with src/ for the CLI and require() folks.
(package.json goes off dist).
2016-02-15 16:38:01 -05:00
214c53a3ab Merge pull request #142 from daniele-rapagnani/feature-private-fields
Implements private fields as requested in #88
2016-02-15 15:52:29 -05:00
ba6b8d45f5 Reverted the compiled JS to avoid merge conflicts 2016-02-14 22:21:06 +01:00
3c166a21a0 Removed the forced private option from the CONVERT verb as it is now the default behaviour 2016-02-14 22:10:30 +01:00
fe46d15031 The ANALYZE command now excludes private fields by default for consistency. 2016-02-14 21:53:10 +01:00
664eea752f parseJSON has been modified to always include private fields if not otherwise instructed. This is to ensure back-compatibility. The BUILD command instead, excludes private fields by default 2016-02-14 21:50:13 +01:00
fed59b704e Implemented private fields that can be included or excluded with cli switch 2016-02-14 19:15:47 +01:00
3cf850ea0e Update test exemplars. 2016-02-14 05:32:33 -05:00
1b0bc87b60 Update changelog and version. 2016-02-14 04:54:44 -05:00
5d3b993737 Bump fresh-themes to 0.15.1-beta.
Not 100% necessary given "^" but support naive stripping of the "^"
decorator.
2016-02-14 04:32:39 -05:00
917fd8e3f3 Refactor helpers.
Rebind Handlebars helpers to drop the pesky options hash for standalone
helpers that don't need it. Move block helpers (which do need the
Handlebars options/context) to a separate file for special handling.
2016-02-14 04:10:23 -05:00
6ac2cd490b Bump fresh-test-resumes to 0.7.0. 2016-02-13 23:45:53 -05:00
8100190978 Bump fresh-themes to 0.15.0-beta. 2016-02-13 22:54:43 -05:00
7c36ff8331 Introduce "date" helper. 2016-02-13 22:54:07 -05:00
255a518565 Set test timeout to 30 seconds.
Most themes should generate in < 1s but allow up to 30 seconds for
network latency when opening a remote file, or for fetching remote
resources (CSS, JS, etc) during a local build.
2016-02-13 20:42:03 -05:00
2d595350c6 Escape LaTeX during generation. 2016-02-13 20:40:17 -05:00
ca92d41d9e Numerous fixes. 2016-02-13 16:08:45 -05:00
3f8e795c61 Fix generation glitches.
Fix output file name glitch, writing CSS files to destination folder,
and an issue where the process would evaporate before PDF/PNG generation
could complete.
2016-02-13 03:27:11 -05:00
9927e79900 Clean up CoffeeScript. 2016-02-13 00:40:10 -05:00
dbef9f0a35 Improve VALIDATE error handling. 2016-02-13 00:11:52 -05:00
c889664c31 More VALIDATE fixups. 2016-02-12 23:47:08 -05:00
7a60cd0bab Fixup VALIDATE command.
Introduce MISSING and UNKNOWN states alongside BROKEN, VALID, and
INVALID and fix regressions introduced in previous refactorings.
2016-02-12 22:49:56 -05:00
964350d3c7 Bump fresh-jrs-converter to 0.2.2.
Technically the "^0.2.1" implies v0.2.2 but eventually we'll drop the
"^" and use shrinkwrapped versions in dev, so explicitly bump for now.
2016-02-12 21:42:50 -05:00
b57d9f05af jsHint: Allow == null.
Relax jsHint's barbaric and antiquated default behavior on triple-equals
null comparisons. Allow expressive, precise, and subtle expressions such
as CoffeeScript's use of "== null" for testing against null OR undefined
under the existential operator.
2016-02-12 17:42:48 -05:00
b26799f9fc Improve JSON error handling.
Add support for detection of invalid line breaks in JSON string values.
Fixes #137. Could be improved to fetch the column number and drop the
messy grabbing of the line number from the exception message via regex,
but currently the "jsonlint" library (not to be confused with
"json-lint") only emits an error string. Since this is also the library
that drives http://jsonlint.com, we'll accept the messy regex in return
for more robust error checking when our default json-lint path fails.

All of the above only necessary because standard JSON.parse error
handling is broken in all environments. : )
2016-02-12 17:11:11 -05:00
daeffd27b5 Remove HB reference from generic helpers. 2016-02-11 22:06:43 -05:00
f87eb46549 Fix theme generation error. 2016-02-11 22:04:11 -05:00
da7cd28734 Remove unused var. 2016-02-11 22:03:49 -05:00
31e0bb69cc Introduce "pad()" helper.
Introduce a helper to emit padded strings / arrays of strings.
2016-02-11 22:02:50 -05:00
5c248cca2a Remove output folder. 2016-02-11 12:09:47 -05:00
f83eb018e8 Scrub tests. 2016-02-11 12:08:11 -05:00
317a250917 Gather. 2016-02-11 11:48:44 -05:00
aaa5e1fc1f Refactor generation.
Merge implicit and explicit generation paths, start emitting file
transform & copy signals, fix various bugs, introduce new bugs, support
better --debug outputs in the future.
2016-02-09 15:27:34 -05:00
1bc4263a46 Aerate. 2016-02-09 10:50:10 -05:00
e191af1fb0 Fix glitch in converted CoffeeScript.
Replace naked ternary with if then else.
2016-02-09 10:41:48 -05:00
7c0a9bcc02 Aerate. 2016-02-09 10:37:33 -05:00
d894f62607 Add ResumeFactory to facade.
Until facade is decommissioned and mothballed
2016-02-09 08:55:00 -05:00
2758038858 Cleanup and bug fixes.
Remove file-based open methods from resume classes; force clients to use
clean string-based or JSON overloads; fix processing glitch in
validate(); tweak outputs; adjust tests; update CHANGELOG; etc.
2016-02-04 18:49:16 -05:00
661fb91861 Aerate. 2016-02-04 15:23:47 -05:00
3c551eb923 Point package.json "main" at "dist" folder. 2016-02-04 14:38:11 -05:00
5bf4bda6de Fix PEEK command. 2016-02-03 20:08:17 -05:00
49ae016f08 Deglitch. 2016-02-02 19:02:56 -05:00
89957aed76 Scrub.
Adding slightly heavier function-level comments as a start for API docs.
2016-02-02 17:47:32 -05:00
233025ddcc Fix indentation. 2016-02-02 17:46:38 -05:00
11dd8952d8 Improve PEEK behavior. 2016-02-02 17:34:10 -05:00
d7c83613df Make CLI tests asynchronous. 2016-02-02 16:18:38 -05:00
a456093f13 Clean up a couple regressions. 2016-02-02 14:13:38 -05:00
dd4851498a Remove Resig's class implementation.
Fun while it lasted.
2016-02-02 13:49:02 -05:00
f72b02a0f4 Refactor generators to CoffeeScript classes. 2016-02-02 13:38:12 -05:00
63a0c78fc5 Refactor verbs to CoffeeScript classes.
Retire Resig's class implementation.
2016-02-01 23:16:49 -05:00
fd39cc9fd9 Adjust error handling / tests. 2016-02-01 22:56:08 -05:00
70f45d468d Asynchrony. 2016-02-01 22:52:13 -05:00
212b01092c Improve proc spawn behavior.
Interim until async / promises support is in.
2016-02-01 09:25:22 -05:00
36d641801b Add Gitter chat badge. 2016-01-31 20:02:27 -05:00
170 changed files with 14828 additions and 3961 deletions

17
.eslintrc.yml Normal file
View File

@ -0,0 +1,17 @@
env:
es6: true
node: true
extends: 'eslint:recommended'
rules:
# indent:
# - error
# - 4
linebreak-style:
- error
- unix
quotes:
- error
- single
semi:
- error
- always

6
.gitattributes vendored
View File

@ -1,9 +1,11 @@
# Auto detect text files and perform LF normalization
* text=auto
*.js text eol=lf
*.json text eol=lf
# Standard to msysgit
*.doc diff=astextplain
*.DOC diff=astextplain
*.docx diff=astextplain
@ -14,3 +16,7 @@
*.PDF diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain
# Git LFS
*.ai filter=lfs diff=lfs merge=lfs -text

2
.gitignore vendored
View File

@ -1,9 +1,9 @@
node_modules/
tests/sandbox/
doc/
docs/
local/
npm-debug.log
*.map
# Emacs detritus
# -*- mode: gitignore; -*-

View File

@ -3,5 +3,12 @@ assets/
test/
doc/
.travis.yml
.eslintrc.yml
Gruntfile.js
.gitattributes
ROADMAP.md
BUILDING.md
CONTRIBUTING.md
CHANGELOG.md
FAQ.md
*.map

View File

@ -1,9 +1,23 @@
sudo: required
before_install:
# Prevents a shared object .so error when running wkhtmltopdf on certain
# platforms (e.g., vanilla Ubuntu 16.04 LTS). Not necessary on current Travis.
# - sudo apt-get install libxrender1
install:
# Install & link HackMyResume
- npm install && npm link
# Download and extract the latest wkhtmltopdf binaries
- mkdir tmp && wget https://github.com/wkhtmltopdf/wkhtmltopdf/releases/download/0.12.4/wkhtmltox-0.12.4_linux-generic-amd64.tar.xz -O tmp/wk.tar.xz
- tar -xf tmp/wk.tar.xz -C ./tmp
# Copy wkhtmltopdf binaries to /usr/bin (also makes them path-accessible)
- sudo cp -R ./tmp/wkhtmltox/bin/* /usr/bin/
# Now you can invoke "wkhtmltopdf" and "wkhtmltoimage" safely in tests.
- wkhtmltopdf -V
- wkhtmltoimage -V
language: node_js
node_js:
- "0.10"
- "0.11"
- "0.12"
- "4.0"
- "4.1"
- "4.2"
- "5.0"
- "6"
- "7"
- "8"
- "9"
- "lts/*"

View File

@ -1,5 +1,199 @@
CHANGELOG
=========
## v1.9.0-beta
*Welcome to the first new version of HackMyResume in over a year. The purpose of
this release is to gather feature enhancements and bug fixes collected over the
past 18 months as we reorganize, rebrand, and prepare for the 2.0 release.*
### Added
- Support for **private resume fields**. Mark any non-leaf node in your resume
JSON with the `private` property and it will be omitted from outbound resumes.
```json
"employment": {
"history": [
{
"employer": "Acme Real Estate"
},
{
"employer": "Area 51 Alien Research Laboratory",
"private": true
},
{
"employer": "H&R Block"
}
]
}
```
- Support for **PDF generation through WeasyPrint** in addition to the
existing support for wkhtmltopdf and PhantomJS.
- Theme authors can now develop and package **custom Handlebars theme helpers**
via the `helpers` key of the `theme.json` file (FRESH themes only) (#158).
- Help system has been updated with a `HELP` command and dedicated help pages
for each command.
- Theme authors can **relocate theme assets** with the `baseFolder` property in
the FRESH `theme.json`.
- HackMyResume will now **validate the options file** (if any) loaded with `-o`
or `--options` and warn the user if necessary.
- Ability to **disable Handlebars encoding/escaping** of resume fields with
`--no-escape`.
- Introduced the [fresh-test-themes][ftt] project as a repository for simple,
test-only resume themes in the FRESH format.
### Changed
- Dropped support for Node 4 and 5. HackMyResume officially runs on Node 6+.
- The FRESCA project has been renamed to [fresh-resume-schema][fresca]. FRESCA
is still the nickname.
- The HackMyResume web page has moved to https://fluentdesk.com/hackmyresume.
### Fixed
- Fixed an issue that would cause the `convert` command to detect the inbound
resume type (FRESH or JRS) incorrectly (#162).
- Fixed an issue where generating new JRS resumes would cause a `null` or
`undefined` error (#165).
- Fixed an issue preventing mixed-case themes from being loaded (#172).
- Fixed an issue requiring JSON Resume themes contain `json-resume-theme` in the
theme path (#173).
- Fixed an issue that would cause strange `@@@@` characters to appear in
generated resumes (#207, #168, #198).
- Fixed an issue that would cause resume generation to hang after a JSON Resume
themed resume was generated (#182).
- Fixed an issue that would cause nothing to be generated for Markdown (.md)
formats (#179).
- Fixed an issue that would prevent a JRS resume from being converted to FRESH
via the `convert` command (#180).
- Fixed an issue that would cause broken styling for JSON Resume themes (#155).
### Internal
- Tests: fixed resume duration tests (#181).
- Style: move to
## v1.8.0
### Added
- Updated `Awesome` theme to latest version of [Awesome-CV][acv].
- Introduced new theme helpers: `pad`, `date`.
### Fixed
- Fixed an issue where the `Awesome` theme wouldn't correctly generate LaTeX
outputs (#138).
- Emit a line number for syntax errors around embedded newlines in JSON strings
(#137).
- Fix several PDF / PNG generation errors (#132, others).
- Display a more helpful error message when attempting to generate a PDF or PNG
on a machine where PhantomJS and/or wkhtmltopdf are either not installed or
not path-accessible.
- Fixed an issue that would cause long-running PDF/PNG generation to fail in
certain environments.
- Fixed an issue involving an unhelpful spawn-related exception (#136).
### Internal
- JSHint will no longer gripe at the use of `== null` and `!= null` in
CoffeeScript transpilation.
- Introduced [template-friendly Awesome-CV fork][awefork] to isolate template
expansion logic & provide better durability for HackMyResume's `awesome` theme.
- Fixed a couple temporary regressions (#139, #140) on the dev branch.
- Additional tests.
- Minor breaking HackMyResume API changes.
## v1.7.4
### Added
- [Build instructions](https://github.com/hacksalot/HackMyResume/blob/master/BUILDING.md).
### Changed
- More precise date handling.
### Fixed
- Issue with incomplete PDF generation (#127).
- Issue with building JSON Resume themes (#128).
- Issue with generating `.json` output format by itself (#97).
## v1.7.3
### Fixed
- Issue with generated PDFs being chopped off and displaying a mysterious sequence of numbers of unknown and possibly alien origin (#127).
- Unsightly border on Modern:PDF.
- Modern|Positive:PDF formats now correctly reference their PDF-specific CSS files.
- `Incorrect helper use` warning in Positive:DOC.
## v1.7.2
### Changed
- Interim release supporting FluentCV Desktop.
### Internal
- Moved [HackMyCore](https://github.com/hacksalot/HackMyCore) dependency to
submodule.
## v1.7.1
### Changed
- Caffeinate. CoffeeScript now used throughout
[HackMyResume](https://github.com/hacksalot/HackMyResume) and
[HackMyCore](https://github.com/hacksalot/HackMyCore); generated JavaScript
lives in `/dist`.
### Fixed
- Issue with generating a single PDF with the `.pdf` extension (#99).
## v1.7.0
### Changed
- [Internal] Relocated HMR processing code to the
[HackMyCore](https://github.com/hacksalot/HackMyCore) project. Shouldn't affect
normal use.
## v1.6.0
### Major Improvements
@ -302,5 +496,8 @@ theme.
[i92]: https://github.com/hacksalot/HackMyResume/issues/92
[i101]: https://github.com/hacksalot/HackMyResume/issues/101
[i111]: https://github.com/hacksalot/HackMyResume/issues/111
[fresca]: https://github.com/fluentdesk/FRESCA
[themes]: https://github.com/fluentdesk/fresh-themes
[fresca]: https://github.com/fresh-standard/fresh-resume-schema
[themes]: https://github.com/fresh-standard/fresh-themes
[awefork]: https://github.com/fluentdesk/Awesome-CV
[acv]: https://github.com/posquit0/Awesome-CV
[ftt]: https://github.com/fresh-standard/fresh-test-themes

View File

@ -33,7 +33,7 @@ HackMyResume is currently maintained by [hacksalot][ha] with assistance from
or internal inquiries to:
```
admin@hackmyresume.com
admin@fluentdesk.com
```
You can reach hacksalot directly at:

View File

@ -17,6 +17,9 @@ module.exports = function (grunt) {
coffee: {
main: {
options: {
sourceMap: true
},
expand: true,
cwd: 'src',
src: ['**/*.coffee'],
@ -36,40 +39,13 @@ module.exports = function (grunt) {
all: { src: ['test/*.js'] }
},
jsdoc : {
dist : {
src: ['src/**/*.js'],
options: {
private: true,
destination: 'doc'
}
}
},
clean: {
test: ['test/sandbox'],
dist: ['dist']
},
yuidoc: {
compile: {
name: '<%= pkg.name %>',
description: '<%= pkg.description %>',
version: '<%= pkg.version %>',
url: '<%= pkg.homepage %>',
options: {
paths: 'src/',
outdir: 'docs/'
}
}
},
jshint: {
options: {
laxcomma: true,
expr: true
},
all: ['Gruntfile.js', 'dist/cli/**/*.js', 'test/*.js']
eslint: {
target: ['Gruntfile.js', 'dist/cli/**/*.js', 'test/*.js']
}
};
@ -78,28 +54,22 @@ module.exports = function (grunt) {
grunt.loadNpmTasks('grunt-contrib-coffee');
grunt.loadNpmTasks('grunt-contrib-copy');
grunt.loadNpmTasks('grunt-simple-mocha');
grunt.loadNpmTasks('grunt-contrib-yuidoc');
grunt.loadNpmTasks('grunt-jsdoc');
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-eslint');
grunt.loadNpmTasks('grunt-contrib-clean');
// Use 'grunt test' for local testing
grunt.registerTask('test', 'Test the HackMyResume application.',
function( config ) {
grunt.task.run(['clean:test','build','jshint','simplemocha:all']);
});
// Use 'grunt document' to build docs
grunt.registerTask('document', 'Generate HackMyResume documentation.',
function( config ) {
grunt.task.run( ['jsdoc'] );
});
function() {
grunt.task.run(['clean:test','build','eslint','simplemocha:all']);
}
);
// Use 'grunt build' to build HMR
grunt.registerTask('build', 'Build the HackMyResume application.',
function( config ) {
grunt.task.run( ['clean:dist','copy','coffee'] );
});
function() {
grunt.task.run( ['clean:dist','copy','coffee','eslint'] );
}
);
// Default task does everything
grunt.registerTask('default', [ 'test', 'document' ]);

View File

@ -1,7 +1,7 @@
The MIT License
===============
Copyright (c) 2015-2016 hacksalot (https://github.com/hacksalot)
Copyright (c) 2015-2018 hacksalot (https://github.com/hacksalot)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

218
README.md
View File

@ -1,16 +1,17 @@
HackMyResume
============
===
[![Latest release][img-release]][latest-release]
[![Build status (MASTER)][img-master]][travis-url-master]
[![Build status (DEV)][img-dev]][travis-url-dev]
[![Join the chat at https://gitter.im/hacksalot/HackMyResume][badge]][gh]
*Create polished résumés and CVs in multiple formats from your command line or
shell. Author in clean Markdown and JSON, export to Word, HTML, PDF, LaTeX,
plain text, and other arbitrary formats. Fight the power, save trees. Compatible
with [FRESH][fresca] and [JRS][6] resumes.*
![](assets/hackmyresume.cli.1.6.0.png)
![](assets/hmr_build.png)
HackMyResume is a dev-friendly, local-only Swiss Army knife for resumes and CVs.
Use it to:
@ -26,6 +27,8 @@ metrics.
HackMyResume is built with Node.js and runs on recent versions of OS X, Linux,
or Windows. View the [FAQ](FAQ.md).
![](assets/hmr_analyze.png)
## Features
- OS X, Linux, and Windows.
@ -61,9 +64,9 @@ Alternately, install the latest bleeding-edge version (updated daily):
HackMyResume tries not to impose a specific PDF engine requirement on
the user, but will instead work with whatever PDF engines you have installed.
Currently, HackMyResume's PDF generation requires either [Phantom.js][2] or
[wkhtmltopdf][3] to be installed on your system and the `phantomjs` and/or
`wkhtmltopdf` binaries to be accessible on your PATH. This is an optional
Currently, HackMyResume's PDF generation requires one of [Phantom.js][2],
[wkhtmltopdf][3], or [WeasyPrint][11] to be installed on your system and the
corresponding binary to be accessible on your PATH. This is an optional
requirement for users who care about PDF formats. If you don't care about PDF
formats, skip this step.
@ -90,7 +93,7 @@ Then when you're ready to generate your resume, just reference the location of
the theme folder as you installed it:
```bash
hackmyresume BUILD resume.json TO out/resume.all -t node_modules/jsonresume-theme-classy
hackmyresume build resume.json TO out/resume.all -t node_modules/jsonresume-theme-classy
```
Note: You can use install themes anywhere on your file system. You don't need a
@ -107,26 +110,26 @@ Use it when you need to submit, upload, print, or email resumes in specific
formats.
```bash
# hackmyresume BUILD <INPUTS...> TO <OUTPUTS...> [-t THEME]
hackmyresume BUILD resume.json TO out/resume.all
hackmyresume BUILD r1.json r2.json TO out/rez.html out/rez.md foo/rez.all
# hackmyresume build <INPUTS...> TO <OUTPUTS...> [-t THEME]
hackmyresume build resume.json TO out/resume.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.
```bash
# hackmyresume NEW <OUTPUTS...> [-f <FORMAT>]
hackmyresume NEW resume.json
hackmyresume NEW resume.json -f fresh
hackmyresume NEW r1.json r2.json -f jrs
# hackmyresume new <OUTPUTS...> [-f <FORMAT>]
hackmyresume new resume.json
hackmyresume new resume.json -f fresh
hackmyresume new r1.json r2.json -f jrs
```
- **analyze** inspects your resume for keywords, duration, and other metrics.
```bash
# hackmyresume ANALYZE <INPUTS...>
hackmyresume ANALYZE resume.json
hackmyresume ANALYZE r1.json r2.json
# hackmyresume analyze <INPUTS...>
hackmyresume analyze resume.json
hackmyresume analyze r1.json r2.json
```
- **convert** converts your source resume between FRESH and JSON Resume
@ -134,29 +137,29 @@ formats. Use it to convert between the two formats to take advantage of tools
and services.
```bash
# hackmyresume CONVERT <INPUTS...> TO <OUTPUTS...>
hackmyresume CONVERT resume.json TO resume-jrs.json
hackmyresume CONVERT 1.json 2.json 3.json TO out/1.json out/2.json out/3.json
# hackmyresume convert <INPUTS...> TO <OUTPUTS...>
hackmyresume convert resume.json TO resume-jrs.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
Resume schema. Use it to make sure your resume data is sufficient and complete.
```bash
# hackmyresume VALIDATE <INPUTS...>
hackmyresume VALIDATE resume.json
hackmyresume VALIDATE r1.json r2.json r3.json
# hackmyresume validate <INPUTS...>
hackmyresume validate resume.json
hackmyresume validate r1.json r2.json r3.json
```
- **peek** echoes your resume or any field, property, or object path on your
resume to standard output.
```bash
# hackmyresume PEEK <INPUTS...> [OBJECT-PATH]
hackmyresume PEEK rez.json # Echo the whole resume
hackmyresume PEEK rez.json info.brief # Echo the "info.brief" field
hackmyresume PEEK rez.json employment.history[1] # Echo the 1st job
hackmyresume PEEK rez.json rez2.json info.brief # Compare value
# hackmyresume peek <INPUTS...> [OBJECT-PATH]
hackmyresume peek rez.json # Echo the whole resume
hackmyresume peek rez.json info.brief # Echo the "info.brief" field
hackmyresume peek rez.json employment.history[1] # Echo the 1st job
hackmyresume peek rez.json rez2.json info.brief # Compare value
```
## Supported Output Formats
@ -183,7 +186,7 @@ Assuming you've got a JSON-formatted resume handy, generating resumes in
different formats and combinations is easy. Just run:
```bash
hackmyresume BUILD <INPUTS> <OUTPUTS> [-t theme].
hackmyresume build <inputs> to <outputs> [-t theme].
```
Where `<INPUTS>` is one or more .json resume files, separated by spaces;
@ -192,19 +195,19 @@ theme (default to Modern). For example:
```bash
# Generate all resume formats (HTML, PDF, DOC, TXT, YML, etc.)
hackmyresume BUILD resume.json TO out/resume.all -t modern
hackmyresume build resume.json TO out/resume.all -t modern
# Generate a specific resume format
hackmyresume BUILD resume.json TO out/resume.html
hackmyresume BUILD resume.json TO out/resume.pdf
hackmyresume BUILD resume.json TO out/resume.md
hackmyresume BUILD resume.json TO out/resume.doc
hackmyresume BUILD resume.json TO out/resume.json
hackmyresume BUILD resume.json TO out/resume.txt
hackmyresume BUILD resume.json TO out/resume.yml
hackmyresume build resume.json TO out/resume.html
hackmyresume build resume.json TO out/resume.pdf
hackmyresume build resume.json TO out/resume.md
hackmyresume build resume.json TO out/resume.doc
hackmyresume build resume.json TO out/resume.json
hackmyresume build resume.json TO out/resume.txt
hackmyresume build resume.json TO out/resume.yml
# Specify 2 inputs and 3 outputs
hackmyresume BUILD in1.json in2.json TO out.html out.doc out.pdf
hackmyresume build in1.json in2.json TO out.html out.doc out.pdf
```
You should see something to the effect of:
@ -231,16 +234,16 @@ installed first). To specify a theme when generating your resume, use the `-t`
or `--theme` parameter:
```bash
hackmyresume BUILD resume.json TO out/rez.all -t [theme]
hackmyresume build resume.json TO out/rez.all -t [theme]
```
The `[theme]` parameter can be the name of a predefined theme OR the path to any
FRESH or JSON Resume theme folder:
```bash
hackmyresume BUILD resume.json TO out/rez.all -t modern
hackmyresume BUILD resume.json TO OUT.rez.all -t ../some-folder/my-custom-theme/
hackmyresume BUILD resume.json TO OUT.rez.all -t node_modules/jsonresume-theme-classy
hackmyresume build resume.json TO out/rez.all -t modern
hackmyresume build resume.json TO OUT.rez.all -t ../some-folder/my-custom-theme/
hackmyresume build resume.json TO OUT.rez.all -t node_modules/jsonresume-theme-classy
```
FRESH themes are currently pre-installed with HackMyResume. JSON Resume themes
@ -264,7 +267,7 @@ most generic to most specific:
```bash
# Merge specific.json onto base.json and generate all formats
hackmyresume BUILD base.json specific.json TO resume.all
hackmyresume build base.json specific.json TO resume.all
```
This can be useful for overriding a base (generic) resume with information from
@ -275,7 +278,7 @@ resume. Merging follows conventional [extend()][9]-style behavior and there's
no arbitrary limit to how many resumes you can merge:
```bash
hackmyresume BUILD in1.json in2.json in3.json in4.json TO out.html out.doc
hackmyresume build in1.json in2.json in3.json in4.json TO out.html out.doc
Reading JSON resume: in1.json
Reading JSON resume: in2.json
Reading JSON resume: in3.json
@ -291,7 +294,7 @@ You can specify **multiple output targets** and HackMyResume will build them:
```bash
# Generate out1.doc, out1.pdf, and foo.txt from me.json.
hackmyresume BUILD me.json TO out1.doc out1.pdf foo.txt
hackmyresume build me.json TO out1.doc out1.pdf foo.txt
```
### Using .all
@ -301,7 +304,7 @@ formats for the given resume. For example, this...
```bash
# Generate all resume formats (HTML, PDF, DOC, TXT, etc.)
hackmyresume BUILD me.json TO out/resume.all
hackmyresume build me.json TO out/resume.all
```
..tells HackMyResume to read `me.json` and generate `out/resume.md`,
@ -316,19 +319,21 @@ and formats with the `--pdf none` switch.*
HackMyResume takes a unique approach to PDF generation. Instead of enforcing
a specific PDF engine on users, HackMyResume will attempt to work with whatever
PDF engine you have installed through the engine's command-line interface (CLI).
Currently that means one or both of...
Currently that means any of...
- [wkhtmltopdf][3]
- [Phantom.js][3]
- [Phantom.js][2]
- [WeasyPrint][11]
..with support for other engines planned in the future. But for now, **one or
both of these engines must be installed and accessible on your PATH in order to
generate PDF resumes with HackMyResume**. That means you should be able to
more of these engines must be installed and accessible on your PATH in order
to generate PDF resumes with HackMyResume**. That means you should be able to
invoke either of these tools directly from your shell or terminal without error:
```bash
wkhtmltopdf input.html output.pdf
phantomjs script.js input.html output.pdf
weasyprint input.html output.pdf
```
Assuming you've installed one or both of these engines on your system, you can
@ -336,9 +341,10 @@ tell HackMyResume which flavor of PDF generation to use via the `--pdf` option
(`-p` for short):
```bash
hackmyresume BUILD resume.json TO out.all --pdf phantom
hackmyresume BUILD resume.json TO out.all --pdf wkhtmltopdf
hackmyresume BUILD resume.json TO out.all --pdf none
hackmyresume build resume.json TO out.all --pdf phantom
hackmyresume build resume.json TO out.all --pdf wkhtmltopdf
hackmyresume build resume.json TO out.all --pdf weasyprint
hackmyresume build resume.json TO out.all --pdf none
```
### Analyzing
@ -347,7 +353,7 @@ HackMyResume can analyze your resume for keywords, employment gaps, and other
metrics. Run:
```bash
hackmyresume ANALYZE <my-resume>.json
hackmyresume analyze <my-resume>.json
```
Depending on the HackMyResume version, you should see output similar to:
@ -444,7 +450,7 @@ resumes, use the `validate` command:
```bash
# Validate myresume.json against either the FRESH or JSON Resume schema.
hackmyresume VALIDATE resumeA.json resumeB.json
hackmyresume validate resumeA.json resumeB.json
```
HackMyResume will validate each specified resume in turn:
@ -461,7 +467,7 @@ HackMyResume can convert between the [FRESH][fresca] and [JSON Resume][6]
formats. Just run:
```bash
hackmyresume CONVERT <INPUTS> <OUTPUTS>
hackmyresume convert <INPUTS> <OUTPUTS>
```
where <INPUTS> is one or more resumes in FRESH or JSON Resume format, and
@ -475,31 +481,34 @@ You can pass options into HackMyResume via an external options or ".hackmyrc"
file with the `--options` or `-o` switch:
```bash
hackmyresume BUILD resume.json -o path/to/options.json
hackmyresume build resume.json -o path/to/options.json
```
The options file can contain any documented HackMyResume option, including
`theme`, `silent`, `debug`, `pdf`, `css`, and other settings.
```javascript
```json
{
// Set the default theme to "compact"
"theme": "compact",
// Change the "employment" section title text to "Work"
"sectionTitles": {
"employment": "Work"
},
"wkhtmltopdf": {
"margin-top": "20mm"
}
}
```
If a particular option is specified both on the command line and in an external
options file, the explicit command-line option takes precedence.
If an option is specified on both the command line and in an external options
file, the command-line option wins.
```bash
# path/to/options.json specifes the POSITIVE theme
# -t parameter specifies the COMPACT theme
# The -t parameter wins.
hackmyresume BUILD resume.json -o path/to/options.json -t compact
hackmyresume build resume.json -o path/to/options.json -t compact
> Reading resume: resume.json
> Applying COMPACT theme (7 formats)
```
@ -511,7 +520,7 @@ HTML-formatted resumes. To disable prettification, the `--no-prettify` or `-n`
flag can be used:
```bash
hackmyresume BUILD resume.json out.all --no-prettify
hackmyresume build resume.json out.all --no-prettify
```
### Silent Mode
@ -519,8 +528,8 @@ hackmyresume BUILD resume.json out.all --no-prettify
Use `-s` or `--silent` to run in silent mode:
```bash
hackmyresume BUILD resume.json -o someFile.all -s
hackmyresume BUILD resume.json -o someFile.all --silent
hackmyresume build resume.json -o someFile.all -s
hackmyresume build resume.json -o someFile.all --silent
```
### Debug Mode
@ -529,15 +538,80 @@ Use `-d` or `--debug` to force HMR to emit a call stack when errors occur. In
the future, this option will emit detailed error logging.
```bash
hackmyresume BUILD resume.json -d
hackmyresume ANALYZE resume.json --debug
hackmyresume build resume.json -d
hackmyresume analyze resume.json --debug
```
### Disable Encoding
Use the `--no-escape` option to disable encoding in Handlebars themes. Note:
this option has no effect for non-Handlebars themes.
```bash
hackmyresume build resume.json --no-escape
```
### Private Resume Fields
Have a gig, education stint, membership, or other relevant history that you'd
like to hide from *most* (e.g. public) resumes but sometimes show on others? Tag it with
`"private": true` to omit it from outbound generated resumes by default.
```json
"employment": {
"history": [
{
"employer": "Acme Real Estate"
},
{
"employer": "Area 51 Alien Research Laboratory",
"private": true
},
{
"employer": "H&R Block"
}
]
}
```
Then, when you want a copy of your resume that includes the private gig / stint
/ etc., tell HackMyResume that it's OK to emit private fields. The way you do
that is with the `--private` switch.
```bash
hackmyresume build resume.json private-resume.all --private
```
### Custom theme helpers
You can attach your own custom Handlebars helpers to a FRESH theme with the
`helpers` key of your theme's `theme.json` file.
```js
{
"title": "my-cool-theme",
// ...
"helpers": [
"../path/to/helpers/*.js",
"some-other-helper.js"
]
}
```
HackMyResume will attempt to load each path or glob and register any specified
files with [Handlebars.registerHelper][hrh], making them available to your
theme.
## Contributing
HackMyResume is a community-driven free and open source project under the MIT
License. Contributions are encouraged and we respond to all PRs and issues,
usually within 24 hours. See [CONTRIBUTING.md][contribute] for details.
License. Contributions are encouraged and we respond to all PRs and issues in
time. See [CONTRIBUTING.md][contribute] for details.
## License
@ -553,8 +627,9 @@ MIT. Go crazy. See [LICENSE.md][1] for details.
[8]: https://youtu.be/N9wsjroVlu8
[9]: https://api.jquery.com/jquery.extend/
[10]: https://github.com/beautify-web/js-beautify
[11]: http://weasyprint.org/
[fresh]: https://github.com/fluentdesk/FRESH
[fresca]: https://github.com/fluentdesk/FRESCA
[fresca]: https://github.com/fresh-standard/fresh-resume-schema
[dry]: https://en.wikipedia.org/wiki/Don%27t_repeat_yourself
[img-release]: https://img.shields.io/github/release/hacksalot/HackMyResume.svg?label=version
[img-master]: https://img.shields.io/travis/hacksalot/HackMyResume/master.svg
@ -565,3 +640,6 @@ MIT. Go crazy. See [LICENSE.md][1] for details.
[contribute]: CONTRIBUTING.md
[fresh-themes]: https://github.com/fluentdesk/fresh-themes
[jrst]: https://www.npmjs.com/search?q=jsonresume-theme
[gh]: https://gitter.im/hacksalot/HackMyResume?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge
[badge]: https://badges.gitter.im/hacksalot/HackMyResume.svg
[hrh]: http://handlebarsjs.com/reference.html#base-registerHelper

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

BIN
assets/hmr_analyze.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
assets/hmr_build.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a476ee59e7d86b5a7599780b5efca57ee6b6d60e1a722343277057ea793703b6
size 1642116

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

123
dist/cli/error.js vendored
View File

@ -1,21 +1,15 @@
/**
Error-handling routines for HackMyResume.
@module cli/error
@license MIT. See LICENSE.md for details.
*/
(function() {
var ErrorHandler, FCMD, FS, HMSTATUS, M2C, PATH, PKG, SyntaxErrorEx, WRAP, YAML, _defaultLog, assembleError, chalk, extend, printf;
/**
Error-handling routines for HackMyResume.
@module cli/error
@license MIT. See LICENSE.md for details.
*/
var FS, HMSTATUS, M2C, PATH, SyntaxErrorEx, WRAP, _defaultLog, assembleError, chalk, extend, printf;
HMSTATUS = require('../core/status-codes');
PKG = require('../../package.json');
FS = require('fs');
FCMD = require('../index');
PATH = require('path');
WRAP = require('word-wrap');
@ -26,21 +20,15 @@ Error-handling routines for HackMyResume.
extend = require('extend');
YAML = require('yamljs');
printf = require('printf');
SyntaxErrorEx = require('../utils/syntax-error-ex');
require('string.prototype.startswith');
/**
Error handler for HackMyResume. All errors are handled here.
@class ErrorHandler
*/
ErrorHandler = module.exports = {
/** Error handler for HackMyResume. All errors are handled here.
@class ErrorHandler */
module.exports = {
init: function(debug, assert, silent) {
this.debug = debug;
this.assert = assert;
@ -50,19 +38,25 @@ Error-handling routines for HackMyResume.
},
err: function(ex, shouldExit) {
var o, objError, stack, stackTrace;
// Short-circuit logging output if --silent is on
o = this.silent ? function() {} : _defaultLog;
if (ex.pass) {
// Special case; can probably be removed.
throw ex;
}
// Load error messages
this.msgs = this.msgs || require('./msg').errors;
// Handle packaged HMR exceptions
if (ex.fluenterror) {
// Output the error message
objError = assembleError.call(this, ex);
o(this['format_' + objError.etype](objError.msg));
// Output the stack (sometimes)
if (objError.withStack) {
stack = ex.stack || (ex.inner && ex.inner.stack);
stack && o(chalk.gray(stack));
}
if (ex.quit || objError.quit) {
if (shouldExit || ex.exit) {
if (this.debug) {
o(chalk.cyan('Exiting with error code ' + ex.fluenterror.toString()));
}
@ -73,6 +67,7 @@ Error-handling routines for HackMyResume.
return process.exit(ex.fluenterror);
}
} else {
// Handle raw exceptions
o(ex);
stackTrace = ex.stack || (ex.inner && ex.inner.stack);
if (stackTrace && this.debug) {
@ -93,7 +88,7 @@ Error-handling routines for HackMyResume.
};
_defaultLog = function() {
return console.log.apply(console.log, arguments);
return console.log.apply(console.log, arguments); // eslint-disable-line no-console
};
assembleError = function(ex) {
@ -114,14 +109,16 @@ Error-handling routines for HackMyResume.
quit = false;
break;
case HMSTATUS.resumeNotFound:
msg = M2C(this.msgs.resumeNotFound.msg, 'yellow');
//msg = M2C( this.msgs.resumeNotFound.msg, 'yellow' );
msg += M2C(FS.readFileSync(PATH.resolve(__dirname, 'help/' + ex.verb + '.txt'), 'utf8'), 'white', 'yellow');
break;
case HMSTATUS.missingCommand:
msg = M2C(this.msgs.missingCommand.msg + " (", 'yellow');
msg += Object.keys(FCMD.verbs).map(function(v, idx, ar) {
return (idx === ar.length - 1 ? chalk.yellow('or ') : '') + chalk.yellow.bold(v.toUpperCase());
}).join(chalk.yellow(', ')) + chalk.yellow(").\n\n");
msg += chalk.gray(FS.readFileSync(PATH.resolve(__dirname, '../cli/use.txt'), 'utf8'));
// msg = M2C( this.msgs.missingCommand.msg + " (", 'yellow');
// msg += Object.keys( FCMD.verbs ).map( (v, idx, ar) ->
// return ( if idx == ar.length - 1 then chalk.yellow('or ') else '') +
// chalk.yellow.bold(v.toUpperCase());
// ).join( chalk.yellow(', ')) + chalk.yellow(").\n\n");
msg += M2C(FS.readFileSync(PATH.resolve(__dirname, 'help/use.txt'), 'utf8'), 'white', 'yellow');
break;
case HMSTATUS.invalidCommand:
msg = printf(M2C(this.msgs.invalidCommand.msg, 'yellow'), ex.attempted);
@ -140,7 +137,6 @@ Error-handling routines for HackMyResume.
if (ex.inner) {
msg += chalk.red('\n' + ex.inner);
}
withStack = true;
quit = false;
etype = 'error';
break;
@ -183,6 +179,7 @@ Error-handling routines for HackMyResume.
break;
case HMSTATUS.readError:
if (!ex.quiet) {
// eslint-disable-next-line no-console
console.error(printf(M2C(this.msgs.readError.msg, 'red'), ex.file));
}
msg = ex.inner.toString();
@ -213,22 +210,78 @@ Error-handling routines for HackMyResume.
break;
case HMSTATUS.parseError:
if (SyntaxErrorEx.is(ex.inner)) {
// eslint-disable-next-line no-console
console.error(printf(M2C(this.msgs.readError.msg, 'red'), ex.file));
se = new SyntaxErrorEx(ex, ex.raw);
msg = printf(M2C(this.msgs.parseError.msg, 'red'), se.line, se.col);
} else if (ex.inner && ex.inner.line !== void 0 && ex.inner.col !== void 0) {
msg = printf(M2C(this.msgs.parseError.msg, 'red'), ex.inner.line, ex.inner.col);
if ((se.line != null) && (se.col != null)) {
msg = printf(M2C(this.msgs.parseError.msg[0], 'red'), se.line, se.col);
} else if (se.line != null) {
msg = printf(M2C(this.msgs.parseError.msg[1], 'red'), se.line);
} else {
msg = M2C(this.msgs.parseError.msg[2], 'red');
}
} else if (ex.inner && (ex.inner.line != null) && (ex.inner.col != null)) {
msg = printf(M2C(this.msgs.parseError.msg[0], 'red'), ex.inner.line, ex.inner.col);
} else {
msg = ex;
}
etype = 'error';
break;
case HMSTATUS.createError:
// inner.code could be EPERM, EACCES, etc
msg = printf(M2C(this.msgs.createError.msg), ex.inner.path);
etype = 'error';
break;
case HMSTATUS.validateError:
msg = printf(M2C(this.msgs.validateError.msg), ex.inner.toString());
etype = 'error';
break;
case HMSTATUS.invalidOptionsFile:
msg = M2C(this.msgs.invalidOptionsFile.msg[0]);
if (SyntaxErrorEx.is(ex.inner)) {
// eslint-disable-next-line no-console
console.error(printf(M2C(this.msgs.readError.msg, 'red'), ex.file));
se = new SyntaxErrorEx(ex, ex.raw);
if ((se.line != null) && (se.col != null)) {
msg += printf(M2C(this.msgs.parseError.msg[0], 'red'), se.line, se.col);
} else if (se.line != null) {
msg += printf(M2C(this.msgs.parseError.msg[1], 'red'), se.line);
} else {
msg += M2C(this.msgs.parseError.msg[2], 'red');
}
} else if (ex.inner && (ex.inner.line != null) && (ex.inner.col != null)) {
msg += printf(M2C(this.msgs.parseError.msg[0], 'red'), ex.inner.line, ex.inner.col);
} else {
msg += ex;
}
msg += this.msgs.invalidOptionsFile.msg[1];
etype = 'error';
break;
case HMSTATUS.optionsFileNotFound:
msg = M2C(this.msgs.optionsFileNotFound.msg);
etype = 'error';
break;
case HMSTATUS.unknownSchema:
msg = M2C(this.msgs.unknownSchema.msg[0]);
//msg += "\n" + M2C( @msgs.unknownSchema.msg[1], 'yellow' )
etype = 'error';
break;
case HMSTATUS.themeHelperLoad:
msg = printf(M2C(this.msgs.themeHelperLoad.msg), ex.glob);
etype = 'error';
break;
case HMSTATUS.invalidSchemaVersion:
msg = printf(M2C(this.msgs.invalidSchemaVersion.msg), ex.data);
etype = 'error';
}
return {
msg: msg,
withStack: withStack,
msg: msg, // The error message to display
withStack: withStack, // Whether to include the stack
quit: quit,
etype: etype
};
};
}).call(this);
//# sourceMappingURL=error.js.map

25
dist/cli/help/analyze.txt vendored Normal file
View File

@ -0,0 +1,25 @@
**analyze** | Analyze a resume for statistical insight
Usage:
**hackmyresume ANALYZE <resume>**
The ANALYZE command evaluates the specified resume(s) for
coverage, duration, gaps, keywords, and other metrics.
This command can be run against multiple resumes. Each
will be analyzed in turn.
Parameters:
**<resume>**
Path to a FRESH or JRS resume. Multiple resumes can be
specified, separated by spaces.
hackmyresume ANALYZE resume.json
hackmyresume ANALYZE r1.json r2.json r3.json
Options:
**None.**

69
dist/cli/help/build.txt vendored Normal file
View File

@ -0,0 +1,69 @@
**build** | Generate themed resumes in multiple formats
Usage:
**hackmyresume BUILD <resume> TO <target> [--theme]**
**[--pdf] [--no-escape] [--private]**
The BUILD command generates themed resumes and CVs in
multiple formats. Use it to create outbound resumes in
specific formats such HTML, MS Word, and PDF.
Parameters:
**<resume>**
Path to a FRESH or JRS resume (*.json) containing your
resume data. Multiple resumes may be specified.
If multiple resumes are specified, they will be merged
into a single resume prior to transformation.
**<target>**
Path to the desired output resume. Multiple resumes
may be specified. The file extension will determine
the format.
.all Generate all supported formats
.html HTML 5
.doc MS Word
.pdf Adobe Acrobat PDF
.txt plain text
.md Markdown
.png PNG Image
.latex LaTeX
Note: not all formats are supported by all themes!
Check the theme's documentation for details or use
the .all extension to build all available formats.
Options:
**--theme -t <theme-name-or-path>**
Path to a FRESH or JSON Resume theme OR the name of a
built-in theme. Valid theme names are 'modern',
'positive', 'compact', 'awesome', and 'basis'.
**--pdf -p <engine>**
Specify the PDF engine to use. Legal values are
'none', 'wkhtmltopdf', 'phantom', or 'weasyprint'.
**--no-escape**
Disable escaping / encoding of resume data during
resume generation. Handlebars themes only.
**--private**
Include resume fields marked as private.
Notes:
The BUILD command can be run against multiple source as well
as multiple target resumes. If multiple source resumes are
provided, they will be merged into a single source resume
before generation. If multiple output resumes are provided,
each will be generated in turn.

33
dist/cli/help/convert.txt vendored Normal file
View File

@ -0,0 +1,33 @@
**convert** | Convert resumes between FRESH and JRS formats
Usage:
**hackmyresume CONVERT <resume> TO <target> [--format]**
The CONVERT command converts one or more resume documents
between the FRESH Resume Schema and JSON Resume formats.
Parameters:
**<resume>**
Path to a FRESH or JRS resume. Multiple resumes can be
specified.
**<targets>**
The path of the converted resume. Multiple resumes can
be specified, one per provided input resume.
Options:
**--format -f <fmt>**
The desired format for the new resume(s). Valid values
are 'FRESH', 'JRS', or, to target the latest edge
version of the JSON Resume Schema, 'JRS@1'.
If this parameter is omitted, the destination format
will be inferred from the source resume's format. If
the source format is FRESH, the destination format
will be JSON Resume, and vice-versa.

23
dist/cli/help/help.txt vendored Normal file
View File

@ -0,0 +1,23 @@
**help** | View help on a specific HackMyResume command
Usage:
**hackmyresume HELP [<command>]**
The HELP command displays help information for a specific
HackMyResume command, including the HELP command itself.
Parameters:
**<command>**
The HackMyResume command to view help information for.
Must be BUILD, NEW, CONVERT, ANALYZE, VALIDATE, PEEK,
or HELP.
hackmyresume help convert
hackmyresume help help
Options:
**None.**

29
dist/cli/help/new.txt vendored Normal file
View File

@ -0,0 +1,29 @@
**new** | Create a new FRESH or JRS resume document
Usage:
**hackmyresume NEW <fileName> [--format]**
The NEW command generates a new resume document in FRESH
or JSON Resume format. This document can serve as an
official source of truth for your resume and career data
as well an input to tools like HackMyResume.
Parameters:
**<fileName>**
The filename (relative or absolute path) of the resume
to be created. Multiple resume paths can be specified,
and each will be created in turn.
hackmyresume NEW resume.json
hackmyresume NEW r1.json foo/r2.json ../r3.json
Options:
**--format -f <fmt>**
The desired format for the new resume(s). Valid values
are 'FRESH', 'JRS', or, to target the latest edge
version of the JSON Resume Schema, 'JRS@1'.

31
dist/cli/help/peek.txt vendored Normal file
View File

@ -0,0 +1,31 @@
**peek** | View portions of a resume from the command line
Usage:
**hackmyresume PEEK <resume> <at>**
The PEEK command displays a specific piece or part of the
resume without requiring the resume to be opened in an
editor.
Parameters:
**<resume>**
Path to a FRESH or JRS resume. Multiple resumes can be
specified, separated by spaces.
hackmyresume PEEK r1.json r2.json r3.json "employment.history[2]"
**<at>**
The resume property or field to be displayed. Can be
any valid resume path, for example:
education[0]
info.name
employment.history[3].start
Options:
**None.**

70
dist/cli/help/use.txt vendored Normal file
View File

@ -0,0 +1,70 @@
**HackMyResume** | A Swiss Army knife for resumes and CVs
Usage:
**hackmyresume [--version] [--help] [--silent] [--debug]**
**[--options] [--no-colors] <command> [<args>]**
Commands: (type "hackmyresume help COMMAND" for details)
**BUILD** Build your resume to the destination format(s).
**ANALYZE** Analyze your resume for keywords, gaps, and metrics.
**VALIDATE** Validate your resume for errors and typos.
**NEW** Create a new resume in FRESH or JSON Resume format.
**CONVERT** Convert your resume between FRESH and JSON Resume.
**PEEK** View a specific field or element on your resume.
**HELP** View help on a specific HackMyResume command.
Common Tasks:
Generate a resume in a specific format (HTML, Word, PDF, etc.)
**hackmyresume build rez.json to out/rez.html**
**hackmyresume build rez.json to out/rez.doc**
**hackmyresume build rez.json to out/rez.pdf**
**hackmyresume build rez.json to out/rez.txt**
**hackmyresume build rez.json to out/rez.md**
**hackmyresume build rez.json to out/rez.png**
**hackmyresume build rez.json to out/rez.tex**
Build a resume to ALL available formats:
**hackmyresume build rez.json to out/rez.all**
Build a resume with a specific theme:
**hackmyresume build rez.json to out/rez.all -t themeName**
Create a new empty resume:
**hackmyresume new rez.json**
Convert a resume between FRESH and JRS formats:
**hackmyresume convert rez.json converted.json**
Analyze a resume for important metrics
**hackmyresume analyze rez.json**
Find more resume themes:
**https://www.npmjs.com/search?q=jsonresume-theme**
**https://www.npmjs.com/search?q=fresh-theme**
**https://github.com/fresh-standard/fresh-themes**
Validate a resume's structure and syntax:
**hackmyresume validate resume.json**
View help on a specific command:
**hackmyresume help [build|convert|new|analyze|validate|peek|help]**
Submit a bug or request:
**https://githut.com/hacksalot/HackMyResume/issues**
HackMyResume is free and open source software published
under the MIT license. For more information, visit the
HackMyResume website or GitHub project page.

26
dist/cli/help/validate.txt vendored Normal file
View File

@ -0,0 +1,26 @@
**validate** | Validate a resume for correctness
Usage:
**hackmyresume VALIDATE <resume> [--assert]**
The VALIDATE command validates a FRESH or JRS document
against its governing schema, verifying that the resume
is correctly structured and formatted.
Parameters:
**<resume>**
Path to a FRESH or JRS resume. Multiple resumes can be
specified.
hackmyresume ANALYZE resume.json
hackmyresume ANALYZE r1.json r2.json r3.json
Options:
**--assert -a**
Tell HackMyResume to return a non-zero process exit
code if a resume fails to validate.

231
dist/cli/main.js vendored
View File

@ -1,12 +1,22 @@
/**
Definition of the `main` function.
@module cli/main
@license MIT. See LICENSE.md for details.
*/
(function() {
var Command, EXTEND, FS, HME, HMR, HMSTATUS, OUTPUT, PAD, PATH, PKG, StringUtils, _, _opts, _out, _title, chalk, execute, initOptions, initialize, loadOptions, logMsg, main, safeLoadJSON, splitSrcDest;
/**
Definition of the `main` function.
@module cli/main
@license MIT. See LICENSE.md for details.
*/
/* Invoke a HackMyResume verb. */
/* Success handler for verb invocations. Calls process.exit by default */
/* Init options prior to setting up command infrastructure. */
/* Massage command-line args and setup Commander.js. */
/*
Initialize HackMyResume options.
TODO: Options loading is a little hacky, for two reasons:
- Commander.js idiosyncracies
- Need to accept JSON inputs from the command line.
*/
/* Simple logging placeholder. */
/* Split multiple command-line filenames by the 'TO' keyword */
var Command, EXTEND, FS, HMR, HMSTATUS, M2C, OUTPUT, PAD, PATH, PKG, _, _err, _exitCallback, _opts, _out, _title, chalk, execute, executeFail, executeSuccess, initOptions, initialize, loadOptions, logMsg, printf, safeLoadJSON, splitSrcDest;
HMR = require('../index');
@ -22,12 +32,9 @@ Definition of the `main` function.
HMSTATUS = require('../core/status-codes');
HME = require('../core/event-codes');
safeLoadJSON = require('../utils/safe-json-loader');
StringUtils = require('../utils/string.js');
//StringUtils = require '../utils/string.js'
_ = require('underscore');
OUTPUT = require('./out');
@ -36,12 +43,19 @@ Definition of the `main` function.
Command = require('commander').Command;
M2C = require('../utils/md2chalk');
printf = require('printf');
_opts = {};
_title = chalk.white.bold('\n*** HackMyResume v' + PKG.version + ' ***');
_out = new OUTPUT(_opts);
_err = require('./error');
_exitCallback = null;
/*
A callable implementation of the HackMyResume CLI. Encapsulates the command
@ -49,38 +63,56 @@ Definition of the `main` function.
@alias module:cli/main.main
@param rawArgs {Array} An array of command-line parameters. Will either be
process.argv (in production) or custom parameters (in test).
*/
main = module.exports = function(rawArgs) {
*/
module.exports = function(rawArgs, exitCallback) {
var args, initInfo, program;
initInfo = initialize(rawArgs);
initInfo = initialize(rawArgs, exitCallback);
if (initInfo === null) {
return;
}
args = initInfo.args;
// Create the top-level (application) command...
program = new Command('hackmyresume').version(PKG.version).description(chalk.yellow.bold('*** HackMyResume ***')).option('-s --silent', 'Run in silent mode').option('--no-color', 'Disable colors').option('--color', 'Enable colors').option('-d --debug', 'Enable diagnostics', false).option('-a --assert', 'Treat warnings as errors', false).option('-v --version', 'Show the version').allowUnknownOption();
program.jsonArgs = initInfo.options;
program.command('new')["arguments"]('<sources...>').option('-f --format <fmt>', 'FRESH or JRS format', 'FRESH').alias('create').description('Create resume(s) in FRESH or JSON RESUME format.').action((function(sources) {
// Create the NEW command
program.command('new').arguments('<sources...>').option('-f --format <fmt>', 'FRESH or JRS format', 'FRESH').alias('create').description('Create resume(s) in FRESH or JSON RESUME format.').action((function(sources) {
execute.call(this, sources, [], this.opts(), logMsg);
}));
program.command('validate')["arguments"]('<sources...>').description('Validate a resume in FRESH or JSON RESUME format.').action(function(sources) {
// Create the VALIDATE command
program.command('validate').arguments('<sources...>').description('Validate a resume in FRESH or JSON RESUME format.').action(function(sources) {
execute.call(this, sources, [], this.opts(), logMsg);
});
program.command('convert').description('Convert a resume to/from FRESH or JSON RESUME format.').action(function() {
// Create the CONVERT command
program.command('convert').description('Convert a resume to/from FRESH or JSON RESUME format.').option('-f --format <fmt>', 'FRESH or JRS format and optional version', void 0).action(function() {
var x;
x = splitSrcDest.call(this);
execute.call(this, x.src, x.dst, this.opts(), logMsg);
});
program.command('analyze')["arguments"]('<sources...>').description('Analyze one or more resumes.').action(function(sources) {
// Create the ANALYZE command
program.command('analyze').arguments('<sources...>').option('--private', 'Include resume fields marked as private', false).description('Analyze one or more resumes.').action(function(sources) {
execute.call(this, sources, [], this.opts(), logMsg);
});
program.command('peek')["arguments"]('<sources...>').description('Peek at a resume field or section').action(function(sources, sectionOrField) {
// Create the PEEK command
//.action(( sources, sectionOrField ) ->
program.command('peek').arguments('<sources...>').description('Peek at a resume field or section').action(function(sources) {
var dst;
dst = sources && sources.length > 1 ? [sources.pop()] : [];
dst = (sources && sources.length > 1) ? [sources.pop()] : [];
execute.call(this, sources, dst, this.opts(), logMsg);
});
program.command('build').alias('generate').option('-t --theme <theme>', 'Theme name or path').option('-n --no-prettify', 'Disable HTML prettification', true).option('-c --css <option>', 'CSS linking / embedding').option('-p --pdf <engine>', 'PDF generation engine').option('--no-sort', 'Sort resume sections by date', false).option('--tips', 'Display theme tips and warnings.', false).description('Generate resume to multiple formats').action(function(sources, targets, options) {
// Create the BUILD command
//.action(( sources, targets, options ) ->
program.command('build').alias('generate').option('-t --theme <theme>', 'Theme name or path').option('-n --no-prettify', 'Disable HTML prettification', true).option('-c --css <option>', 'CSS linking / embedding').option('-p --pdf <engine>', 'PDF generation engine').option('--no-sort', 'Sort resume sections by date', false).option('--tips', 'Display theme tips and warnings.', false).option('--private', 'Include resume fields marked as private', false).option('--no-escape', 'Turn off encoding in Handlebars themes.', false).description('Generate resume to multiple formats').action(function() {
var x;
x = splitSrcDest.call(this);
execute.call(this, x.src, x.dst, this.opts(), logMsg);
});
// Create the HELP command
program.command('help').arguments('[command]').description('Get help on a HackMyResume command').action(function(cmd) {
var manPage;
cmd = cmd || 'use';
manPage = FS.readFileSync(PATH.join(__dirname, 'help/' + cmd + '.txt'), 'utf8');
_out.log(M2C(manPage, 'white', 'yellow.bold'));
});
program.parse(args);
if (!program.args.length) {
throw {
@ -89,13 +121,29 @@ Definition of the `main` function.
}
};
/* Massage command-line args and setup Commander.js. */
initialize = function(ar) {
initialize = function(ar, exitCallback) {
var o;
_exitCallback = exitCallback || process.exit;
o = initOptions(ar);
if (o.ex) {
_err.init(false, true, false);
if (o.ex.op === 'parse') {
_err.err({
fluenterror: o.ex.op === 'parse' ? HMSTATUS.invalidOptionsFile : HMSTATUS.optionsFileNotFound,
inner: o.ex.inner,
quit: true
});
} else {
_err.err({
fluenterror: HMSTATUS.optionsFileNotFound,
inner: o.ex.inner,
quit: true
});
}
return null;
}
o.silent || logMsg(_title);
// Emit debug prelude if --debug was specified
if (o.debug) {
_out.log(chalk.cyan('The -d or --debug switch was specified. DEBUG mode engaged.'));
_out.log('');
@ -103,27 +151,33 @@ Definition of the `main` function.
_out.log(chalk.cyan(PAD(' Node.js:', 25, null, PAD.RIGHT)) + chalk.cyan.bold(process.version));
_out.log(chalk.cyan(PAD(' HackMyResume:', 25, null, PAD.RIGHT)) + chalk.cyan.bold('v' + PKG.version));
_out.log(chalk.cyan(PAD(' FRESCA:', 25, null, PAD.RIGHT)) + chalk.cyan.bold(PKG.dependencies.fresca));
//_out.log(chalk.cyan(PAD(' fresh-themes:',25, null, PAD.RIGHT)) + chalk.cyan.bold( PKG.dependencies['fresh-themes'] ))
//_out.log(chalk.cyan(PAD(' fresh-jrs-converter:',25, null, PAD.RIGHT)) + chalk.cyan.bold( PKG.dependencies['fresh-jrs-converter'] ))
_out.log('');
}
if (o.verb && !HMR.verbs[o.verb] && !HMR.alias[o.verb]) {
throw {
_err.init(o.debug, o.assert, o.silent);
// Handle invalid verbs here (a bit easier here than in commander.js)...
if (o.verb && !HMR.verbs[o.verb] && !HMR.alias[o.verb] && o.verb !== 'help') {
_err.err({
fluenterror: HMSTATUS.invalidCommand,
quit: true,
attempted: o.orgVerb
};
}, true);
}
Command.prototype.missingArgument = function(name) {
if (this.name() !== 'new') {
throw {
fluenterror: HMSTATUS.resumeNotFound,
quit: true
};
// Override the .missingArgument behavior
Command.prototype.missingArgument = function(/* unused */) {
if (this.name() !== 'help') {
_err.err({
verb: this.name(),
fluenterror: HMSTATUS.resumeNotFound
}, true);
}
};
// Override the .helpInformation behavior
Command.prototype.helpInformation = function() {
var manPage;
manPage = FS.readFileSync(PATH.join(__dirname, 'use.txt'), 'utf8');
return chalk.green.bold(manPage);
manPage = FS.readFileSync(PATH.join(__dirname, 'help/use.txt'), 'utf8');
return M2C(manPage, 'white', 'yellow');
};
return {
args: o.args,
@ -131,17 +185,16 @@ Definition of the `main` function.
};
};
/* Init options prior to setting up command infrastructure. */
initOptions = function(ar) {
oVerb;
var args, cleanArgs, inf, isDebug, isMono, isSilent, oJSON, oVerb, optStr, optsIdx, verb, vidx;
/* jshint ignore:end */
var args, cleanArgs, inf, isAssert, isDebug, isMono, isNoEscape, isSilent, oJSON, oVerb, optStr, optsIdx, verb, vidx;
verb = '';
args = ar.slice();
cleanArgs = args.slice(2);
oJSON;
if (cleanArgs.length) {
// Support case-insensitive sub-commands (build, generate, validate, etc)
vidx = _.findIndex(cleanArgs, function(v) {
return v[0] !== '-';
});
@ -149,6 +202,7 @@ Definition of the `main` function.
oVerb = cleanArgs[vidx];
verb = args[vidx + 2] = oVerb.trim().toLowerCase();
}
// Remove --options --opts -o and process separately
optsIdx = _.findIndex(cleanArgs, function(v) {
return v === '-o' || v === '--options' || v === '--opts';
});
@ -156,34 +210,44 @@ Definition of the `main` function.
optStr = cleanArgs[optsIdx + 1];
args.splice(optsIdx + 2, 2);
if (optStr && (optStr = optStr.trim())) {
//var myJSON = JSON.parse(optStr);
if (optStr[0] === '{') {
// TODO: remove use of evil(). - hacksalot
/* jshint ignore:start */
oJSON = eval('(' + optStr + ')');
/* jshint ignore:end */
oJSON = eval('(' + optStr + ')'); // jshint ignore:line <-- no worky
} else {
inf = safeLoadJSON(optStr);
if (!inf.ex) {
oJSON = inf.json;
} else {
return inf;
}
}
}
}
}
// Grab the --debug flag, --silent, --assert and --no-color flags
isDebug = _.some(args, function(v) {
return v === '-d' || v === '--debug';
});
isSilent = _.some(args, function(v) {
return v === '-s' || v === '--silent';
});
isAssert = _.some(args, function(v) {
return v === '-a' || v === '--assert';
});
isMono = _.some(args, function(v) {
return v === '--no-color';
});
isNoEscape = _.some(args, function(v) {
return v === '--no-escape';
});
return {
color: !isMono,
debug: isDebug,
silent: isSilent,
assert: isAssert,
noescape: isNoEscape,
orgVerb: oVerb,
verb: verb,
json: oJSON,
@ -191,43 +255,65 @@ Definition of the `main` function.
};
};
/* Invoke a HackMyResume verb. */
execute = function(src, dst, opts, log) {
var hand, v;
loadOptions.call(this, opts, this.parent.jsonArgs);
hand = require('./error');
hand.init(_opts.debug, _opts.assert, _opts.silent);
var prom, v;
// Create the verb
v = new HMR.verbs[this.name()]();
// Initialize command-specific options
loadOptions.call(this, opts, this.parent.jsonArgs);
// Set up error/output handling
_opts.errHandler = v;
_out.init(_opts);
// Hook up event notifications
v.on('hmr:status', function() {
return _out["do"].apply(_out, arguments);
return _out.do.apply(_out, arguments);
});
v.on('hmr:error', function() {
return hand.err.apply(hand, arguments);
return _err.err.apply(_err, arguments);
});
v.invoke.call(v, src, dst, _opts, log);
if (v.errorCode) {
console.log('Exiting with error code ' + v.errorCode);
return process.exit(v.errorCode);
}
// Invoke the verb using promise syntax
prom = v.invoke.call(v, src, dst, _opts, log);
prom.then(executeSuccess, executeFail);
};
executeSuccess = function(/*obj*/) {};
/*
Initialize HackMyResume options.
TODO: Options loading is a little hacky, for two reasons:
- Commander.js idiosyncracies
- Need to accept JSON inputs from the command line.
*/
// Can't call _exitCallback here (process.exit) when PDF is running in BK
//_exitCallback 0; return
/* Failure handler for verb invocations. Calls process.exit by default */
executeFail = function(err) {
var finalErrorCode, msgs;
//console.dir err
finalErrorCode = -1;
if (err) {
if (err.fluenterror) {
finalErrorCode = err.fluenterror;
} else if (err.length) {
finalErrorCode = err[0].fluenterror;
} else {
finalErrorCode = err;
}
}
if (_opts.debug) {
msgs = require('./msg').errors;
logMsg(printf(M2C(msgs.exiting.msg, 'cyan'), finalErrorCode));
if (err.stack) {
logMsg(err.stack);
}
}
_exitCallback(finalErrorCode);
};
loadOptions = function(o, cmdO) {
// o and this.opts() seem to be the same (command-specific options)
// Load the specified options file (if any) and apply options
if (cmdO) {
o = EXTEND(true, o, cmdO);
}
// Merge in command-line options
o = EXTEND(true, o, this.opts());
// Kludge parent-level options until piping issue is resolved
if (this.parent.silent !== void 0 && this.parent.silent !== null) {
o.silent = this.parent.silent;
}
@ -247,9 +333,6 @@ Definition of the `main` function.
EXTEND(true, _opts, o);
};
/* Split multiple command-line filenames by the 'TO' keyword */
splitSrcDest = function() {
var params, splitAt;
params = this.parent.args.filter(function(j) {
@ -257,13 +340,17 @@ Definition of the `main` function.
});
if (params.length === 0) {
throw {
//tmpName = @name()
fluenterror: HMSTATUS.resumeNotFound,
verb: this.name(),
quit: true
};
}
// Find the TO keyword, if any
splitAt = _.findIndex(params, function(p) {
return p.toLowerCase() === 'to';
});
// TO can't be the last keyword
if (splitAt === params.length - 1 && splitAt !== -1) {
logMsg(chalk.yellow('Please ') + chalk.yellow.bold('specify an output file') + chalk.yellow(' for this operation or ') + chalk.yellow.bold('omit the TO keyword') + chalk.yellow('.'));
return;
@ -274,11 +361,11 @@ Definition of the `main` function.
};
};
/* Simple logging placeholder. */
logMsg = function() {
// eslint-disable-next-line no-console
return _opts.silent || console.log.apply(console.log, arguments);
};
}).call(this);
//# sourceMappingURL=main.js.map

14
dist/cli/msg.js vendored
View File

@ -1,11 +1,9 @@
/**
Message-handling routines for HackMyResume.
@module cli/msg
@license MIT. See LICENSE.md for details.
*/
(function() {
/**
Message-handling routines for HackMyResume.
@module cli/msg
@license MIT. See LICENSE.md for details.
*/
var PATH, YAML;
PATH = require('path');
@ -15,3 +13,5 @@ Message-handling routines for HackMyResume.
module.exports = YAML.load(PATH.join(__dirname, 'msg.yml'));
}).call(this);
//# sourceMappingURL=msg.js.map

45
dist/cli/msg.yml vendored
View File

@ -3,6 +3,8 @@ events:
msg: Invoking **%s** command.
beforeCreate:
msg: Creating new **%s** resume: **%s**
afterCreate:
msg: Creating new **%s** resume: **%s**
afterRead:
msg: Reading **%s** resume: **%s**
beforeTheme:
@ -41,6 +43,8 @@ events:
- "VALID!"
- "INVALID"
- "BROKEN"
- "MISSING"
- "ERROR"
beforePeek:
msg:
- Peeking at **%s** in **%s**
@ -79,7 +83,10 @@ errors:
readError:
msg: Reading **???** resume: **%s**
parseError:
msg: Invalid or corrupt JSON on line %s column %s.
msg:
- Invalid or corrupt JSON on line %s column %s.
- Invalid or corrupt JSON on line %s.
- Invalid or corrupt JSON.
invalidHelperUse:
msg: "**Warning**: Incorrect use of the **%s** theme helper."
fileSaveError:
@ -96,3 +103,39 @@ errors:
msg: "Invalid number of parameters. Expected: **%s**."
missingParam:
msg: The '**%s**' parameter was needed but not supplied.
createError:
msg: Failed to create **'%s'**.
exiting:
msg: Exiting with status code **%s**.
validateError:
msg: "An error occurred during validation:\n%s"
invalidOptionsFile:
msg:
- "The specified options file is invalid:\n"
- "\nMake sure the options file contains valid JSON."
optionsFileNotFound:
msg: "The specified options file is missing or inaccessible."
unknownSchema:
msg:
- "Unknown resume schema. Did you specify a valid FRESH or JRS resume?"
- |
At a minimum, a FRESH resume must include a "name" field and a "meta"
property.
"name": "John Doe",
"meta": {
"format": "FRESH@0.1.0"
}
JRS-format resumes must include a "basics" section with a "name":
"basics": {
"name": "John Doe"
}
themeHelperLoad:
msg: >-
An error occurred while attempting to load the '%s' theme helper. Is the
theme correctly installed?
dummy: dontcare
invalidSchemaVersion:
msg: "'%s' is not recognized as a valid schema version."

101
dist/cli/out.js vendored
View File

@ -1,12 +1,10 @@
/**
Output routines for HackMyResume.
@license MIT. See LICENSE.md for details.
@module cli/out
*/
(function() {
var Class, EXTEND, FS, HANDLEBARS, HME, LO, M2C, OutputHandler, PATH, YAML, _, chalk, dbgStyle, pad, printf;
/**
Output routines for HackMyResume.
@license MIT. See LICENSE.md for details.
@module cli/out
*/
var EXTEND, FS, HANDLEBARS, HME, M2C, OutputHandler, PATH, YAML, _, chalk, dbgStyle, pad, printf;
chalk = require('chalk');
@ -14,14 +12,10 @@ Output routines for HackMyResume.
_ = require('underscore');
Class = require('../utils/class.js');
M2C = require('../utils/md2chalk.js');
PATH = require('path');
LO = require('lodash');
FS = require('fs');
EXTEND = require('extend');
@ -36,23 +30,26 @@ Output routines for HackMyResume.
dbgStyle = 'cyan';
OutputHandler = class OutputHandler {
constructor(opts) {
this.init(opts);
return;
}
/** A stateful output module. All HMR console output handled here. */
OutputHandler = module.exports = Class.extend({
init: function(opts) {
init(opts) {
this.opts = EXTEND(true, this.opts || {}, opts);
this.msgs = YAML.load(PATH.join(__dirname, 'msg.yml')).events;
},
log: function(msg) {
}
log() {
var finished;
msg = msg || '';
printf = require('printf');
finished = printf.apply(printf, arguments);
return this.opts.silent || console.log(finished);
},
"do": function(evt) {
var L, WRAP, info, msg, numFormats, output, rawTpl, sty, style, suffix, template, that, themeName, tot;
return this.opts.silent || console.log(finished); // eslint-disable-line no-console
}
do(evt) {
var L, adj, info, msg, msgs, numFormats, output, rawTpl, sty, style, suffix, template, that, themeName, tot;
that = this;
L = function() {
return that.log.apply(that, arguments);
@ -60,8 +57,11 @@ Output routines for HackMyResume.
switch (evt.sub) {
case HME.begin:
return this.opts.debug && L(M2C(this.msgs.begin.msg, dbgStyle), evt.cmd.toUpperCase());
case HME.beforeCreate:
L(M2C(this.msgs.beforeCreate.msg, 'green'), evt.fmt, evt.file);
//when HME.beforeCreate
//L( M2C( this.msgs.beforeCreate.msg, 'green' ), evt.fmt, evt.file )
//break;
case HME.afterCreate:
L(M2C(this.msgs.beforeCreate.msg, evt.isError ? 'red' : 'green'), evt.fmt, evt.file);
break;
case HME.beforeTheme:
return this.opts.debug && L(M2C(this.msgs.beforeTheme.msg, dbgStyle), evt.theme.toUpperCase());
@ -81,7 +81,6 @@ Output routines for HackMyResume.
if (evt.cmd === 'build') {
themeName = this.theme.name.toUpperCase();
if (this.opts.tips && (this.theme.message || this.theme.render)) {
WRAP = require('word-wrap');
if (this.theme.message) {
L(M2C(this.msgs.afterBuild.msg[0], 'cyan'), themeName);
return L(M2C(this.theme.message, 'white'));
@ -123,35 +122,69 @@ Output routines for HackMyResume.
output = template(info);
return this.log(chalk.cyan(output));
case HME.beforeConvert:
return L(M2C(this.msgs.beforeConvert.msg, 'green'), evt.srcFile, evt.srcFmt, evt.dstFile, evt.dstFmt);
return L(M2C(this.msgs.beforeConvert.msg, evt.error ? 'red' : 'green'), evt.srcFile, evt.srcFmt, evt.dstFile, evt.dstFmt);
case HME.afterInlineConvert:
return L(M2C(this.msgs.afterInlineConvert.msg, 'gray', 'white.dim'), evt.file, evt.fmt);
case HME.afterValidate:
style = evt.isValid ? 'green' : 'yellow';
L(M2C(this.msgs.afterValidate.msg[0], 'white') + chalk[style].bold(evt.isValid ? this.msgs.afterValidate.msg[1] : this.msgs.afterValidate.msg[2]), evt.file, evt.fmt);
if (evt.errors) {
return _.each(evt.errors, function(err, idx) {
return L(chalk.yellow.bold('--> ') + chalk.yellow(err.field.replace('data.', 'resume.').toUpperCase() + ' ' + err.message));
style = 'red';
adj = '';
msgs = this.msgs.afterValidate.msg;
switch (evt.status) {
case 'valid':
style = 'green';
adj = msgs[1];
break;
case 'invalid':
style = 'yellow';
adj = msgs[2];
break;
case 'broken':
style = 'red';
adj = msgs[3];
break;
case 'missing':
style = 'red';
adj = msgs[4];
break;
case 'unknown':
style = 'red';
adj = msgs[5];
}
evt.schema = evt.schema.replace('jars', 'JSON Resume').toUpperCase();
L(M2C(msgs[0], 'white') + chalk[style].bold(adj), evt.file, evt.schema);
if (evt.violations) {
_.each(evt.violations, function(err) {
L(chalk.yellow.bold('--> ') + chalk.yellow(err.field.replace('data.', 'resume.').toUpperCase() + ' ' + err.message));
}, this);
}
break;
case HME.afterPeek:
sty = evt.error ? 'red' : (evt.target !== void 0 ? 'green' : 'yellow');
// "Peeking at 'someKey' in 'someFile'."
if (evt.requested) {
L(M2C(this.msgs.beforePeek.msg[0], sty), evt.requested, evt.file);
} else {
L(M2C(this.msgs.beforePeek.msg[1], sty), evt.file);
}
if (evt.target !== void 0) {
// If the key was present, print it
if (evt.target !== void 0 && !evt.error) {
// eslint-disable-next-line no-console
return console.dir(evt.target, {
depth: null,
colors: true
});
} else if (!evt.error) {
return L(M2C(this.msgs.afterPeek.msg, 'yellow'), evt.requested, evt.file);
} else if (evt.error) {
return L(chalk.red(evt.error.inner.inner));
}
}
}
});
};
module.exports = OutputHandler;
}).call(this);
//# sourceMappingURL=out.js.map

51
dist/cli/use.txt vendored
View File

@ -1,51 +0,0 @@
Usage:
hackmyresume <command> <sources> [TO <targets>] [<options>]
Available commands:
BUILD Build your resume to the destination format(s).
ANALYZE Analyze your resume for keywords, gaps, and metrics.
VALIDATE Validate your resume for errors and typos.
CONVERT Convert your resume between FRESH and JSON Resume.
NEW Create a new resume in FRESH or JSON Resume format.
PEEK View a specific field or element on your resume.
Available options:
--theme -t Path to a FRESH or JSON Resume theme.
--pdf -p Specify the PDF engine to use (wkhtmltopdf or phantom).
--options -o Load options from an external JSON file.
--format -f The format (FRESH or JSON Resume) to use.
--debug -d Emit extended debugging info.
--assert -a Treat resume validation warnings as errors.
--no-colors Disable terminal colors.
--tips Display theme messages and tips.
--help -h Display help documentation.
--version -v Display the current version.
Not all options are supported for all commands. For example, the
--theme option is only supported for the BUILD command.
Examples:
hackmyresume BUILD resume.json TO out/resume.all --theme modern
hackmyresume ANALYZE resume.json
hackmyresume NEW my-new-resume.json --format JRS
hackmyresume CONVERT resume-fresh.json TO resume-jrs.json
hackmyresume VALIDATE resume.json
hackmyresume PEEK resume.json employment[2].summary
Tips:
- You can specify multiple sources and/or targets for all commands.
- You can use any FRESH or JSON Resume theme with HackMyResume.
- Specify a file extension of .all to generate your resume to all
available formats supported by the theme. (BUILD command.)
- The --theme parameter can specify either the name of a preinstalled
theme, or the path to a local FRESH or JSON Resume theme.
- Visit https://www.npmjs.com/search?q=jsonresume-theme for a full
listing of all available JSON Resume themes.
- Visit https://github.com/fluentdesk/fresh-themes for a complete
listing of all available FRESH themes.
- Report bugs to https://githut.com/hacksalot/HackMyResume/issues.

View File

@ -1,55 +1,59 @@
/*
Event code definitions.
@module core/default-formats
@license MIT. See LICENSE.md for details.
*/
/** Supported resume formats. */
(function() {
/*
Event code definitions.
@module core/default-formats
@license MIT. See LICENSE.md for details.
*/
/** Supported resume formats. */
module.exports = [
{
name: 'html',
ext: 'html',
gen: new (require('../generators/html-generator'))()
}, {
},
{
name: 'txt',
ext: 'txt',
gen: new (require('../generators/text-generator'))()
}, {
},
{
name: 'doc',
ext: 'doc',
fmt: 'xml',
gen: new (require('../generators/word-generator'))()
}, {
},
{
name: 'pdf',
ext: 'pdf',
fmt: 'html',
is: false,
gen: new (require('../generators/html-pdf-cli-generator'))()
}, {
},
{
name: 'png',
ext: 'png',
fmt: 'html',
is: false,
gen: new (require('../generators/html-png-generator'))()
}, {
},
{
name: 'md',
ext: 'md',
fmt: 'txt',
gen: new (require('../generators/markdown-generator'))()
}, {
},
{
name: 'json',
ext: 'json',
gen: new (require('../generators/json-generator'))()
}, {
},
{
name: 'yml',
ext: 'yml',
fmt: 'yml',
gen: new (require('../generators/json-yaml-generator'))()
}, {
},
{
name: 'latex',
ext: 'tex',
fmt: 'latex',
@ -58,3 +62,5 @@ Event code definitions.
];
}).call(this);
//# sourceMappingURL=default-formats.js.map

View File

@ -1,18 +1,20 @@
/*
Event code definitions.
@module core/default-options
@license MIT. See LICENSE.md for details.
*/
(function() {
/*
Event code definitions.
@module core/default-options
@license MIT. See LICENSE.md for details.
*/
module.exports = {
theme: 'modern',
prettify: {
indent_size: 2,
unformatted: ['em', 'strong'],
max_char: 80
max_char: 80 // ← See lib/html.js in above-linked repo
}
};
// wrap_line_length: 120, ← Don't use this
}).call(this);
//# sourceMappingURL=default-options.js.map

View File

@ -1,11 +1,9 @@
/*
Event code definitions.
@module core/event-codes
@license MIT. See LICENSE.md for details.
*/
(function() {
/*
Event code definitions.
@module core/event-codes
@license MIT. See LICENSE.md for details.
*/
module.exports = {
error: -1,
success: 0,
@ -33,7 +31,12 @@ Event code definitions.
beforeInlineConvert: 22,
afterInlineConvert: 23,
beforeValidate: 24,
afterValidate: 25
afterValidate: 25,
beforeWrite: 26,
afterWrite: 27,
applyTheme: 28
};
}).call(this);
//# sourceMappingURL=event-codes.js.map

View File

@ -1,18 +1,15 @@
/**
The HackMyResume date representation.
@license MIT. See LICENSE.md for details.
@module core/fluent-date
*/
(function() {
/**
The HackMyResume date representation.
@license MIT. See LICENSE.md for details.
@module core/fluent-date
*/
var FluentDate, abbr, moment, months;
moment = require('moment');
require('../utils/string');
/**
Create a FluentDate from a string or Moment date object. There are a few date
formats to be aware of here.
@ -28,20 +25,17 @@ The HackMyResume date representation.
deprecation warnings, it's recommended to either a) explicitly specify the date
format or b) use an ISO format. For clarity, we handle these cases explicitly.
@class FluentDate
*/
FluentDate = (function() {
function FluentDate(dt) {
*/
FluentDate = class FluentDate {
constructor(dt) {
this.rep = this.fmt(dt);
}
FluentDate.isCurrent = function(dt) {
static isCurrent(dt) {
return !dt || (String.is(dt) && /^(present|now|current)$/.test(dt));
};
}
return FluentDate;
})();
};
months = {};
@ -64,20 +58,20 @@ The HackMyResume date representation.
throws = (throws === void 0 || throws === null) || throws;
if (typeof dt === 'string' || dt instanceof String) {
dt = dt.toLowerCase().trim();
if (/^(present|now|current)$/.test(dt)) {
if (/^(present|now|current)$/.test(dt)) { // "Present", "Now"
return moment();
} else if (/^\D+\s+\d{4}$/.test(dt)) {
} else if (/^\D+\s+\d{4}$/.test(dt)) { // "Mar 2015"
parts = dt.split(' ');
month = months[parts[0]] || abbr[parts[0]];
temp = parts[1] + '-' + ((ref = month < 10) != null ? ref : '0' + {
month: month.toString()
});
return moment(temp, 'YYYY-MM');
} else if (/^\d{4}-\d{1,2}$/.test(dt)) {
} else if (/^\d{4}-\d{1,2}$/.test(dt)) { // "2015-03", "1998-4"
return moment(dt, 'YYYY-MM');
} else if (/^\s*\d{4}\s*$/.test(dt)) {
} else if (/^\s*\d{4}\s*$/.test(dt)) { // "2015"
return moment(dt, 'YYYY');
} else if (/^\s*$/.test(dt)) {
} else if (/^\s*$/.test(dt)) { // "", " "
return moment();
} else {
mt = moment(dt);
@ -103,3 +97,5 @@ The HackMyResume date representation.
};
}).call(this);
//# sourceMappingURL=fluent-date.js.map

View File

@ -1,14 +1,17 @@
/**
Definition of the FRESHResume class.
@license MIT. See LICENSE.md for details.
@module core/fresh-resume
*/
(function() {
var AbstractResume, CONVERTER, FS, FluentDate, FreshResume, JRSResume, MD, PATH, XML, _, __, _parseDates, extend, moment, validator,
extend1 = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
hasProp = {}.hasOwnProperty;
/**
Definition of the FRESHResume class.
@license MIT. See LICENSE.md for details.
@module core/fresh-resume
*/
/**
Convert human-friendly dates into formal Moment.js dates for all collections.
We don't want to lose the raw textual date as entered by the user, so we store
the Moment-ified date as a separate property with a prefix of .safe. For ex:
job.startDate is the date as entered by the user. job.safeStartDate is the
parsed Moment.js date that we actually use in processing.
*/
var CONVERTER, FS, FluentDate, FreshResume, JRSResume, MD, PATH, XML, _, __, _parseDates, extend, moment, validator;
FS = require('fs');
@ -34,44 +37,20 @@ Definition of the FRESHResume class.
FluentDate = require('./fluent-date');
AbstractResume = require('./abstract-resume');
/**
A FRESH resume or CV. FRESH resumes are backed by JSON, and each FreshResume
object is an instantiation of that JSON decorated with utility methods.
@constructor
*/
FreshResume = (function(superClass) {
extend1(FreshResume, superClass);
function FreshResume() {
return FreshResume.__super__.constructor.apply(this, arguments);
}
/** Initialize the FreshResume from file. */
FreshResume.prototype.open = function(file, opts) {
var raw, ret;
raw = FS.readFileSync(file, 'utf8');
ret = this.parse(raw, opts);
this.imp.file = file;
return ret;
};
*/
FreshResume = class FreshResume { // extends AbstractResume
/** Initialize the the FreshResume from JSON string data. */
FreshResume.prototype.parse = function(stringData, opts) {
parse(stringData, opts) {
var ref;
this.imp = (ref = this.imp) != null ? ref : {
raw: stringData
};
return this.parseJSON(JSON.parse(stringData), opts);
};
}
/**
Initialize the FreshResume from JSON.
@ -81,27 +60,22 @@ Definition of the FRESHResume class.
@param rep {Object} The raw JSON representation.
@param opts {Object} Resume loading and parsing options.
{
date: Perform safe date conversion.
sort: Sort resume items by date.
compute: Prepare computed resume totals.
date: Perform safe date conversion.
sort: Sort resume items by date.
compute: Prepare computed resume totals.
}
*/
FreshResume.prototype.parseJSON = function(rep, opts) {
var ignoreList, ref, scrubbed, that, traverse;
that = this;
traverse = require('traverse');
ignoreList = [];
scrubbed = traverse(rep).map(function(x) {
if (!this.isLeaf && this.node.ignore) {
if (this.node.ignore === true || this.node.ignore === 'true') {
ignoreList.push(this.node);
return this.remove();
}
}
});
extend(true, this, scrubbed);
*/
parseJSON(rep, opts) {
var ignoreList, privateList, ref, scrubbed, scrubber;
if (opts && opts.privatize) {
// Ignore any element with the 'ignore: true' or 'private: true' designator.
scrubber = require('../utils/resume-scrubber');
({scrubbed, ignoreList, privateList} = scrubber.scrubResume(rep, opts));
}
// Now apply the resume representation onto this object
extend(true, this, opts && opts.privatize ? scrubbed : rep);
if (!((ref = this.imp) != null ? ref.processed : void 0)) {
// Set up metadata TODO: Clean up metadata on the object model.
opts = opts || {};
if (opts.imp === void 0 || opts.imp) {
this.imp = this.imp || {};
@ -111,6 +85,7 @@ Definition of the FRESHResume class.
}
}
this.imp.processed = true;
// Parse dates, sort dates, and calculate computed values
(opts.date === void 0 || opts.date) && _parseDates.call(this);
(opts.sort === void 0 || opts.sort) && this.sort();
(opts.compute === void 0 || opts.compute) && (this.computed = {
@ -119,34 +94,43 @@ Definition of the FRESHResume class.
});
}
return this;
};
}
/** Save the sheet to disk (for environments that have disk access). */
FreshResume.prototype.save = function(filename) {
save(filename) {
this.imp.file = filename || this.imp.file;
FS.writeFileSync(this.imp.file, this.stringify(), 'utf8');
return this;
};
}
/**
Save the sheet to disk in a specific format, either FRESH or JSON Resume.
*/
FreshResume.prototype.saveAs = function(filename, format) {
var newRep;
if (format !== 'JRS') {
*/
saveAs(filename, format) {
var newRep, parts, safeFormat, useEdgeSchema;
// If format isn't specified, default to FRESH
safeFormat = (format && format.trim()) || 'FRESH';
// Validate against the FRESH version regex
// freshVersionReg = require '../utils/fresh-version-regex'
// if (not freshVersionReg().test( safeFormat ))
// throw badVer: safeFormat
parts = safeFormat.split('@');
if (parts[0] === 'FRESH') {
this.imp.file = filename || this.imp.file;
FS.writeFileSync(this.imp.file, this.stringify(), 'utf8');
} else {
newRep = CONVERTER.toJRS(this);
} else if (parts[0] === 'JRS') {
useEdgeSchema = parts.length > 1 ? parts[1] === '1' : false;
newRep = CONVERTER.toJRS(this, {
edge: useEdgeSchema
});
FS.writeFileSync(filename, JRSResume.stringify(newRep), 'utf8');
} else {
throw {
badVer: safeFormat
};
}
return this;
};
}
/**
Duplicate this FreshResume instance.
@ -154,47 +138,40 @@ Definition of the FRESHResume class.
and then passes the result into a new FreshResume instance via .parseJSON.
We do it this way to create a true clone of the object without re-running any
of the associated processing.
*/
FreshResume.prototype.dupe = function() {
*/
dupe() {
var jso, rnew;
jso = extend(true, {}, this);
rnew = new FreshResume();
rnew.parseJSON(jso, {});
return rnew;
};
}
/**
Convert this object to a JSON string, sanitizing meta-properties along the
way.
*/
FreshResume.prototype.stringify = function() {
*/
stringify() {
return FreshResume.stringify(this);
};
}
/**
Create a copy of this resume in which all string fields have been run through
a transformation function (such as a Markdown filter or XML encoder).
TODO: Move this out of FRESHResume.
*/
FreshResume.prototype.transformStrings = function(filt, transformer) {
*/
transformStrings(filt, transformer) {
var ret, trx;
ret = this.dupe();
trx = require('../utils/string-transformer');
return trx(ret, filt, transformer);
};
}
/**
Create a copy of this resume in which all fields have been interpreted as
Markdown.
*/
FreshResume.prototype.markdownify = function() {
*/
markdownify() {
var MDIN, trx;
MDIN = function(txt) {
return MD(txt || '').replace(/^\s*<p>|<\/p>\s*$/gi, '');
@ -206,42 +183,49 @@ Definition of the FRESHResume class.
return MDIN(val);
};
return this.transformStrings(['skills', 'url', 'start', 'end', 'date'], trx);
};
}
/**
Create a copy of this resume in which all fields have been interpreted as
Markdown.
*/
FreshResume.prototype.xmlify = function() {
*/
xmlify() {
var trx;
trx = function(key, val) {
return XML(val);
};
return this.transformStrings([], trx);
};
}
/** Return the resume format. */
FreshResume.prototype.format = function() {
format() {
return 'FRESH';
};
}
/**
Return internal metadata. Create if it doesn't exist.
*/
FreshResume.prototype.i = function() {
*/
i() {
return this.imp = this.imp || {};
};
}
/**
Return a unique list of all skills declared in the resume.
*/
// TODO: Several problems here:
// 1) Confusing name. Easily confused with the keyword-inspector module, which
// parses resume body text looking for these same keywords. This should probably
// be renamed.
/** Return a unique list of all keywords across all skills. */
// 2) Doesn't bother trying to integrate skills.list with skills.sets if they
// happen to declare different skills, and if skills.sets declares ONE skill and
// skills.list declared 50, only 1 skill will be registered.
FreshResume.prototype.keywords = function() {
// 3) In the future, skill.sets should only be able to use skills declared in
// skills.list. That is, skills.list is the official record of a candidate's
// declared skills. skills.sets is just a way of grouping those into skillsets
// for easier consumption.
keywords() {
var flatSkills;
flatSkills = [];
if (this.skills) {
@ -259,19 +243,17 @@ Definition of the FRESHResume class.
flatSkills = _.uniq(flatSkills);
}
return flatSkills;
};
}
/**
Reset the sheet to an empty state. TODO: refactor/review
*/
FreshResume.prototype.clear = function(clearMeta) {
*/
clear(clearMeta) {
clearMeta = ((clearMeta === void 0) && true) || clearMeta;
if (clearMeta) {
delete this.imp;
}
delete this.computed;
delete this.computed; // Don't use Object.keys() here
delete this.employment;
delete this.service;
delete this.education;
@ -281,14 +263,12 @@ Definition of the FRESHResume class.
delete this.interests;
delete this.skills;
return delete this.social;
};
}
/**
Get a safe count of the number of things in a section.
*/
FreshResume.prototype.count = function(obj) {
*/
count(obj) {
if (!obj) {
return 0;
}
@ -299,14 +279,11 @@ Definition of the FRESHResume class.
return obj.sets.length;
}
return obj.length || 0;
};
}
/** Add work experience to the sheet. */
FreshResume.prototype.add = function(moniker) {
add(moniker) {
var defSheet, newObject;
defSheet = FreshResume["default"]();
defSheet = FreshResume.default();
newObject = defSheet[moniker].history ? $.extend(true, {}, defSheet[moniker].history[0]) : moniker === 'skills' ? $.extend(true, {}, defSheet.skills.sets[0]) : $.extend(true, {}, defSheet[moniker][0]);
this[moniker] = this[moniker] || [];
if (this[moniker].history) {
@ -317,63 +294,53 @@ Definition of the FRESHResume class.
this[moniker].push(newObject);
}
return newObject;
};
}
/**
Determine if the sheet includes a specific social profile (eg, GitHub).
*/
FreshResume.prototype.hasProfile = function(socialNetwork) {
*/
hasProfile(socialNetwork) {
socialNetwork = socialNetwork.trim().toLowerCase();
return this.social && _.some(this.social, function(p) {
return p.network.trim().toLowerCase() === socialNetwork;
});
};
}
/** Return the specified network profile. */
FreshResume.prototype.getProfile = function(socialNetwork) {
getProfile(socialNetwork) {
socialNetwork = socialNetwork.trim().toLowerCase();
return this.social && _.find(this.social, function(sn) {
return sn.network.trim().toLowerCase() === socialNetwork;
});
};
}
/**
Return an array of profiles for the specified network, for when the user
has multiple eg. GitHub accounts.
*/
FreshResume.prototype.getProfiles = function(socialNetwork) {
*/
getProfiles(socialNetwork) {
socialNetwork = socialNetwork.trim().toLowerCase();
return this.social && _.filter(this.social, function(sn) {
return sn.network.trim().toLowerCase() === socialNetwork;
});
};
}
/** Determine if the sheet includes a specific skill. */
FreshResume.prototype.hasSkill = function(skill) {
hasSkill(skill) {
skill = skill.trim().toLowerCase();
return this.skills && _.some(this.skills, function(sk) {
return sk.keywords && _.some(sk.keywords, function(kw) {
return kw.trim().toLowerCase() === skill;
});
});
};
}
/** Validate the sheet against the FRESH Resume schema. */
FreshResume.prototype.isValid = function(info) {
isValid(info) {
var ret, schemaObj, validate;
schemaObj = require('fresca');
schemaObj = require('fresh-resume-schema');
validator = require('is-my-json-valid');
validate = validator(schemaObj, {
validate = validator(schemaObj, { // See Note [1].
formats: {
date: /^\d{4}(?:-(?:0[0-9]{1}|1[0-2]{1})(?:-[0-9]{2})?)?$/
}
@ -384,19 +351,19 @@ Definition of the FRESHResume class.
this.imp.validationErrors = validate.errors;
}
return ret;
};
FreshResume.prototype.duration = function(unit) {
return FreshResume.__super__.duration.call(this, 'employment.history', 'start', 'end', unit);
};
}
duration(unit) {
var inspector;
inspector = require('../inspectors/duration-inspector');
return inspector.run(this, 'employment.history', 'start', 'end', unit);
}
/**
Sort dated things on the sheet by start date descending. Assumes that dates
on the sheet have been processed with _parseDates().
*/
FreshResume.prototype.sort = function() {
*/
sort() {
var byDateDesc, sortSection;
byDateDesc = function(a, b) {
if (a.safe.start.isBefore(b.safe.start)) {
@ -430,30 +397,24 @@ Definition of the FRESHResume class.
return (a.safe.date.isAfter(b.safe.date) && -1) || 0;
}
});
};
return FreshResume;
})(AbstractResume);
}
};
/**
Get the default (starter) sheet.
*/
FreshResume["default"] = function() {
return new FreshResume().parseJSON(require('fresh-resume-starter'));
*/
FreshResume.default = function() {
return new FreshResume().parseJSON(require('fresh-resume-starter').fresh);
};
/**
Convert the supplied FreshResume to a JSON string, sanitizing meta-properties
along the way.
*/
*/
FreshResume.stringify = function(obj) {
var replacer;
replacer = function(key, value) {
replacer = function(key, value) { // Exclude these keys from stringification
var exKeys;
exKeys = ['imp', 'warnings', 'computed', 'filt', 'ctrl', 'index', 'safe', 'result', 'isModified', 'htmlPreview', 'display_progress_bar'];
if (_.some(exKeys, function(val) {
@ -467,19 +428,11 @@ Definition of the FRESHResume class.
return JSON.stringify(obj, replacer, 2);
};
/**
Convert human-friendly dates into formal Moment.js dates for all collections.
We don't want to lose the raw textual date as entered by the user, so we store
the Moment-ified date as a separate property with a prefix of .safe. For ex:
job.startDate is the date as entered by the user. job.safeStartDate is the
parsed Moment.js date that we actually use in processing.
*/
_parseDates = function() {
var _fmt, replaceDatesInObject, that;
_fmt = require('./fluent-date').fmt;
that = this;
// TODO: refactor recursion
replaceDatesInObject = function(obj) {
if (!obj) {
return;
@ -511,9 +464,15 @@ Definition of the FRESHResume class.
});
};
/** Export the Sheet function/ctor. */
module.exports = FreshResume;
// Note 1: Adjust default date validation to allow YYYY and YYYY-MM formats
// in addition to YYYY-MM-DD. The original regex:
// /^\d{4}-(?:0[0-9]{1}|1[0-2]{1})-[0-9]{2}$/
}).call(this);
//# sourceMappingURL=fresh-resume.js.map

View File

@ -1,12 +1,13 @@
/**
Definition of the FRESHTheme class.
@module core/fresh-theme
@license MIT. See LICENSE.md for details.
*/
(function() {
var EXTEND, FRESHTheme, FS, HMSTATUS, PATH, READFILES, _, friendlyName, loadExplicit, loadImplicit, loadSafeJson, moment, parsePath, pathExists, validator;
/**
Definition of the FRESHTheme class.
@module core/fresh-theme
@license MIT. See LICENSE.md for details.
*/
/* Load and parse theme source files. */
/* Load a single theme file. */
/* Return a more friendly name for certain formats. */
var EXTEND, FRESHTheme, FS, HMSTATUS, PATH, READFILES, _, _load, _loadOne, friendlyName, loadSafeJson, moment, parsePath, pathExists, validator;
FS = require('fs');
@ -30,137 +31,98 @@ Definition of the FRESHTheme class.
READFILES = require('recursive-readdir-sync');
/* A representation of a FRESH theme asset.
@class FRESHTheme */
FRESHTheme = class FRESHTheme {
constructor() {
this.baseFolder = 'src';
return;
}
/*
The FRESHTheme class is a representation of a FRESH theme
asset. See also: JRSTheme.
@class FRESHTheme
*/
FRESHTheme = (function() {
function FRESHTheme() {}
/*
Open and parse the specified theme.
*/
FRESHTheme.prototype.open = function(themeFolder) {
/* Open and parse the specified theme. */
open(themeFolder) {
var cached, formatsHash, pathInfo, that, themeFile, themeInfo;
this.folder = themeFolder;
// Open the [theme-name].json file; should have the same name as folder
pathInfo = parsePath(themeFolder);
// Set up a formats hash for the theme
formatsHash = {};
// Load the theme
themeFile = PATH.join(themeFolder, 'theme.json');
themeInfo = loadSafeJson(themeFile);
if (themeInfo.ex) {
throw {
fluenterror: themeInfo.ex.operation === 'parse' ? HMSTATUS.parseError : HMSTATUS.readError,
inner: themeInfo.ex.inner
fluenterror: themeInfo.ex.op === 'parse' ? HMSTATUS.parseError : HMSTATUS.readError
};
({
inner: themeInfo.ex.inner
});
}
that = this;
// Move properties from the theme JSON file to the theme object
EXTEND(true, this, themeInfo.json);
// Check for an "inherits" entry in the theme JSON.
if (this.inherits) {
cached = {};
_.each(this.inherits, function(th, key) {
var d, themePath, themesFolder;
themesFolder = require.resolve('fresh-themes');
d = parsePath(themeFolder).dirname;
themePath = PATH.join(d, th);
var d, themePath, themesObj;
// First, see if this is one of the predefined FRESH themes. There are
// only a handful of these, but they may change over time, so we need to
// query the official source of truth: the fresh-themes repository, which
// mounts the themes conveniently by name to the module object, and which
// is embedded locally inside the HackMyResume installation.
// TODO: merge this code with
themesObj = require('fresh-themes');
if (_.has(themesObj.themes, th)) {
themePath = PATH.join(parsePath(require.resolve('fresh-themes')).dirname, '/themes/', th);
} else {
d = parsePath(th).dirname;
themePath = PATH.join(d, th);
}
cached[th] = cached[th] || new FRESHTheme().open(themePath);
return formatsHash[key] = cached[th].getFormat(key);
});
}
if (!!this.formats) {
formatsHash = loadExplicit.call(this, formatsHash);
this.explicit = true;
} else {
formatsHash = loadImplicit.call(this, formatsHash);
}
// Load theme files
formatsHash = _load.call(this, formatsHash);
// Cache
this.formats = formatsHash;
// Set the official theme name
this.name = parsePath(this.folder).name;
return this;
};
}
/* Determine if the theme supports the specified output format. */
FRESHTheme.prototype.hasFormat = function(fmt) {
hasFormat(fmt) {
return _.has(this.formats, fmt);
};
}
/* Determine if the theme supports the specified output format. */
FRESHTheme.prototype.getFormat = function(fmt) {
getFormat(fmt) {
return this.formats[fmt];
};
}
return FRESHTheme;
};
})();
/* Load the theme implicitly, by scanning the theme folder for files. TODO:
Refactor duplicated code with loadExplicit.
*/
loadImplicit = function(formatsHash) {
var fmts, major, that, tplFolder;
_load = function(formatsHash) {
var copyOnly, fmts, jsFiles, major, that, tplFolder;
that = this;
major = false;
tplFolder = PATH.join(this.folder, 'src');
tplFolder = PATH.join(this.folder, this.baseFolder);
copyOnly = ['.ttf', '.otf', '.png', '.jpg', '.jpeg', '.pdf'];
// Iterate over all files in the theme folder, producing an array, fmts,
// containing info for each file. While we're doing that, also build up
// the formatsHash object.
fmts = READFILES(tplFolder).map(function(absPath) {
var idx, isMajor, obj, outFmt, pathInfo, portion, reg, res;
pathInfo = parsePath(absPath);
outFmt = '';
isMajor = false;
portion = pathInfo.dirname.replace(tplFolder, '');
if (portion && portion.trim()) {
if (portion[1] === '_') {
return;
}
reg = /^(?:\/|\\)(html|latex|doc|pdf|png|partials)(?:\/|\\)?/ig;
res = reg.exec(portion);
if (res) {
if (res[1] !== 'partials') {
outFmt = res[1];
} else {
that.partials = that.partials || [];
that.partials.push({
name: pathInfo.name,
path: absPath
});
return null;
}
}
}
if (!outFmt) {
idx = pathInfo.name.lastIndexOf('-');
outFmt = idx === -1 ? pathInfo.name : pathInfo.name.substr(idx + 1);
isMajor = true;
}
formatsHash[outFmt] = formatsHash[outFmt] || {
outFormat: outFmt,
files: []
};
obj = {
action: 'transform',
path: absPath,
major: isMajor,
orgPath: PATH.relative(tplFolder, absPath),
ext: pathInfo.extname.slice(1),
title: friendlyName(outFmt),
pre: outFmt,
data: FS.readFileSync(absPath, 'utf8'),
css: null
};
formatsHash[outFmt].files.push(obj);
return obj;
});
return _loadOne.call(this, absPath, formatsHash, tplFolder);
}, this);
// Now, get all the CSS files...
this.cssFiles = fmts.filter(function(fmt) {
return fmt && (fmt.ext === 'css');
});
// For each CSS file, get its corresponding HTML file. It's possible that
// a theme can have a CSS file but *no* HTML file, as when a theme author
// creates a pure CSS override of an existing theme.
this.cssFiles.forEach(function(cssf) {
var idx;
idx = _.findIndex(fmts, function(fmt) {
@ -172,6 +134,8 @@ Definition of the FRESHTheme class.
return fmts[idx].cssPath = cssf.path;
} else {
if (that.inherits) {
// Found a CSS file without an HTML file in a theme that inherits
// from another theme. This is the override CSS file.
return that.overrides = {
file: cssf.path,
data: cssf.data
@ -179,93 +143,111 @@ Definition of the FRESHTheme class.
}
}
});
// Now, save all the javascript file paths to a theme property.
jsFiles = fmts.filter(function(fmt) {
return fmt && (fmt.ext === 'js');
});
this.jsFiles = jsFiles.map(function(jsf) {
return jsf['path'];
});
return formatsHash;
};
/*
Load the theme explicitly, by following the 'formats' hash
in the theme's JSON settings file.
*/
loadExplicit = function(formatsHash) {
var act, fmts, that, tplFolder;
tplFolder = PATH.join(this.folder, 'src');
act = null;
that = this;
fmts = READFILES(tplFolder).map(function(absPath) {
var absPathSafe, idx, obj, outFmt, pathInfo, portion, reg, res;
act = null;
pathInfo = parsePath(absPath);
absPathSafe = absPath.trim().toLowerCase();
outFmt = _.find(Object.keys(that.formats), function(fmtKey) {
_loadOne = function(absPath, formatsHash, tplFolder) {
var absPathSafe, act, defFormats, idx, isPrimary, obj, outFmt, pathInfo, portion, ref, ref1, reg, res;
pathInfo = parsePath(absPath);
if (pathInfo.basename.toLowerCase() === 'theme.json') {
return;
}
absPathSafe = absPath.trim().toLowerCase();
outFmt = '';
act = 'copy';
isPrimary = false;
// If this is an "explicit" theme, all files of importance are specified in
// the "transform" section of the theme.json file.
if (this.explicit) {
outFmt = _.find(Object.keys(this.formats), function(fmtKey) {
var fmtVal;
fmtVal = that.formats[fmtKey];
fmtVal = this.formats[fmtKey];
return _.some(fmtVal.transform, function(fpath) {
var absPathB;
absPathB = PATH.join(that.folder, fpath).trim().toLowerCase();
absPathB = PATH.join(this.folder, fpath).trim().toLowerCase();
return absPathB === absPathSafe;
});
});
}, this);
}, this);
if (outFmt) {
act = 'transform';
}
if (!outFmt) {
portion = pathInfo.dirname.replace(tplFolder, '');
if (portion && portion.trim()) {
reg = /^(?:\/|\\)(html|latex|doc|pdf)(?:\/|\\)?/ig;
res = reg.exec(portion);
res && (outFmt = res[1]);
}
if (!outFmt) {
// If this file lives in a specific format folder within the theme,
// such as "/latex" or "/html", then that format is the implicit output
// format for all files within the folder
portion = pathInfo.dirname.replace(tplFolder, '');
if (portion && portion.trim()) {
if (portion[1] === '_') {
return;
}
reg = /^(?:\/|\\)(html|latex|doc|pdf|png|partials)(?:\/|\\)?/ig;
res = reg.exec(portion);
if (res) {
if (res[1] !== 'partials') {
outFmt = res[1];
if (!this.explicit) {
act = 'transform';
}
} else {
this.partials = this.partials || [];
this.partials.push({
name: pathInfo.name,
path: absPath
});
return null;
}
}
}
if (!outFmt) {
idx = pathInfo.name.lastIndexOf('-');
outFmt = idx === -1 ? pathInfo.name : pathInfo.name.substr(idx + 1);
}
if (!outFmt) {
idx = pathInfo.name.lastIndexOf('-');
outFmt = idx === -1 ? pathInfo.name : pathInfo.name.substr(idx + 1);
if (!this.explicit) {
act = 'transform';
}
formatsHash[outFmt] = formatsHash[outFmt] || {
outFormat: outFmt,
files: [],
symLinks: that.formats[outFmt].symLinks
};
obj = {
action: act,
orgPath: PATH.relative(that.folder, absPath),
path: absPath,
ext: pathInfo.extname.slice(1),
title: friendlyName(outFmt),
pre: outFmt,
data: FS.readFileSync(absPath, 'utf8'),
css: null
};
formatsHash[outFmt].files.push(obj);
return obj;
});
this.cssFiles = fmts.filter(function(fmt) {
return fmt.ext === 'css';
});
this.cssFiles.forEach(function(cssf) {
var idx;
idx = _.findIndex(fmts, function(fmt) {
return fmt.pre === cssf.pre && fmt.ext === 'html';
defFormats = require('./default-formats');
isPrimary = _.some(defFormats, function(form) {
return form.name === outFmt && pathInfo.extname !== '.css';
});
fmts[idx].css = cssf.data;
return fmts[idx].cssPath = cssf.path;
});
fmts = fmts.filter(function(fmt) {
return fmt.ext !== 'css';
});
return formatsHash;
}
// Make sure we have a valid formatsHash
formatsHash[outFmt] = formatsHash[outFmt] || {
outFormat: outFmt,
files: []
};
// Move symlink descriptions from theme.json to the format
if ((ref = this.formats) != null ? (ref1 = ref[outFmt]) != null ? ref1.symLinks : void 0 : void 0) {
formatsHash[outFmt].symLinks = this.formats[outFmt].symLinks;
}
// Create the file representation object
obj = {
action: act,
primary: isPrimary,
path: absPath,
orgPath: PATH.relative(tplFolder, absPath),
ext: pathInfo.extname.slice(1),
title: friendlyName(outFmt),
pre: outFmt,
// outFormat: outFmt || pathInfo.name,
data: FS.readFileSync(absPath, 'utf8'),
css: null
};
// Add this file to the list of files for this format type.
formatsHash[outFmt].files.push(obj);
return obj;
};
/*
Return a more friendly name for certain formats.
TODO: Refactor
*/
friendlyName = function(val) {
var friendly;
val = val.trim().toLowerCase();
val = (val && val.trim().toLowerCase()) || '';
friendly = {
yml: 'yaml',
md: 'markdown',
@ -277,3 +259,5 @@ Definition of the FRESHTheme class.
module.exports = FRESHTheme;
}).call(this);
//# sourceMappingURL=fresh-theme.js.map

View File

@ -1,14 +1,17 @@
/**
Definition of the JRSResume class.
@license MIT. See LICENSE.md for details.
@module core/jrs-resume
*/
(function() {
var AbstractResume, CONVERTER, FS, JRSResume, MD, PATH, _, _parseDates, extend, moment, validator,
extend1 = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
hasProp = {}.hasOwnProperty;
/**
Definition of the JRSResume class.
@license MIT. See LICENSE.md for details.
@module core/jrs-resume
*/
/**
Convert human-friendly dates into formal Moment.js dates for all collections.
We don't want to lose the raw textual date as entered by the user, so we store
the Moment-ified date as a separate property with a prefix of .safe. For ex:
job.startDate is the date as entered by the user. job.safeStartDate is the
parsed Moment.js date that we actually use in processing.
*/
var CONVERTER, FS, JRSResume, MD, PATH, _, _parseDates, extend, moment, validator;
FS = require('fs');
@ -26,169 +29,247 @@ Definition of the JRSResume class.
moment = require('moment');
AbstractResume = require('./abstract-resume');
/**
A JRS resume or CV. JRS resumes are backed by JSON, and each JRSResume object
is an instantiation of that JSON decorated with utility methods.
@class JRSResume
*/
JRSResume = (function(superClass) {
var clear, format;
extend1(JRSResume, superClass);
function JRSResume() {
return JRSResume.__super__.constructor.apply(this, arguments);
}
/** Initialize the JSResume from file. */
JRSResume.prototype.open = function(file, opts) {
var raw, ret;
raw = FS.readFileSync(file, 'utf8');
ret = this.parse(raw, opts);
this.imp.file = file;
return ret;
};
/** Initialize the the JSResume from string. */
JRSResume.prototype.parse = function(stringData, opts) {
var ref;
this.imp = (ref = this.imp) != null ? ref : {
raw: stringData
};
return this.parseJSON(JSON.parse(stringData), opts);
};
JRSResume = (function() {
/** Reset the sheet to an empty state. */
var clear;
/**
Initialize the JRSResume object from JSON.
Open and parse the specified JRS resume. Merge the JSON object model onto
this Sheet instance with extend() and convert sheet dates to a safe &
consistent format. Then sort each section by startDate descending.
@param rep {Object} The raw JSON representation.
@param opts {Object} Resume loading and parsing options.
{
A JRS resume or CV. JRS resumes are backed by JSON, and each JRSResume object
is an instantiation of that JSON decorated with utility methods.
@class JRSResume
*/
class JRSResume { // extends AbstractResume
/** Initialize the the JSResume from string. */
parse(stringData, opts) {
var ref;
this.imp = (ref = this.imp) != null ? ref : {
raw: stringData
};
return this.parseJSON(JSON.parse(stringData), opts);
}
/**
Initialize the JRSResume object from JSON.
Open and parse the specified JRS resume. Merge the JSON object model onto
this Sheet instance with extend() and convert sheet dates to a safe &
consistent format. Then sort each section by startDate descending.
@param rep {Object} The raw JSON representation.
@param opts {Object} Resume loading and parsing options.
{
date: Perform safe date conversion.
sort: Sort resume items by date.
compute: Prepare computed resume totals.
}
*/
JRSResume.prototype.parseJSON = function(rep, opts) {
var ignoreList, ref, scrubbed, that, traverse;
opts = opts || {};
that = this;
traverse = require('traverse');
ignoreList = [];
scrubbed = traverse(rep).map(function(x) {
if (!this.isLeaf && this.node.ignore) {
if (this.node.ignore === true || this.node.ignore === 'true') {
ignoreList.push(this.node);
return this.remove();
}
}
});
extend(true, this, scrubbed);
if (!((ref = this.imp) != null ? ref.processed : void 0)) {
}
*/
parseJSON(rep, opts) {
var ignoreList, privateList, ref, scrubbed, scrubber;
opts = opts || {};
if (opts.imp === void 0 || opts.imp) {
this.imp = this.imp || {};
this.imp.title = (opts.title || this.imp.title) || this.basics.name;
if (!this.imp.raw) {
this.imp.raw = JSON.stringify(rep);
}
if (opts.privatize) {
scrubber = require('../utils/resume-scrubber');
// Ignore any element with the 'ignore: true' or 'private: true' designator.
({scrubbed, ignoreList, privateList} = scrubber.scrubResume(rep, opts));
}
this.imp.processed = true;
// Extend resume properties onto ourself.
extend(true, this, opts.privatize ? scrubbed : rep);
if (!((ref = this.imp) != null ? ref.processed : void 0)) {
// Set up metadata TODO: Clean up metadata on the object model.
opts = opts || {};
if (opts.imp === void 0 || opts.imp) {
this.imp = this.imp || {};
this.imp.title = (opts.title || this.imp.title) || this.basics.name;
if (!this.imp.raw) {
this.imp.raw = JSON.stringify(rep);
}
}
this.imp.processed = true;
}
// Parse dates, sort dates, and calculate computed values
(opts.date === void 0 || opts.date) && _parseDates.call(this);
(opts.sort === void 0 || opts.sort) && this.sort();
if (opts.compute === void 0 || opts.compute) {
this.basics.computed = {
numYears: this.duration(),
keywords: this.keywords()
};
}
return this;
}
(opts.date === void 0 || opts.date) && _parseDates.call(this);
(opts.sort === void 0 || opts.sort) && this.sort();
if (opts.compute === void 0 || opts.compute) {
this.basics.computed = {
numYears: this.duration(),
keywords: this.keywords()
};
}
return this;
};
/** Save the sheet to disk (for environments that have disk access). */
JRSResume.prototype.save = function(filename) {
this.imp.file = filename || this.imp.file;
FS.writeFileSync(this.imp.file, this.stringify(this), 'utf8');
return this;
};
/** Save the sheet to disk in a specific format, either FRESH or JRS. */
JRSResume.prototype.saveAs = function(filename, format) {
var newRep, stringRep;
if (format === 'JRS') {
/** Save the sheet to disk (for environments that have disk access). */
save(filename) {
this.imp.file = filename || this.imp.file;
FS.writeFileSync(this.imp.file, this.stringify(), 'utf8');
} else {
newRep = CONVERTER.toFRESH(this);
stringRep = CONVERTER.toSTRING(newRep);
FS.writeFileSync(filename, stringRep, 'utf8');
FS.writeFileSync(this.imp.file, this.stringify(this), 'utf8');
return this;
}
return this;
};
/** Save the sheet to disk in a specific format, either FRESH or JRS. */
saveAs(filename, format) {
var newRep, stringRep;
if (format === 'JRS') {
this.imp.file = filename || this.imp.file;
FS.writeFileSync(this.imp.file, this.stringify(), 'utf8');
} else {
newRep = CONVERTER.toFRESH(this);
stringRep = CONVERTER.toSTRING(newRep);
FS.writeFileSync(filename, stringRep, 'utf8');
}
return this;
}
/** Return the resume format. */
/** Return the resume format. */
format() {
return 'JRS';
}
format = function() {
return 'JRS';
};
stringify() {
return JRSResume.stringify(this);
}
JRSResume.prototype.stringify = function() {
return JRSResume.stringify(this);
};
/** Return a unique list of all keywords across all skills. */
keywords() {
var flatSkills;
flatSkills = [];
if (this.skills && this.skills.length) {
this.skills.forEach(function(s) {
return flatSkills = _.union(flatSkills, s.keywords);
});
}
return flatSkills;
}
/**
Return internal metadata. Create if it doesn't exist.
JSON Resume v0.0.0 doesn't allow additional properties at the root level,
so tuck this into the .basic sub-object.
*/
i() {
var ref;
return this.imp = (ref = this.imp) != null ? ref : {};
}
/** Return a unique list of all keywords across all skills. */
/** Add work experience to the sheet. */
add(moniker) {
var defSheet, newObject;
defSheet = JRSResume.default();
newObject = $.extend(true, {}, defSheet[moniker][0]);
this[moniker] = this[moniker] || [];
this[moniker].push(newObject);
return newObject;
}
JRSResume.prototype.keywords = function() {
var flatSkills;
flatSkills = [];
if (this.skills && this.skills.length) {
this.skills.forEach(function(s) {
return flatSkills = _.union(flatSkills, s.keywords);
/** Determine if the sheet includes a specific social profile (eg, GitHub). */
hasProfile(socialNetwork) {
socialNetwork = socialNetwork.trim().toLowerCase();
return this.basics.profiles && _.some(this.basics.profiles, function(p) {
return p.network.trim().toLowerCase() === socialNetwork;
});
}
return flatSkills;
/** Determine if the sheet includes a specific skill. */
hasSkill(skill) {
skill = skill.trim().toLowerCase();
return this.skills && _.some(this.skills, function(sk) {
return sk.keywords && _.some(sk.keywords, function(kw) {
return kw.trim().toLowerCase() === skill;
});
});
}
/** Validate the sheet against the JSON Resume schema. */
isValid() { // TODO: ↓ fix this path ↓
var ret, schema, schemaObj, temp, validate;
schema = FS.readFileSync(PATH.join(__dirname, 'resume.json'), 'utf8');
schemaObj = JSON.parse(schema);
validator = require('is-my-json-valid');
validate = validator(schemaObj, { // Note [1]
formats: {
date: /^\d{4}(?:-(?:0[0-9]{1}|1[0-2]{1})(?:-[0-9]{2})?)?$/
}
});
temp = this.imp;
delete this.imp;
ret = validate(this);
this.imp = temp;
if (!ret) {
this.imp = this.imp || {};
this.imp.validationErrors = validate.errors;
}
return ret;
}
duration(unit) {
var inspector;
inspector = require('../inspectors/duration-inspector');
return inspector.run(this, 'work', 'startDate', 'endDate', unit);
}
/**
Sort dated things on the sheet by start date descending. Assumes that dates
on the sheet have been processed with _parseDates().
*/
sort() {
var byDateDesc;
byDateDesc = function(a, b) {
if (a.safeStartDate.isBefore(b.safeStartDate)) {
return 1;
} else {
return (a.safeStartDate.isAfter(b.safeStartDate) && -1) || 0;
}
};
this.work && this.work.sort(byDateDesc);
this.education && this.education.sort(byDateDesc);
this.volunteer && this.volunteer.sort(byDateDesc);
this.awards && this.awards.sort(function(a, b) {
if (a.safeDate.isBefore(b.safeDate)) {
return 1;
} else {
return (a.safeDate.isAfter(b.safeDate) && -1) || 0;
}
});
return this.publications && this.publications.sort(function(a, b) {
if (a.safeReleaseDate.isBefore(b.safeReleaseDate)) {
return 1;
} else {
return (a.safeReleaseDate.isAfter(b.safeReleaseDate) && -1) || 0;
}
});
}
dupe() {
var rnew;
rnew = new JRSResume();
rnew.parse(this.stringify(), {});
return rnew;
}
/**
Create a copy of this resume in which all fields have been interpreted as
Markdown.
*/
harden() {
var HD, HDIN, ret, transformer;
ret = this.dupe();
HD = function(txt) {
return '@@@@~' + txt + '~@@@@';
};
HDIN = function(txt) {
//return MD(txt || '' ).replace(/^\s*<p>|<\/p>\s*$/gi, '');
return HD(txt);
};
transformer = require('../utils/string-transformer');
return transformer(ret, ['skills', 'url', 'website', 'startDate', 'endDate', 'releaseDate', 'date', 'phone', 'email', 'address', 'postalCode', 'city', 'country', 'region', 'safeStartDate', 'safeEndDate'], function(key, val) {
return HD(val);
});
}
};
/**
Return internal metadata. Create if it doesn't exist.
JSON Resume v0.0.0 doesn't allow additional properties at the root level,
so tuck this into the .basic sub-object.
*/
JRSResume.prototype.i = function() {
var ref;
return this.imp = (ref = this.imp) != null ? ref : {};
};
/** Reset the sheet to an empty state. */
clear = function(clearMeta) {
clearMeta = ((clearMeta === void 0) && true) || clearMeta;
if (clearMeta) {
delete this.imp;
}
delete this.basics.computed;
delete this.basics.computed; // Don't use Object.keys() here
delete this.work;
delete this.volunteer;
delete this.education;
@ -199,183 +280,22 @@ Definition of the JRSResume class.
return delete this.basics.profiles;
};
/** Add work experience to the sheet. */
JRSResume.prototype.add = function(moniker) {
var defSheet, newObject;
defSheet = JRSResume["default"]();
newObject = $.extend(true, {}, defSheet[moniker][0]);
this[moniker] = this[moniker] || [];
this[moniker].push(newObject);
return newObject;
};
/** Determine if the sheet includes a specific social profile (eg, GitHub). */
JRSResume.prototype.hasProfile = function(socialNetwork) {
socialNetwork = socialNetwork.trim().toLowerCase();
return this.basics.profiles && _.some(this.basics.profiles, function(p) {
return p.network.trim().toLowerCase() === socialNetwork;
});
};
/** Determine if the sheet includes a specific skill. */
JRSResume.prototype.hasSkill = function(skill) {
skill = skill.trim().toLowerCase();
return this.skills && _.some(this.skills, function(sk) {
return sk.keywords && _.some(sk.keywords, function(kw) {
return kw.trim().toLowerCase() === skill;
});
});
};
/** Validate the sheet against the JSON Resume schema. */
JRSResume.prototype.isValid = function() {
var ret, schema, schemaObj, temp, validate;
schema = FS.readFileSync(PATH.join(__dirname, 'resume.json'), 'utf8');
schemaObj = JSON.parse(schema);
validator = require('is-my-json-valid');
validate = validator(schemaObj, {
formats: {
date: /^\d{4}(?:-(?:0[0-9]{1}|1[0-2]{1})(?:-[0-9]{2})?)?$/
}
});
temp = this.imp;
delete this.imp;
ret = validate(this);
this.imp = temp;
if (!ret) {
this.imp = this.imp || {};
this.imp.validationErrors = validate.errors;
}
return ret;
};
JRSResume.prototype.duration = function(unit) {
return JRSResume.__super__.duration.call(this, 'work', 'startDate', 'endDate', unit);
};
/**
Sort dated things on the sheet by start date descending. Assumes that dates
on the sheet have been processed with _parseDates().
*/
JRSResume.prototype.sort = function() {
var byDateDesc;
byDateDesc = function(a, b) {
if (a.safeStartDate.isBefore(b.safeStartDate)) {
return 1;
} else {
return (a.safeStartDate.isAfter(b.safeStartDate) && -1) || 0;
}
};
this.work && this.work.sort(byDateDesc);
this.education && this.education.sort(byDateDesc);
this.volunteer && this.volunteer.sort(byDateDesc);
this.awards && this.awards.sort(function(a, b) {
if (a.safeDate.isBefore(b.safeDate)) {
return 1;
} else {
return (a.safeDate.isAfter(b.safeDate) && -1) || 0;
}
});
return this.publications && this.publications.sort(function(a, b) {
if (a.safeReleaseDate.isBefore(b.safeReleaseDate)) {
return 1;
} else {
return (a.safeReleaseDate.isAfter(b.safeReleaseDate) && -1) || 0;
}
});
};
JRSResume.prototype.dupe = function() {
var rnew;
rnew = new JRSResume();
rnew.parse(this.stringify(), {});
return rnew;
};
/**
Create a copy of this resume in which all fields have been interpreted as
Markdown.
*/
JRSResume.prototype.harden = function() {
var HD, HDIN, hardenStringsInObject, ret, that;
that = this;
ret = this.dupe();
HD = function(txt) {
return '@@@@~' + txt + '~@@@@';
};
HDIN = function(txt) {
return HD(txt);
};
hardenStringsInObject = function(obj, inline) {
if (!obj) {
return;
}
inline = inline === void 0 || inline;
if (Object.prototype.toString.call(obj) === '[object Array]') {
return obj.forEach(function(elem, idx, ar) {
if (typeof elem === 'string' || elem instanceof String) {
return ar[idx] = inline ? HDIN(elem) : HD(elem);
} else {
return hardenStringsInObject(elem);
}
});
} else if (typeof obj === 'object') {
return Object.keys(obj).forEach(function(key) {
var sub;
sub = obj[key];
if (typeof sub === 'string' || sub instanceof String) {
if (_.contains(['skills', 'url', 'website', 'startDate', 'endDate', 'releaseDate', 'date', 'phone', 'email', 'address', 'postalCode', 'city', 'country', 'region'], key)) {
return;
}
if (key === 'summary') {
return obj[key] = HD(obj[key]);
} else {
return obj[key] = inline ? HDIN(obj[key]) : HD(obj[key]);
}
} else {
return hardenStringsInObject(sub);
}
});
}
};
Object.keys(ret).forEach(function(member) {
return hardenStringsInObject(ret[member]);
});
return ret;
};
return JRSResume;
})(AbstractResume);
}).call(this);
/** Get the default (empty) sheet. */
JRSResume["default"] = function() {
return new JRSResume().open(PATH.join(__dirname, 'empty-jrs.json'), 'Empty');
JRSResume.default = function() {
return new JRSResume().parseJSON(require('fresh-resume-starter').jrs);
};
/**
Convert this object to a JSON string, sanitizing meta-properties along the
way. Don't override .toString().
*/
*/
JRSResume.stringify = function(obj) {
var replacer;
replacer = function(key, value) {
replacer = function(key, value) { // Exclude these keys from stringification
var temp;
temp = _.some(['imp', 'warnings', 'computed', 'filt', 'ctrl', 'index', 'safeStartDate', 'safeEndDate', 'safeDate', 'safeReleaseDate', 'result', 'isModified', 'htmlPreview', 'display_progress_bar'], function(val) {
return key.trim() === val;
@ -389,15 +309,6 @@ Definition of the JRSResume class.
return JSON.stringify(obj, replacer, 2);
};
/**
Convert human-friendly dates into formal Moment.js dates for all collections.
We don't want to lose the raw textual date as entered by the user, so we store
the Moment-ified date as a separate property with a prefix of .safe. For ex:
job.startDate is the date as entered by the user. job.safeStartDate is the
parsed Moment.js date that we actually use in processing.
*/
_parseDates = function() {
var _fmt;
_fmt = require('./fluent-date').fmt;
@ -421,11 +332,11 @@ Definition of the JRSResume class.
});
};
/**
Export the JRSResume function/ctor.
*/
*/
module.exports = JRSResume;
}).call(this);
//# sourceMappingURL=jrs-resume.js.map

View File

@ -1,12 +1,10 @@
/**
Definition of the JRSTheme class.
@module core/jrs-theme
@license MIT. See LICENSE.MD for details.
*/
(function() {
var JRSTheme, PATH, _, parsePath, pathExists;
/**
Definition of the JRSTheme class.
@module core/jrs-theme
@license MIT. See LICENSE.MD for details.
*/
var JRSTheme, PATH, _, errors, parsePath, pathExists;
_ = require('underscore');
@ -16,32 +14,32 @@ Definition of the JRSTheme class.
pathExists = require('path-exists').sync;
errors = require('./status-codes');
/**
The JRSTheme class is a representation of a JSON Resume theme asset.
@class JRSTheme
*/
JRSTheme = (function() {
function JRSTheme() {}
*/
JRSTheme = class JRSTheme {
/**
Open and parse the specified theme.
Open and parse the specified JRS theme.
@method open
*/
JRSTheme.prototype.open = function(thFolder) {
*/
open(thFolder) {
var pathInfo, pkgJsonPath, thApi, thPkg;
this.folder = thFolder;
pathInfo = parsePath(thFolder);
// Open and parse the theme's package.json file
pkgJsonPath = PATH.join(thFolder, 'package.json');
if (pathExists(pkgJsonPath)) {
thApi = require(thFolder);
thPkg = require(pkgJsonPath);
thApi = require(thFolder); // Requiring the folder yields whatever the package.json's "main" is set to
thPkg = require(pkgJsonPath); // Get the package.json as JSON
this.name = thPkg.name;
this.render = (thApi && thApi.render) || void 0;
this.engine = 'jrs';
// Create theme formats (HTML and PDF). Just add the bare minimum mix of
// properties necessary to allow JSON Resume themes to share a rendering
// path with FRESH themes.
this.formats = {
html: {
outFormat: 'html',
@ -49,7 +47,7 @@ Definition of the JRSTheme class.
{
action: 'transform',
render: this.render,
major: true,
primary: true,
ext: 'html',
css: null
}
@ -61,7 +59,7 @@ Definition of the JRSTheme class.
{
action: 'transform',
render: this.render,
major: true,
primary: true,
ext: 'pdf',
css: null
}
@ -70,36 +68,32 @@ Definition of the JRSTheme class.
};
} else {
throw {
fluenterror: HACKMYSTATUS.missingPackageJSON
fluenterror: errors.missingPackageJSON
};
}
return this;
};
}
/**
Determine if the theme supports the output format.
@method hasFormat
*/
JRSTheme.prototype.hasFormat = function(fmt) {
*/
hasFormat(fmt) {
return _.has(this.formats, fmt);
};
}
/**
Return the requested output format.
@method getFormat
*/
JRSTheme.prototype.getFormat = function(fmt) {
*/
getFormat(fmt) {
return this.formats[fmt];
};
}
return JRSTheme;
})();
};
module.exports = JRSTheme;
}).call(this);
//# sourceMappingURL=jrs-theme.js.map

View File

@ -1,16 +1,18 @@
/**
Definition of the ResumeFactory class.
@license MIT. See LICENSE.md for details.
@module core/resume-factory
*/
(function() {
var FS, HACKMYSTATUS, HME, ResumeConverter, ResumeFactory, SyntaxErrorEx, _, _parse, chalk;
/**
Definition of the ResumeFactory class.
@license MIT. See LICENSE.md for details.
@module core/resume-factory
*/
/**
A simple factory class for FRESH and JSON Resumes.
@class ResumeFactory
*/
var FS, HME, HMS, ResumeConverter, ResumeFactory, SyntaxErrorEx, _, _parse, chalk, resumeDetect;
FS = require('fs');
HACKMYSTATUS = require('./status-codes');
HMS = require('./status-codes');
HME = require('./event-codes');
@ -22,22 +24,17 @@ Definition of the ResumeFactory class.
_ = require('underscore');
resumeDetect = require('../utils/resume-detector');
require('string.prototype.startswith');
/**
A simple factory class for FRESH and JSON Resumes.
@class ResumeFactory
*/
ResumeFactory = module.exports = {
/**
Load one or more resumes from disk.
@param {Object} opts An options object with settings for the factory as well
as passthrough settings for FRESHResume or JRSResume. Structure:
{
format: 'FRESH', // Format to open as. ('FRESH', 'JRS', null)
objectify: true, // FRESH/JRSResume or raw JSON?
@ -45,32 +42,42 @@ Definition of the ResumeFactory class.
sort: false
}
}
*/
*/
load: function(sources, opts, emitter) {
return sources.map(function(src) {
return this.loadOne(src, opts, emitter);
}, this);
},
/** Load a single resume from disk. */
/** Load a single resume from disk. */
loadOne: function(src, opts, emitter) {
var ResumeClass, info, isFRESH, json, objectify, orgFormat, rez, toFormat;
toFormat = opts.format;
objectify = opts.objectify;
var ResumeClass, info, json, orgFormat, reqLib, rez, toFormat;
toFormat = opts.format; // Can be null
// Get the destination format. Can be 'fresh', 'jrs', or null/undefined.
toFormat && (toFormat = toFormat.toLowerCase().trim());
// Load and parse the resume JSON
info = _parse(src, opts, emitter);
if (info.fluenterror) {
return info;
}
// Determine the resume format: FRESH or JRS
json = info.json;
isFRESH = json.meta && json.meta.format && json.meta.format.startsWith('FRESH@');
orgFormat = isFRESH ? 'fresh' : 'jrs';
orgFormat = resumeDetect(json);
if (orgFormat === 'unk') {
info.fluenterror = HMS.unknownSchema;
return info;
}
// Convert between formats if necessary
if (toFormat && (orgFormat !== toFormat)) {
json = ResumeConverter['to' + toFormat.toUpperCase()](json);
}
// Objectify the resume, that is, convert it from JSON to a FRESHResume
// or JRSResume object.
rez = null;
if (objectify) {
ResumeClass = require('../core/' + (toFormat || orgFormat) + '-resume');
if (opts.objectify) {
reqLib = '../core/' + (toFormat || orgFormat) + '-resume';
ResumeClass = require(reqLib);
rez = new ResumeClass().parseJSON(json, opts.inner);
rez.i().file = src;
}
@ -83,9 +90,10 @@ Definition of the ResumeFactory class.
};
_parse = function(fileName, opts, eve) {
var ex, orgFormat, rawData, ret;
var err, orgFormat, rawData, ret;
rawData = null;
try {
// Read the file
eve && eve.stat(HME.beforeRead, {
file: fileName
});
@ -107,21 +115,18 @@ Definition of the ResumeFactory class.
fmt: orgFormat
});
return ret;
} catch (_error) {
ex = {
fluenterror: rawData ? HACKMYSTATUS.parseError : HACKMYSTATUS.readError,
inner: _error,
} catch (error) {
err = error;
return {
// Can be ENOENT, EACCES, SyntaxError, etc.
fluenterror: rawData ? HMS.parseError : HMS.readError,
inner: err,
raw: rawData,
file: fileName,
shouldExit: false
file: fileName
};
opts.quit && (ex.quit = true);
eve && eve.err(ex.fluenterror, ex);
if (opts["throw"]) {
throw ex;
}
return ex;
}
};
}).call(this);
//# sourceMappingURL=resume-factory.js.map

View File

@ -1,11 +1,9 @@
/**
Status codes for HackMyResume.
@module core/status-codes
@license MIT. See LICENSE.MD for details.
*/
(function() {
/**
Status codes for HackMyResume.
@module core/status-codes
@license MIT. See LICENSE.MD for details.
*/
module.exports = {
success: 0,
themeNotFound: 1,
@ -16,7 +14,7 @@ Status codes for HackMyResume.
resumeNotFoundAlt: 6,
inputOutputParity: 7,
createNameMissing: 8,
pdfgeneration: 9,
pdfGeneration: 9,
missingPackageJSON: 10,
invalid: 11,
invalidFormat: 12,
@ -31,7 +29,16 @@ Status codes for HackMyResume.
compileTemplate: 21,
themeLoad: 22,
invalidParamCount: 23,
missingParam: 24
missingParam: 24,
createError: 25,
validateError: 26,
invalidOptionsFile: 27,
optionsFileNotFound: 28,
unknownSchema: 29,
themeHelperLoad: 30,
invalidSchemaVersion: 31
};
}).call(this);
//# sourceMappingURL=status-codes.js.map

View File

@ -1,33 +1,34 @@
/**
Definition of the BaseGenerator class.
@module base-generator.js
@license MIT. See LICENSE.md for details.
*/
(function() {
var BaseGenerator, Class;
Class = require('../utils/class');
/**
Definition of the BaseGenerator class.
@module generators/base-generator
@license MIT. See LICENSE.md for details.
*/
var BaseGenerator;
/**
The BaseGenerator class is the root of the generator hierarchy. Functionality
common to ALL generators lives here.
*/
*/
module.exports = BaseGenerator = (function() {
class BaseGenerator {
/** Base-class initialize. */
constructor(format) {
this.format = format;
}
BaseGenerator = module.exports = Class.extend({
/** Base-class initialize. */
init: function(outputFormat) {
return this.format = outputFormat;
},
};
/** Status codes. */
codes: require('../core/status-codes'),
BaseGenerator.prototype.codes = require('../core/status-codes');
/** Generator options. */
opts: {}
});
BaseGenerator.prototype.opts = {};
return BaseGenerator;
}).call(this);
}).call(this);
//# sourceMappingURL=base-generator.js.map

View File

@ -1,11 +1,9 @@
/**
Definition of the HTMLGenerator class.
@license MIT. See LICENSE.md for details.
@module html-generator.js
*/
(function() {
/**
Definition of the HTMLGenerator class.
@module generators/html-generator
@license MIT. See LICENSE.md for details.
*/
var FS, HTML, HtmlGenerator, PATH, TemplateGenerator;
TemplateGenerator = require('./template-generator');
@ -18,16 +16,16 @@ Definition of the HTMLGenerator class.
require('string.prototype.endswith');
HtmlGenerator = module.exports = TemplateGenerator.extend({
init: function() {
return this._super('html');
},
module.exports = HtmlGenerator = class HtmlGenerator extends TemplateGenerator {
constructor() {
super('html');
}
/**
Copy satellite CSS files to the destination and optionally pretty-print
the HTML resume prior to saving.
*/
onBeforeSave: function(info) {
*/
onBeforeSave(info) {
if (info.outputFile.endsWith('.css')) {
return info.mk;
}
@ -37,6 +35,9 @@ Definition of the HTMLGenerator class.
return info.mk;
}
}
});
};
}).call(this);
//# sourceMappingURL=html-generator.js.map

View File

@ -1,98 +1,126 @@
/**
Definition of the HtmlPdfCLIGenerator class.
@module html-pdf-generator.js
@license MIT. See LICENSE.md for details.
*/
(function() {
var FS, HTML, HtmlPdfCLIGenerator, PATH, SLASH, SPAWN, TemplateGenerator, engines;
/**
Definition of the HtmlPdfCLIGenerator class.
@module generators/html-pdf-generator.js
@license MIT. See LICENSE.md for details.
*/
var FS, HMSTATUS, HtmlPdfCLIGenerator, PATH, SLASH, SPAWN, TemplateGenerator, _, engines;
TemplateGenerator = require('./template-generator');
FS = require('fs-extra');
HTML = require('html');
PATH = require('path');
SPAWN = require('../utils/safe-spawn');
SLASH = require('slash');
_ = require('underscore');
HMSTATUS = require('../core/status-codes');
SPAWN = require('../utils/safe-spawn');
/**
An HTML-driven PDF resume generator for HackMyResume. Talks to Phantom,
wkhtmltopdf, and other PDF engines over a CLI (command-line interface).
If an engine isn't installed for a particular platform, error out gracefully.
*/
HtmlPdfCLIGenerator = module.exports = TemplateGenerator.extend({
init: function() {
return this._super('pdf', 'html');
},
*/
module.exports = HtmlPdfCLIGenerator = class HtmlPdfCLIGenerator extends TemplateGenerator {
constructor() {
super('pdf', 'html');
}
/** Generate the binary PDF. */
onBeforeSave: function(info) {
var ex, safe_eng;
try {
safe_eng = info.opts.pdf || 'wkhtmltopdf';
if (safe_eng !== 'none') {
engines[safe_eng].call(this, info.mk, info.outputFile);
return null;
}
} catch (_error) {
ex = _error;
if (ex.inner && ex.inner.code === 'ENOENT') {
throw {
fluenterror: this.codes.notOnPath,
inner: ex.inner,
engine: ex.cmd,
stack: ex.inner && ex.inner.stack
};
} else {
throw {
fluenterror: this.codes.pdfGeneration,
inner: ex,
stack: ex.stack
};
onBeforeSave(info) {
var safe_eng;
if (info.ext !== 'html' && info.ext !== 'pdf') {
//console.dir _.omit( info, 'mk' ), depth: null, colors: true
return info.mk;
}
safe_eng = info.opts.pdf || 'wkhtmltopdf';
if (safe_eng === 'phantom') {
safe_eng = 'phantomjs';
}
if (_.has(engines, safe_eng)) {
this.errHandler = info.opts.errHandler;
engines[safe_eng].call(this, info.mk, info.outputFile, info.opts, this.onError);
return null; // halt further processing
}
}
/* Low-level error callback for spawn(). May be called after HMR process
termination, so object references may not be valid here. That's okay; if
the references are invalid, the error was already logged. We could use
spawn-watch here but that causes issues on legacy Node.js. */
onError(ex, param) {
var ref;
if ((ref = param.errHandler) != null) {
if (typeof ref.err === "function") {
ref.err(HMSTATUS.pdfGeneration, ex);
}
}
}
});
};
// TODO: Move each engine to a separate module
engines = {
/**
Generate a PDF from HTML using wkhtmltopdf's CLI interface.
Spawns a child process with `wkhtmltopdf <source> <target>`. wkhtmltopdf
must be installed and path-accessible.
TODO: If HTML generation has run, reuse that output
TODO: Local web server to ease wkhtmltopdf rendering
*/
wkhtmltopdf: function(markup, fOut) {
var info, tempFile;
*/
wkhtmltopdf: function(markup, fOut, opts, on_error) {
var tempFile, wkargs, wkopts;
// Save the markup to a temporary file
tempFile = fOut.replace(/\.pdf$/i, '.pdf.html');
FS.writeFileSync(tempFile, markup, 'utf8');
return info = SPAWN('wkhtmltopdf', [tempFile, fOut]);
// Prepare wkhtmltopdf arguments.
wkopts = _.extend({
'margin-top': '10mm',
'margin-bottom': '10mm'
}, opts.wkhtmltopdf);
wkopts = _.flatten(_.map(wkopts, function(v, k) {
return ['--' + k, v];
}));
wkargs = wkopts.concat([tempFile, fOut]);
SPAWN('wkhtmltopdf', wkargs, false, on_error, this);
},
/**
Generate a PDF from HTML using Phantom's CLI interface.
Spawns a child process with `phantomjs <script> <source> <target>`. Phantom
must be installed and path-accessible.
TODO: If HTML generation has run, reuse that output
TODO: Local web server to ease Phantom rendering
*/
phantom: function(markup, fOut) {
var destPath, info, scriptPath, sourcePath, tempFile;
*/
phantomjs: function(markup, fOut, opts, on_error) {
var destPath, scriptPath, sourcePath, tempFile;
// Save the markup to a temporary file
tempFile = fOut.replace(/\.pdf$/i, '.pdf.html');
FS.writeFileSync(tempFile, markup, 'utf8');
scriptPath = SLASH(PATH.relative(process.cwd(), PATH.resolve(__dirname, '../utils/rasterize.js')));
scriptPath = PATH.relative(process.cwd(), PATH.resolve(__dirname, '../utils/rasterize.js'));
scriptPath = SLASH(scriptPath);
sourcePath = SLASH(PATH.relative(process.cwd(), tempFile));
destPath = SLASH(PATH.relative(process.cwd(), fOut));
return info = SPAWN('phantomjs', [scriptPath, sourcePath, destPath]);
SPAWN('phantomjs', [scriptPath, sourcePath, destPath], false, on_error, this);
},
/**
Generate a PDF from HTML using WeasyPrint's CLI interface.
Spawns a child process with `weasyprint <source> <target>`. Weasy Print
must be installed and path-accessible.
TODO: If HTML generation has run, reuse that output
*/
weasyprint: function(markup, fOut, opts, on_error) {
var tempFile;
// Save the markup to a temporary file
tempFile = fOut.replace(/\.pdf$/i, '.pdf.html');
FS.writeFileSync(tempFile, markup, 'utf8');
SPAWN('weasyprint', [tempFile, fOut], false, on_error, this);
}
};
}).call(this);
//# sourceMappingURL=html-pdf-cli-generator.js.map

View File

@ -1,11 +1,16 @@
/**
Definition of the HtmlPngGenerator class.
@license MIT. See LICENSE.MD for details.
@module html-png-generator.js
*/
(function() {
/**
Definition of the HtmlPngGenerator class.
@module generators/html-png-generator
@license MIT. See LICENSE.MD for details.
*/
/**
Generate a PDF from HTML using Phantom's CLI interface.
Spawns a child process with `phantomjs <script> <source> <target>`. Phantom
must be installed and path-accessible.
TODO: If HTML generation has run, reuse that output
TODO: Local web server to ease Phantom rendering
*/
var FS, HTML, HtmlPngGenerator, PATH, SLASH, SPAWN, TemplateGenerator, phantom;
TemplateGenerator = require('./template-generator');
@ -20,17 +25,18 @@ Definition of the HtmlPngGenerator class.
PATH = require('path');
/**
An HTML-based PNG resume generator for HackMyResume.
*/
*/
module.exports = HtmlPngGenerator = class HtmlPngGenerator extends TemplateGenerator {
constructor() {
super('png', 'html');
}
HtmlPngGenerator = module.exports = TemplateGenerator.extend({
init: function() {
return this._super('png', 'html');
},
invoke: function(rez, themeMarkup, cssInfo, opts) {},
generate: function(rez, f, opts) {
invoke(rez, themeMarkup, cssInfo, opts) {}
// TODO: Not currently called or callable.
generate(rez, f, opts) {
var htmlFile, htmlResults;
htmlResults = opts.targets.filter(function(t) {
return t.fmt.outFormat === 'html';
@ -40,19 +46,12 @@ Definition of the HtmlPngGenerator class.
});
phantom(htmlFile[0].data, f);
}
});
/**
Generate a PDF from HTML using Phantom's CLI interface.
Spawns a child process with `phantomjs <script> <source> <target>`. Phantom
must be installed and path-accessible.
TODO: If HTML generation has run, reuse that output
TODO: Local web server to ease Phantom rendering
*/
};
phantom = function(markup, fOut) {
var destPath, info, scriptPath, sourcePath, tempFile;
// Save the markup to a temporary file
tempFile = fOut.replace(/\.png$/i, '.png.html');
FS.writeFileSync(tempFile, markup, 'utf8');
scriptPath = SLASH(PATH.relative(process.cwd(), PATH.resolve(__dirname, '../utils/rasterize.js')));
@ -62,3 +61,5 @@ Definition of the HtmlPngGenerator class.
};
}).call(this);
//# sourceMappingURL=html-png-generator.js.map

View File

@ -1,12 +1,10 @@
/**
Definition of the JsonGenerator class.
@license MIT. See LICENSE.md for details.
@module generators/json-generator
*/
(function() {
var BaseGenerator, FS, JsonGenerator, _;
/**
Definition of the JsonGenerator class.
@module generators/json-generator
@license MIT. See LICENSE.md for details.
*/
var BaseGenerator, FJCV, FS, JsonGenerator, _;
BaseGenerator = require('./base-generator');
@ -14,32 +12,27 @@ Definition of the JsonGenerator class.
_ = require('underscore');
FJCV = require('fresh-jrs-converter');
/**
The JsonGenerator generates a JSON resume directly.
*/
/** The JsonGenerator generates a FRESH or JRS resume as an output. */
module.exports = JsonGenerator = class JsonGenerator extends BaseGenerator {
constructor() {
super('json');
}
JsonGenerator = module.exports = BaseGenerator.extend({
init: function() {
return this._super('json');
},
keys: ['imp', 'warnings', 'computed', 'filt', 'ctrl', 'index', 'safeStartDate', 'safeEndDate', 'safeDate', 'safeReleaseDate', 'result', 'isModified', 'htmlPreview', 'safe'],
invoke: function(rez) {
var replacer;
replacer = function(key, value) {
if (_.some(this.keys, function(val) {
return key.trim() === val;
})) {
return void 0;
} else {
return value;
}
};
return JSON.stringify(rez, replacer, 2);
},
generate: function(rez, f) {
invoke(rez) {
var altRez;
altRez = FJCV['to' + (rez.format() === 'FRESH' ? 'JRS' : 'FRESH')](rez);
return altRez = FJCV.toSTRING(altRez);
}
//altRez.stringify()
generate(rez, f) {
FS.writeFileSync(f, this.invoke(rez), 'utf8');
}
});
};
}).call(this);
//# sourceMappingURL=json-generator.js.map

View File

@ -1,11 +1,9 @@
/**
Definition of the JsonYamlGenerator class.
@module json-yaml-generator.js
@license MIT. See LICENSE.md for details.
*/
(function() {
/**
Definition of the JsonYamlGenerator class.
@module generators/json-yaml-generator
@license MIT. See LICENSE.md for details.
*/
var BaseGenerator, FS, JsonYamlGenerator, YAML;
BaseGenerator = require('./base-generator');
@ -14,25 +12,29 @@ Definition of the JsonYamlGenerator class.
YAML = require('yamljs');
/**
JsonYamlGenerator takes a JSON resume object and translates it directly to
JSON without a template, producing an equivalent YAML-formatted resume. See
also YamlGenerator (yaml-generator.js).
*/
JsonYamlGenerator = module.exports = BaseGenerator.extend({
init: function() {
return this._super('yml');
},
invoke: function(rez, themeMarkup, cssInfo, opts) {
return YAML.stringify(JSON.parse(rez.stringify()), Infinity, 2);
},
generate: function(rez, f, opts) {
var data;
data = YAML.stringify(JSON.parse(rez.stringify()), Infinity, 2);
return FS.writeFileSync(f, data, 'utf8');
*/
module.exports = JsonYamlGenerator = class JsonYamlGenerator extends BaseGenerator {
constructor() {
super('yml');
}
});
invoke(rez, themeMarkup, cssInfo, opts) {
return YAML.stringify(JSON.parse(rez.stringify()), 2e308, 2);
}
generate(rez, f, opts) {
var data;
data = YAML.stringify(JSON.parse(rez.stringify()), 2e308, 2);
FS.writeFileSync(f, data, 'utf8');
return data;
}
};
}).call(this);
//# sourceMappingURL=json-yaml-generator.js.map

View File

@ -1,24 +1,23 @@
/**
Definition of the LaTeXGenerator class.
@license MIT. See LICENSE.md for details.
@module generators/latex-generator
*/
(function() {
/**
Definition of the LaTeXGenerator class.
@module generators/latex-generator
@license MIT. See LICENSE.md for details.
*/
var LaTeXGenerator, TemplateGenerator;
TemplateGenerator = require('./template-generator');
/**
LaTeXGenerator generates a LaTeX resume via TemplateGenerator.
*/
LaTeXGenerator = module.exports = TemplateGenerator.extend({
init: function() {
return this._super('latex', 'tex');
*/
module.exports = LaTeXGenerator = class LaTeXGenerator extends TemplateGenerator {
constructor() {
super('latex', 'tex');
}
});
};
}).call(this);
//# sourceMappingURL=latex-generator.js.map

View File

@ -1,24 +1,23 @@
/**
Definition of the MarkdownGenerator class.
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
@module markdown-generator.js
*/
(function() {
/**
Definition of the MarkdownGenerator class.
@module generators/markdown-generator
@license MIT. See LICENSE.md for details.
*/
var MarkdownGenerator, TemplateGenerator;
TemplateGenerator = require('./template-generator');
/**
MarkdownGenerator generates a Markdown-formatted resume via TemplateGenerator.
*/
MarkdownGenerator = module.exports = TemplateGenerator.extend({
init: function() {
return this._super('md', 'txt');
*/
module.exports = MarkdownGenerator = class MarkdownGenerator extends TemplateGenerator {
constructor() {
super('md', 'txt');
}
});
};
}).call(this);
//# sourceMappingURL=markdown-generator.js.map

View File

@ -1,12 +1,13 @@
/**
Definition of the TemplateGenerator class. TODO: Refactor
@license MIT. See LICENSE.md for details.
@module template-generator.js
*/
(function() {
var BaseGenerator, EXTEND, FRESHTheme, FS, JRSTheme, MD, MKDIRP, PATH, TemplateGenerator, XML, _, _defaultOpts, _reg, freeze, parsePath, unfreeze;
/**
Definition of the TemplateGenerator class. TODO: Refactor
@module generators/template-generator
@license MIT. See LICENSE.md for details.
*/
/** Default template generator options. */
/** Freeze newlines for protection against errant JST parsers. */
/** Unfreeze newlines when the coast is clear. */
var BaseGenerator, EXTEND, FRESHTheme, FS, JRSTheme, MD, MKDIRP, PATH, TemplateGenerator, XML, _, _defaultOpts, _reg, createSymLinks, freeze, parsePath, unfreeze;
FS = require('fs-extra');
@ -30,24 +31,21 @@ Definition of the TemplateGenerator class. TODO: Refactor
JRSTheme = require('../core/jrs-theme');
/**
TemplateGenerator performs resume generation via local Handlebar or Underscore
style template expansion and is appropriate for text-based formats like HTML,
plain text, and XML versions of Microsoft Word, Excel, and OpenOffice.
@class TemplateGenerator
*/
TemplateGenerator = module.exports = BaseGenerator.extend({
*/
module.exports = TemplateGenerator = class TemplateGenerator extends BaseGenerator {
/** Constructor. Set the output format and template format for this
generator. Will usually be called by a derived generator such as
HTMLGenerator or MarkdownGenerator.
*/
init: function(outputFormat, templateFormat, cssFile) {
this._super(outputFormat);
HTMLGenerator or MarkdownGenerator. */
constructor(outputFormat, templateFormat, cssFile) {
super(outputFormat);
this.tplFormat = templateFormat || outputFormat;
},
return;
}
/** Generate a resume using string-based inputs and outputs without touching
the filesystem.
@ -55,22 +53,30 @@ Definition of the TemplateGenerator class. TODO: Refactor
@param rez A FreshResume object.
@param opts Generator options.
@returns {Array} An array of objects representing the generated output
files.
*/
invoke: function(rez, opts) {
files. */
invoke(rez, opts) {
var curFmt, results;
opts = opts ? (this.opts = EXTEND(true, {}, _defaultOpts, opts)) : this.opts;
// Sort such that CSS files are processed before others
curFmt = opts.themeObj.getFormat(this.format);
curFmt.files = _.sortBy(curFmt.files, function(fi) {
return fi.ext !== 'css';
});
// Run the transformation!
results = curFmt.files.map(function(tplInfo, idx) {
var trx;
trx = this.single(rez, tplInfo.data, this.format, opts, opts.themeObj, curFmt);
if (tplInfo.ext === 'css') {
curFmt.files[idx].data = trx;
if (tplInfo.action === 'transform') {
trx = this.transform(rez, tplInfo.data, this.format, opts, opts.themeObj, curFmt);
if (tplInfo.ext === 'css') {
curFmt.files[idx].data = trx;
} else {
tplInfo.ext === 'html';
}
} else {
tplInfo.ext === 'html';
}
if (typeof opts.onTransform === "function") {
opts.onTransform(tplInfo);
}
return {
info: tplInfo,
@ -80,42 +86,64 @@ Definition of the TemplateGenerator class. TODO: Refactor
return {
files: results
};
},
}
/** Generate a resume using file-based inputs and outputs. Requires access
to the local filesystem.
@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) {
@param opts Generator options. */
generate(rez, f, opts) {
var curFmt, genInfo, outFolder;
// Prepare
this.opts = EXTEND(true, {}, _defaultOpts, opts);
// Call the string-based generation method
genInfo = this.invoke(rez, null);
outFolder = parsePath(f).dirname;
curFmt = opts.themeObj.getFormat(this.format);
// Process individual files within this format. For example, the HTML
// output format for a theme may have multiple HTML files, CSS files,
// etc. Process them here.
genInfo.files.forEach(function(file) {
var fileName, thisFilePath;
var thisFilePath;
// console.dir _.omit(file.info,'cssData','data','css' )
// Pre-processing
file.info.orgPath = file.info.orgPath || '';
thisFilePath = PATH.join(outFolder, file.info.orgPath);
if (this.onBeforeSave) {
thisFilePath = file.info.primary ? f : PATH.join(outFolder, file.info.orgPath);
if (file.info.action !== 'copy' && this.onBeforeSave) {
file.data = this.onBeforeSave({
theme: opts.themeObj,
outputFile: file.info.major ? f : thisFilePath,
outputFile: thisFilePath,
mk: file.data,
opts: this.opts
opts: this.opts,
ext: file.info.ext
});
if (!file.data) {
return;
}
}
fileName = file.info.major ? f : thisFilePath;
MKDIRP.sync(PATH.dirname(fileName));
FS.writeFileSync(fileName, file.data, {
encoding: 'utf8',
flags: 'w'
});
if (typeof opts.beforeWrite === "function") {
opts.beforeWrite({
data: thisFilePath
});
}
MKDIRP.sync(PATH.dirname(thisFilePath));
if (file.info.action !== 'copy') {
FS.writeFileSync(thisFilePath, file.data, {
encoding: 'utf8',
flags: 'w'
});
} else {
FS.copySync(file.info.path, thisFilePath);
}
if (typeof opts.afterWrite === "function") {
opts.afterWrite({
data: thisFilePath
});
}
// Post-processing
if (this.onAfterSave) {
return this.onAfterSave({
outputFile: fileName,
@ -124,19 +152,10 @@ Definition of the TemplateGenerator class. TODO: Refactor
});
}
}, this);
if (curFmt.symLinks) {
Object.keys(curFmt.symLinks).forEach(function(loc) {
var absLoc, absTarg, ref, type;
absLoc = PATH.join(outFolder, loc);
absTarg = PATH.join(PATH.dirname(absLoc), curFmt.symLinks[loc]);
type = (ref = parsePath(absLoc).extname) != null ? ref : {
'file': 'junction'
};
return FS.symlinkSync(absTarg, absLoc, type);
});
}
// Some themes require a symlink structure. If so, create it.
createSymLinks(curFmt, outFolder);
return genInfo;
},
}
/** Perform a single resume resume transformation using string-based inputs
and outputs without touching the local file system.
@ -144,9 +163,8 @@ Definition of the TemplateGenerator class. TODO: Refactor
@param jst The stringified template data
@param format The format name, such as "html" or "latex"
@param cssInfo Needs to be refactored.
@param opts Options and passthrough data.
*/
single: function(json, jst, format, opts, theme, curFmt) {
@param opts Options and passthrough data. */
transform(json, jst, format, opts, theme, curFmt) {
var eng, result;
if (this.opts.freezeBreaks) {
jst = freeze(jst);
@ -158,38 +176,54 @@ Definition of the TemplateGenerator class. TODO: Refactor
}
return result;
}
});
};
/** Export the TemplateGenerator function/ctor. */
module.exports = TemplateGenerator;
/** Freeze newlines for protection against errant JST parsers. */
createSymLinks = function(curFmt, outFolder) {
// Some themes require a symlink structure. If so, create it.
if (curFmt.symLinks) {
Object.keys(curFmt.symLinks).forEach(function(loc) {
var absLoc, absTarg, err, succeeded, type;
absLoc = PATH.join(outFolder, loc);
absTarg = PATH.join(PATH.dirname(absLoc), curFmt.symLinks[loc]);
// Set type to 'file', 'dir', or 'junction' (Windows only)
type = parsePath(absLoc).extname ? 'file' : 'junction';
try {
return FS.symlinkSync(absTarg, absLoc, type);
} catch (error) {
err = error;
succeeded = false;
if (err.code === 'EEXIST') {
FS.unlinkSync(absLoc);
try {
FS.symlinkSync(absTarg, absLoc, type);
succeeded = true;
} catch (error) {}
}
if (!succeeded) {
throw ex;
}
}
});
}
};
freeze = function(markup) {
markup.replace(_reg.regN, _defaultOpts.nSym);
return markup.replace(_reg.regR, _defaultOpts.rSym);
};
/** Unfreeze newlines when the coast is clear. */
unfreeze = function(markup) {
markup.replace(_reg.regSymR, '\r');
return markup.replace(_reg.regSymN, '\n');
};
/** Default template generator options. */
_defaultOpts = {
engine: 'underscore',
keepBreaks: true,
freezeBreaks: false,
nSym: '&newl;',
rSym: '&retn;',
nSym: '&newl;', // newline entity
rSym: '&retn;', // return entity
template: {
interpolate: /\{\{(.+?)\}\}/g,
escape: /\{\{\=(.+?)\}\}/g,
@ -226,13 +260,12 @@ Definition of the TemplateGenerator class. TODO: Refactor
prettify: {
indent_size: 2,
unformatted: ['em', 'strong', 'a'],
max_char: 80
max_char: 80 // ← See lib/html.js in above-linked repo
}
};
//wrap_line_length: 120, <-- Don't use this
/** Regexes for linebreak preservation. */
_reg = {
regN: new RegExp('\n', 'g'),
regR: new RegExp('\r', 'g'),
@ -241,3 +274,5 @@ Definition of the TemplateGenerator class. TODO: Refactor
};
}).call(this);
//# sourceMappingURL=template-generator.js.map

View File

@ -1,24 +1,23 @@
/**
Definition of the TextGenerator class.
@license MIT. See LICENSE.md for details.
@module text-generator.js
*/
(function() {
/**
Definition of the TextGenerator class.
@module generators/text-generator
@license MIT. See LICENSE.md for details.
*/
var TemplateGenerator, TextGenerator;
TemplateGenerator = require('./template-generator');
/**
The TextGenerator generates a plain-text resume via the TemplateGenerator.
*/
TextGenerator = module.exports = TemplateGenerator.extend({
init: function() {
return this._super('txt');
*/
module.exports = TextGenerator = class TextGenerator extends TemplateGenerator {
constructor() {
super('txt');
}
});
};
}).call(this);
//# sourceMappingURL=text-generator.js.map

View File

@ -1,19 +1,20 @@
/*
Definition of the WordGenerator class.
@license MIT. See LICENSE.md for details.
@module generators/word-generator
*/
(function() {
/*
Definition of the WordGenerator class.
@module generators/word-generator
@license MIT. See LICENSE.md for details.
*/
var TemplateGenerator, WordGenerator;
TemplateGenerator = require('./template-generator');
WordGenerator = module.exports = TemplateGenerator.extend({
init: function() {
return this._super('doc', 'xml');
module.exports = WordGenerator = class WordGenerator extends TemplateGenerator {
constructor() {
super('doc', 'xml');
}
});
};
}).call(this);
//# sourceMappingURL=word-generator.js.map

View File

@ -1,24 +1,21 @@
/**
Definition of the XMLGenerator class.
@license MIT. See LICENSE.md for details.
@module generatprs/xml-generator
*/
(function() {
/**
Definition of the XMLGenerator class.
@license MIT. See LICENSE.md for details.
@module generatprs/xml-generator
*/
var BaseGenerator, XMLGenerator;
BaseGenerator = require('./base-generator');
/**
The XmlGenerator generates an XML resume via the TemplateGenerator.
*/
XMLGenerator = module.exports = BaseGenerator.extend({
init: function() {
return this._super('xml');
/** The XmlGenerator generates an XML resume via the TemplateGenerator. */
module.exports = XMLGenerator = class XMLGenerator extends BaseGenerator {
constructor() {
super('xml');
}
});
};
}).call(this);
//# sourceMappingURL=xml-generator.js.map

View File

@ -1,24 +1,23 @@
/**
Definition of the YAMLGenerator class.
@module yaml-generator.js
@license MIT. See LICENSE.md for details.
*/
(function() {
/**
Definition of the YAMLGenerator class.
@module yaml-generator.js
@license MIT. See LICENSE.md for details.
*/
var TemplateGenerator, YAMLGenerator;
TemplateGenerator = require('./template-generator');
/**
YamlGenerator generates a YAML-formatted resume via TemplateGenerator.
*/
YAMLGenerator = module.exports = TemplateGenerator.extend({
init: function() {
return this._super('yml', 'yml');
*/
module.exports = YAMLGenerator = class YAMLGenerator extends TemplateGenerator {
constructor() {
super('yml', 'yml');
}
});
};
}).call(this);
//# sourceMappingURL=yaml-generator.js.map

74
dist/helpers/block-helpers.js vendored Normal file
View File

@ -0,0 +1,74 @@
(function() {
/**
Block helper definitions for HackMyResume / FluentCV.
@license MIT. See LICENSE.md for details.
@module helpers/generic-helpers
*/
/** Block helper function definitions. */
var BlockHelpers, HMSTATUS, LO, _, unused;
HMSTATUS = require('../core/status-codes');
LO = require('lodash');
_ = require('underscore');
unused = require('../utils/string');
BlockHelpers = module.exports = {
/**
Emit the enclosed content if the resume has a section with
the specified name. Otherwise, emit an empty string ''.
*/
section: function(title, options) {
var obj, ret;
title = title.trim().toLowerCase();
obj = LO.get(this.r, title);
ret = '';
if (obj) {
if (_.isArray(obj)) {
if (obj.length) {
ret = options.fn(this);
}
} else if (_.isObject(obj)) {
if ((obj.history && obj.history.length) || (obj.sets && obj.sets.length)) {
ret = options.fn(this);
}
}
}
return ret;
},
ifHasSkill: function(rez, skill, options) {
var ret, skUp;
skUp = skill.toUpperCase();
ret = _.some(rez.skills.list, function(sk) {
return (skUp.toUpperCase() === sk.name.toUpperCase()) && sk.years;
}, this);
if (ret) {
return options.fn(this);
}
},
/**
Emit the enclosed content if the resume has the named
property or subproperty.
*/
has: function(title, options) {
title = title && title.trim().toLowerCase();
if (LO.get(this.r, title)) {
return options.fn(this);
}
},
/**
Return true if either value is truthy.
@method either
*/
either: function(lhs, rhs, options) {
if (lhs || rhs) {
return options.fn(this);
}
}
};
}).call(this);
//# sourceMappingURL=block-helpers.js.map

View File

@ -1,11 +1,9 @@
/**
Generic template helper definitions for command-line output.
@module console-helpers.js
@license MIT. See LICENSE.md for details.
*/
(function() {
/**
Generic template helper definitions for command-line output.
@module console-helpers.js
@license MIT. See LICENSE.md for details.
*/
var CHALK, LO, PAD, _, consoleFormatHelpers;
PAD = require('string-padding');
@ -21,7 +19,7 @@ Generic template helper definitions for command-line output.
consoleFormatHelpers = module.exports = {
v: function(val, defaultVal, padding, style) {
var retVal, spaces;
retVal = val === null || val === void 0 ? defaultVal : val;
retVal = (val === null || val === void 0) ? defaultVal : val;
spaces = 0;
if (String.is(padding)) {
spaces = parseInt(padding, 10);
@ -62,3 +60,5 @@ Generic template helper definitions for command-line output.
};
}).call(this);
//# sourceMappingURL=console-helpers.js.map

View File

@ -1,12 +1,18 @@
/**
Generic template helper definitions for HackMyResume / FluentCV.
@license MIT. See LICENSE.md for details.
@module helpers/generic-helpers
*/
(function() {
var FS, FluentDate, GenericHelpers, H2W, HMSTATUS, LO, MD, PATH, XML, _, _fromTo, _reportError, moment, printf, skillLevelToIndex, unused;
/**
Generic template helper definitions for HackMyResume / FluentCV.
@license MIT. See LICENSE.md for details.
@module helpers/generic-helpers
*/
/** Generic template helper function definitions. */
/**
Format a from/to date range for display.
*/
/**
Report an error to the outside world without throwing an exception. Currently
relies on kludging the running verb into. opts.
*/
var FS, FluentDate, GenericHelpers, H2W, HMSTATUS, LO, MD, PATH, XML, _, _fromTo, _reportError, _skillLevelToIndex, moment, printf, unused;
MD = require('marked');
@ -32,56 +38,108 @@ Generic template helper definitions for HackMyResume / FluentCV.
unused = require('../utils/string');
/** Generic template helper function definitions. */
GenericHelpers = module.exports = {
/**
Convert the input date to a specified format through Moment.js.
If date is invalid, will return the time provided by the user,
or default to the fallback param or 'Present' if that is set to true
@method formatDate
*/
formatDate: function(datetime, format, fallback) {
var momentDate, ref, ref1;
if (moment) {
Emit a formatted string representing the specified datetime.
Convert the input date to the specified format through Moment.js. If date is
valid, return the formatted date string. If date is null, undefined, or other
falsy value, return the value of the 'fallback' parameter, if specified, or
null if no fallback was specified. If date is invalid, but not null/undefined/
falsy, return it as-is.
@param {string|Moment} datetime A date value.
@param {string} [dtFormat='YYYY-MM'] The desired datetime format. Must be a
Moment.js-compatible datetime format.
@param {string|Moment} fallback A fallback value to use if the specified date
is null, undefined, or falsy.
*/
formatDate: function(datetime, dtFormat, fallback) {
var momentDate;
if (datetime == null) {
datetime = void 0;
}
if (dtFormat == null) {
dtFormat = 'YYYY-MM';
}
// If a Moment.js object was passed in, just call format on it
if (datetime && moment.isMoment(datetime)) {
return datetime.format(dtFormat);
}
if (String.is(datetime)) {
// If a string was passed in, convert to Moment using the 2-paramter
// constructor with an explicit format string.
momentDate = moment(datetime, dtFormat);
if (momentDate.isValid()) {
return momentDate.format(dtFormat);
}
// If that didn't work, try again with the single-parameter constructor
// but this may throw a deprecation warning
momentDate = moment(datetime);
if (momentDate.isValid()) {
return momentDate.format(format);
return momentDate.format(dtFormat);
}
}
return datetime || ((ref = typeof fallback === 'string') != null ? ref : {
fallback: (ref1 = fallback === true) != null ? ref1 : {
'Present': null
}
});
// We weren't able to format the provided datetime. Now do one of three
// things.
// 1. If datetime is non-null/non-falsy, return it. For this helper,
// string date values that we can't parse are assumed to be display dates.
// 2. If datetime IS null or falsy, use the value from the fallback.
// 3. If the fallback value is specifically 'true', emit 'Present'.
return datetime || (typeof fallback === 'string' ? fallback : (fallback === true ? 'Present' : ''));
},
/**
Emit a formatted string representing the specified datetime.
@param {string} dateValue A raw date value from the FRESH or JRS resume.
@param {string} [dateFormat='YYYY-MM'] The desired datetime format. Must be
compatible with Moment.js datetime formats.
@param {string} [dateDefault=null] The default date value to use if the dateValue
parameter is null, undefined, or falsy.
*/
date: function(dateValue, dateFormat, dateDefault) {
var dateValueMoment, dateValueSafe, reserved;
if (!dateDefault || !String.is(dateDefault)) {
dateDefault = 'Current';
}
if (!dateFormat || !String.is(dateFormat)) {
dateFormat = 'YYYY-MM';
}
if (!dateValue || !String.is(dateValue)) {
dateValue = null;
}
if (!dateValue) {
return dateDefault;
}
reserved = ['current', 'present', 'now'];
dateValueSafe = dateValue.trim().toLowerCase();
if (_.contains(reserved, dateValueSafe)) {
return dateValue;
}
dateValueMoment = moment(dateValue, dateFormat);
if (dateValueMoment.isValid()) {
return dateValueMoment.format(dateFormat);
}
return dateValue;
},
/**
Given a resume sub-object with a start/end date, format a representation of
the date range.
@method dateRange
*/
dateRange: function(obj, fmt, sep, fallback, options) {
*/
dateRange: function(obj, fmt, sep, fallback) {
if (!obj) {
return '';
}
return _fromTo(obj.start, obj.end, fmt, sep, fallback, options);
return _fromTo(obj.start, obj.end, fmt, sep, fallback);
},
/**
Format a from/to date range for display.
@method toFrom
*/
*/
fromTo: function() {
return _fromTo.apply(this, arguments);
},
/**
Return a named color value as an RRGGBB string.
@method toFrom
*/
*/
color: function(colorName, colorDefault) {
var ret;
if (!(colorName && colorName.trim())) {
@ -101,36 +159,11 @@ Generic template helper definitions for HackMyResume / FluentCV.
return ret;
}
},
/**
Return true if the section is present on the resume and has at least one
element.
@method section
*/
section: function(title, options) {
var obj, ret;
title = title.trim().toLowerCase();
obj = LO.get(this.r, title);
ret = '';
if (obj) {
if (_.isArray(obj)) {
if (obj.length) {
ret = options.fn(this);
}
} else if (_.isObject(obj)) {
if ((obj.history && obj.history.length) || (obj.sets && obj.sets.length)) {
ret = options.fn(this);
}
}
}
return ret;
},
/**
Emit the size of the specified named font.
@param key {String} A named style from the "fonts" section of the theme's
theme.json file. For example: 'default' or 'heading1'.
*/
*/
fontSize: function(key, defSize, units) {
var fontSpec, hasDef, ret;
ret = '';
@ -145,18 +178,22 @@ Generic template helper definitions for HackMyResume / FluentCV.
} else if (GenericHelpers.theme.fonts) {
fontSpec = LO.get(GenericHelpers.theme.fonts, this.format + '.' + key);
if (!fontSpec) {
// Check for an "all" format
if (GenericHelpers.theme.fonts.all) {
fontSpec = GenericHelpers.theme.fonts.all[key];
}
}
if (fontSpec) {
// fontSpec can be a string, an array, or an object
if (String.is(fontSpec)) {
// No font size was specified, only a font family.
} else if (_.isArray(fontSpec)) {
if (!String.is(fontSpec[0])) {
ret = fontSpec[0].size;
}
} else {
// A font description object.
ret = fontSpec.size;
}
}
@ -175,7 +212,6 @@ Generic template helper definitions for HackMyResume / FluentCV.
}
return ret;
},
/**
Emit the font face (such as 'Helvetica' or 'Calibri') associated with the
provided key.
@ -183,7 +219,7 @@ Generic template helper definitions for HackMyResume / FluentCV.
theme.json file. For example: 'default' or 'heading1'.
@param defFont {String} The font to use if the specified key isn't present.
Can be any valid font-face name such as 'Helvetica Neue' or 'Calibri'.
*/
*/
fontFace: function(key, defFont) {
var fontSpec, hasDef, ret;
ret = '';
@ -195,19 +231,25 @@ Generic template helper definitions for HackMyResume / FluentCV.
expected: 'key'
});
return ret;
// If the theme has a "fonts" section, lookup the font face.
} else if (GenericHelpers.theme.fonts) {
fontSpec = LO.get(GenericHelpers.theme.fonts, this.format + '.' + key);
if (!fontSpec) {
// Check for an "all" format
if (GenericHelpers.theme.fonts.all) {
fontSpec = GenericHelpers.theme.fonts.all[key];
}
}
if (fontSpec) {
// fontSpec can be a string, an array, or an object
if (String.is(fontSpec)) {
ret = fontSpec;
} else if (_.isArray(fontSpec)) {
// An array of fonts were specified. Each one could be a string
// or an object
ret = String.is(fontSpec[0]) ? fontSpec[0] : fontSpec[0].name;
} else {
// A font description object.
ret = fontSpec.name;
}
}
@ -225,18 +267,6 @@ Generic template helper definitions for HackMyResume / FluentCV.
}
return ret;
},
/**
Emit a comma-delimited list of font names suitable associated with the
provided key.
@param key {String} A named style from the "fonts" section of the theme's
theme.json file. For example: 'default' or 'heading1'.
@param defFontList {Array} The font list to use if the specified key isn't
present. Can be an array of valid font-face name such as 'Helvetica Neue'
or 'Calibri'.
@param sep {String} The default separator to use in the rendered output.
Defaults to ", " (comma with a space).
*/
fontList: function(key, defFontList, sep) {
var fontSpec, hasDef, ret;
ret = '';
@ -255,14 +285,18 @@ Generic template helper definitions for HackMyResume / FluentCV.
}
}
if (fontSpec) {
// fontSpec can be a string, an array, or an object
if (String.is(fontSpec)) {
ret = fontSpec;
} else if (_.isArray(fontSpec)) {
// An array of fonts were specified. Each one could be a string
// or an object
fontSpec = fontSpec.map(function(ff) {
return "'" + (String.is(ff) ? ff : ff.name) + "'";
});
ret = fontSpec.join(sep === void 0 ? ', ' : sep || '');
} else {
// A font description object.
ret = fontSpec.name;
}
}
@ -281,11 +315,6 @@ Generic template helper definitions for HackMyResume / FluentCV.
}
return ret;
},
/**
Capitalize the first letter of the word.
@method section
*/
camelCase: function(val) {
val = (val && val.trim()) || '';
if (val) {
@ -294,48 +323,35 @@ Generic template helper definitions for HackMyResume / FluentCV.
return val;
}
},
/**
Return true if the context has the property or subpropery.
@method has
*/
has: function(title, options) {
title = title && title.trim().toLowerCase();
if (LO.get(this.r, title)) {
return options.fn(this);
}
},
Display a user-overridable section title for a FRESH resume theme. Use this in
lieue of hard-coding section titles.
/**
Generic template helper function to display a user-overridable section
title for a FRESH resume theme. Use this in lieue of hard-coding section
titles.
Usage:
{{sectionTitle "sectionName"}}
{{sectionTitle "sectionName" "sectionTitle"}}
{{sectionTitle "sectionName"}}
{{sectionTitle "sectionName" "sectionTitle"}}
Example:
{{sectionTitle "Education"}}
{{sectionTitle "Employment" "Project History"}}
{{sectionTitle "Education"}}
{{sectionTitle "Employment" "Project History"}}
@param sect_name The name of the section being title. Must be one of the
top-level FRESH resume sections ("info", "education", "employment", etc.).
@param sect_title The theme-specified section title. May be replaced by the
user.
@method sectionTitle
*/
*/
sectionTitle: function(sname, stitle) {
// If not provided by the user, stitle should default to sname. ps.
// Handlebars silently passes in the options object to the last param,
// where in Underscore stitle will be null/undefined, so we check both.
// TODO: not actually sure that's true, given that we _.wrap these functions
stitle = (stitle && String.is(stitle) && stitle) || sname;
// If there's a section title override, use it.
return (this.opts.stitles && this.opts.stitles[sname.toLowerCase().trim()]) || stitle;
},
/**
Convert inline Markdown to inline WordProcessingML.
@method wpml
*/
wpml: function(txt, inline) {
if (!txt) {
return '';
@ -346,11 +362,10 @@ Generic template helper definitions for HackMyResume / FluentCV.
txt = H2W(txt);
return txt;
},
/**
Emit a conditional link.
@method link
*/
*/
link: function(text, url) {
if (url && url.trim()) {
return '<a href="' + url + '">' + text + '</a>';
@ -358,11 +373,21 @@ Generic template helper definitions for HackMyResume / FluentCV.
return text;
}
},
/**
Emit a conditional Markdown link.
@method link
*/
linkMD: function(text, url) {
if (url && url.trim()) {
return '[' + text + '](' + url + ')';
} else {
return text;
}
},
/**
Return the last word of the specified text.
@method lastWord
*/
*/
lastWord: function(txt) {
if (txt && txt.trim()) {
return _.last(txt.split(' '));
@ -370,7 +395,6 @@ Generic template helper definitions for HackMyResume / FluentCV.
return '';
}
},
/**
Convert a skill level to an RGB color triplet. TODO: refactor
@method skillColor
@ -378,28 +402,26 @@ Generic template helper definitions for HackMyResume / FluentCV.
("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, skillColors;
idx = skillLevelToIndex(lvl);
idx = _skillLevelToIndex(lvl);
skillColors = (this.theme && this.theme.palette && this.theme.palette.skillLevels) || ['#FFFFFF', '#5CB85C', '#F1C40F', '#428BCA', '#C00000'];
return skillColors[idx];
},
/**
Return an appropriate height. TODO: refactor
@method lastWord
*/
*/
skillHeight: function(lvl) {
var idx;
idx = skillLevelToIndex(lvl);
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) {
if (txt && txt.trim()) {
return _.initial(txt.split(' ')).join(' ');
@ -407,11 +429,10 @@ Generic template helper definitions for HackMyResume / FluentCV.
return '';
}
},
/**
Trim the protocol (http or https) from a URL/
@method trimURL
*/
*/
trimURL: function(url) {
if (url && url.trim()) {
return url.trim().replace(/^https?:\/\//i, '');
@ -419,11 +440,10 @@ Generic template helper definitions for HackMyResume / FluentCV.
return '';
}
},
/**
Convert text to lowercase.
@method toLower
*/
*/
toLower: function(txt) {
if (txt && txt.trim()) {
return txt.toLowerCase();
@ -431,11 +451,10 @@ Generic template helper definitions for HackMyResume / FluentCV.
return '';
}
},
/**
Convert text to lowercase.
@method toLower
*/
*/
toUpper: function(txt) {
if (txt && txt.trim()) {
return txt.toUpperCase();
@ -443,17 +462,6 @@ Generic template helper definitions for HackMyResume / FluentCV.
return '';
}
},
/**
Return true if either value is truthy.
@method either
*/
either: function(lhs, rhs, options) {
if (lhs || rhs) {
return options.fn(this);
}
},
/**
Conditional stylesheet link. Creates a link to the specified stylesheet with
<link> or embeds the styles inline with <style></style>, depending on the
@ -462,9 +470,10 @@ Generic template helper definitions for HackMyResume / FluentCV.
@param linkage {String} The default link method. Can be either `embed` or
`link`. If omitted, defaults to `embed`. Can be overridden by the `--css`
command-line switch.
*/
*/
styleSheet: function(url, linkage) {
var rawCss, renderedCss, ret;
// Establish the linkage style
linkage = this.opts.css || linkage || 'embed';
ret = '';
if (linkage === 'link') {
@ -474,21 +483,24 @@ Generic template helper definitions for HackMyResume / FluentCV.
renderedCss = this.engine.generateSimple(this, rawCss);
ret = printf('<style>%s</style>', renderedCss);
}
// If the currently-executing template is inherited, append styles
if (this.opts.themeObj.inherits && this.opts.themeObj.inherits.html && this.format === 'html') {
ret += linkage === 'link' ? '<link href="' + this.opts.themeObj.overrides.path + '" rel="stylesheet" type="text/css">' : '<style>' + this.opts.themeObj.overrides.data + '</style>';
}
// TODO: It would be nice to use Handlebar.SafeString here, but these
// are supposed to be generic helpers. Provide an equivalent, or expose
// it when Handlebars is the chosen engine, which is most of the time.
return ret;
},
/**
Perform a generic comparison.
See: http://doginthehat.com.au/2012/02/comparison-block-helper-for-handlebars-templates
@method compare
*/
*/
compare: function(lvalue, rvalue, options) {
var operator, operators, result;
if (arguments.length < 3) {
throw new Error("Handlerbars Helper 'compare' needs 2 parameters");
throw new Error("Template helper 'compare' needs 2 parameters");
}
operator = options.hash.operator || "==";
operators = {
@ -518,7 +530,7 @@ Generic template helper definitions for HackMyResume / FluentCV.
}
};
if (!operators[operator]) {
throw new Error("Handlerbars Helper 'compare' doesn't know the operator " + operator);
throw new Error("Helper 'compare' doesn't know the operator " + operator);
}
result = operators[operator](lvalue, rvalue);
if (result) {
@ -526,26 +538,61 @@ Generic template helper definitions for HackMyResume / FluentCV.
} else {
return options.inverse(this);
}
},
/**
Emit padded text.
*/
pad: function(stringOrArray, padAmount, unused) {
var PAD, ret;
stringOrArray = stringOrArray || '';
padAmount = padAmount || 0;
ret = '';
PAD = require('string-padding');
if (!String.is(stringOrArray)) {
ret = stringOrArray.map(function(line) {
return PAD(line, line.length + Math.abs(padAmount), null, padAmount < 0 ? PAD.LEFT : PAD.RIGHT);
}).join('\n');
} else {
ret = PAD(stringOrArray, stringOrArray.length + Math.abs(padAmount), null, padAmount < 0 ? PAD.LEFT : PAD.RIGHT);
}
return ret;
},
/**
Given the name of a skill ("JavaScript" or "HVAC repair"), return the number
of years assigned to that skill in the resume.skills.list collection.
*/
skillYears: function(skill, rez) {
var sk;
sk = _.find(rez.skills.list, function(sk) {
return sk.name.toUpperCase() === skill.toUpperCase();
});
if (sk) {
return sk.years;
} else {
return '?';
}
},
/**
Given an object that may be a string or an object, return it as-is if it's a
string, otherwise return the value at obj[objPath].
*/
stringOrObject: function(obj, objPath, rez) {
if (_.isString(obj)) {
return obj;
} else {
return LO.get(obj, objPath);
}
}
};
/**
Report an error to the outside world without throwing an exception. Currently
relies on kludging the running verb into. opts.
*/
_reportError = function(code, params) {
return GenericHelpers.opts.errHandler.err(code, params);
};
/**
Format a from/to date range for display.
*/
_fromTo = function(dateA, dateB, fmt, sep, fallback) {
var dateATrim, dateBTrim, dateFrom, dateTemp, dateTo, reserved;
// Prevent accidental use of safe.start, safe.end, safe.date
// The dateRange helper is for raw dates only
if (moment.isMoment(dateA) || moment.isMoment(dateB)) {
_reportError(HMSTATUS.invalidHelperUse, {
helper: 'dateRange'
@ -555,6 +602,7 @@ Generic template helper definitions for HackMyResume / FluentCV.
dateFrom = null;
dateTo = null;
dateTemp = null;
// Check for 'current', 'present', 'now', '', null, and undefined
dateA = dateA || '';
dateB = dateB || '';
dateATrim = dateA.trim().toLowerCase();
@ -569,12 +617,14 @@ Generic template helper definitions for HackMyResume / FluentCV.
dateFrom = dateTemp.format(fmt);
}
if (_.contains(reserved, dateBTrim)) {
dateTo = fallback || 'Current';
dateTo = fallback || 'Present';
} else {
dateTemp = FluentDate.fmt(dateB);
dateTo = dateTemp.format(fmt);
}
if (dateFrom && dateTo) {
if (dateFrom === dateTo) {
return dateFrom;
} else if (dateFrom && dateTo) {
return dateFrom + sep + dateTo;
} else if (dateFrom || dateTo) {
return dateFrom || dateTo;
@ -582,7 +632,7 @@ Generic template helper definitions for HackMyResume / FluentCV.
return '';
};
skillLevelToIndex = function(lvl) {
_skillLevelToIndex = function(lvl) {
var idx, intVal;
idx = 0;
if (String.is(lvl)) {
@ -613,4 +663,25 @@ Generic template helper definitions for HackMyResume / FluentCV.
return idx;
};
// Note [1] --------------------------------------------------------------------
// Make sure it's precisely a string or array since some template engines jam
// their options/context object into the last parameter and we are allowing the
// defFont parameter to be omitted in certain cases. This is a little kludgy,
// but works fine for this case. If we start doing this regularly, we should
// rebind these parameters.
// Note [2]: -------------------------------------------------------------------
// If execution reaches here, some sort of cosmic ray or sunspot has landed on
// HackMyResume, or a theme author is deliberately messing with us by doing
// something like:
// "fonts": {
// "default": "",
// "heading1": null
// }
// Rather than sort it out, we'll just fall back to defFont.
}).call(this);
//# sourceMappingURL=generic-helpers.js.map

View File

@ -1,12 +1,10 @@
/**
Template helper definitions for Handlebars.
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
@module handlebars-helpers.js
*/
(function() {
var HANDLEBARS, _, helpers;
/**
Template helper definitions for Handlebars.
@license MIT. See LICENSE.md for details.
@module handlebars-helpers.js
*/
var HANDLEBARS, HMS, _, blockHelpers, helpers, path;
HANDLEBARS = require('handlebars');
@ -14,16 +12,86 @@ Template helper definitions for Handlebars.
helpers = require('./generic-helpers');
path = require('path');
blockHelpers = require('./block-helpers');
HMS = require('../core/status-codes');
/**
Register useful Handlebars helpers.
@method registerHelpers
*/
module.exports = function(theme, opts) {
*/
module.exports = function(theme, rez, opts) {
var curGlob, ex, glob, slash, wrappedHelpers;
helpers.theme = theme;
helpers.opts = opts;
return HANDLEBARS.registerHelper(helpers);
helpers.type = 'handlebars';
// Prepare generic helpers for use with Handlebars. We do this by wrapping them
// in a Handlebars-aware wrapper which calls the helper internally.
wrappedHelpers = _.mapObject(helpers, function(hVal, hKey) {
if (_.isFunction(hVal)) {
return _.wrap(hVal, function(func) {
var args;
args = Array.prototype.slice.call(arguments);
args.shift(); // lose the 1st element (func) [^1]
//args.pop() # lose the last element (HB options hash)
args[args.length - 1] = rez; // replace w/ resume object
return func.apply(this, args); // call the generic helper
});
}
return hVal;
}, this);
HANDLEBARS.registerHelper(wrappedHelpers);
// Prepare Handlebars-specific helpers - "blockHelpers" is really a misnomer
// since any kind of Handlebars-specific helper can live here
HANDLEBARS.registerHelper(blockHelpers);
if (_.isString(theme.helpers)) {
// Register any theme-provided custom helpers...
// Normalize "theme.helpers" (string or array) to an array
theme.helpers = [theme.helpers];
}
if (_.isArray(theme.helpers)) {
glob = require('glob');
slash = require('slash');
curGlob = null;
try {
_.each(theme.helpers, function(fGlob) { // foreach theme.helpers entry
var files;
curGlob = fGlob; // ..cache in case of exception
fGlob = path.join(theme.folder, fGlob); // ..make relative to theme
files = glob.sync(slash(fGlob)); // ..expand the glob
if (files.length > 0) { // ..guard against empty glob
_.each(files, function(f) { // ..loop over concrete paths
HANDLEBARS.registerHelper(require(f)); // ..register the path
});
} else {
throw {
fluenterror: HMS.themeHelperLoad,
inner: er,
glob: fGlob
};
}
});
} catch (error) {
ex = error;
throw {
fluenterror: HMS.themeHelperLoad,
inner: ex,
glob: curGlob,
exit: true
};
}
}
};
// [^1]: This little bit of acrobatics ensures that our generic helpers are
// called as generic helpers, not as Handlebars-specific helpers. This allows
// them to be used in other templating engines, like Underscore. If you need a
// Handlebars-specific helper with normal Handlebars context and options, put it
// in block-helpers.coffee.
}).call(this);
//# sourceMappingURL=handlebars-helpers.js.map

View File

@ -1,11 +1,9 @@
/**
Template helper definitions for Underscore.
@license MIT. Copyright (c) 2016 hacksalot (https://github.com/hacksalot)
@module handlebars-helpers.js
*/
(function() {
/**
Template helper definitions for Underscore.
@license MIT. See LICENSE.md for details.
@module handlebars-helpers.js
*/
var HANDLEBARS, _, helpers;
HANDLEBARS = require('handlebars');
@ -14,19 +12,17 @@ Template helper definitions for Underscore.
helpers = require('./generic-helpers');
/**
Register useful Underscore helpers.
@method registerHelpers
*/
*/
module.exports = function(theme, opts, cssInfo, ctx, eng) {
helpers.theme = theme;
helpers.opts = opts;
helpers.cssInfo = cssInfo;
helpers.engine = eng;
ctx.h = helpers;
return _.each(helpers, function(hVal, hKey) {
_.each(helpers, function(hVal, hKey) {
if (_.isFunction(hVal)) {
return _.bind(hVal, ctx);
}
@ -34,3 +30,5 @@ Template helper definitions for Underscore.
};
}).call(this);
//# sourceMappingURL=underscore-helpers.js.map

27
dist/index.js vendored
View File

@ -1,25 +1,17 @@
/**
External API surface for HackMyResume.
@license MIT. See LICENSE.md for details.
@module hackmycore/index
*/
/**
API facade for HackMyCore.
*/
(function() {
var HackMyCore;
HackMyCore = module.exports = {
/**
External API surface for HackMyResume.
@license MIT. See LICENSE.md for details.
@module hackmycore/index
*/
/** API facade for HackMyResume. */
module.exports = {
verbs: {
build: require('./verbs/build'),
analyze: require('./verbs/analyze'),
validate: require('./verbs/validate'),
convert: require('./verbs/convert'),
"new": require('./verbs/create'),
new: require('./verbs/create'),
peek: require('./verbs/peek')
},
alias: {
@ -33,6 +25,7 @@ API facade for HackMyCore.
JRSResume: require('./core/jrs-resume'),
FRESHTheme: require('./core/fresh-theme'),
JRSTheme: require('./core/jrs-theme'),
ResumeFactory: require('./core/resume-factory'),
FluentDate: require('./core/fluent-date'),
HtmlGenerator: require('./generators/html-generator'),
TextGenerator: require('./generators/text-generator'),
@ -47,3 +40,5 @@ API facade for HackMyCore.
};
}).call(this);
//# sourceMappingURL=index.js.map

View File

@ -1,23 +1,13 @@
/**
Definition of the AbstractResume class.
@license MIT. See LICENSE.md for details.
@module core/abstract-resume
*/
(function() {
var AbstractResume, FluentDate, _, __;
var FluentDate, _, lo;
FluentDate = require('../core/fluent-date');
_ = require('underscore');
__ = require('lodash');
FluentDate = require('./fluent-date');
AbstractResume = (function() {
function AbstractResume() {}
lo = require('lodash');
module.exports = {
/**
Compute the total duration of the work history.
@returns The total duration of the sheet's work history, that is, the number
@ -25,18 +15,23 @@ Definition of the AbstractResume class.
*latest end date of all jobs in the work history*. This last condition is for
sheets that have overlapping jobs.
*/
AbstractResume.prototype.duration = function(collKey, startKey, endKey, unit) {
run: function(rez, collKey, startKey, endKey, unit) {
var firstDate, hist, lastDate, new_e;
unit = unit || 'years';
hist = __.get(this, collKey);
hist = lo.get(rez, collKey);
if (!hist || !hist.length) {
return 0;
}
// BEGIN CODE DUPLICATION --> src/inspectors/gap-inspector.coffee (TODO)
// Convert the candidate's employment history to an array of dates,
// where each element in the array is a start date or an end date of a
// job -- it doesn't matter which.
new_e = hist.map(function(job) {
var obj;
obj = _.pick(job, [startKey, endKey]);
if (!_.has(obj, endKey)) {
// Synthesize an end date if this is a "current" gig
obj[endKey] = 'current';
}
if (obj && (obj[startKey] || obj[endKey])) {
@ -48,6 +43,7 @@ Definition of the AbstractResume class.
}
return obj;
});
// Flatten the array, remove empties, and sort
new_e = _.filter(_.flatten(new_e, true), function(v) {
return v && v.length && v[0] && v[0].length;
});
@ -57,15 +53,13 @@ Definition of the AbstractResume class.
new_e = _.sortBy(new_e, function(elem) {
return elem[1].unix();
});
// END CODE DUPLICATION
firstDate = _.first(new_e)[1];
lastDate = _.last(new_e)[1];
return lastDate.diff(firstDate, unit);
};
return AbstractResume;
})();
module.exports = AbstractResume;
}
};
}).call(this);
//# sourceMappingURL=duration-inspector.js.map

View File

@ -1,11 +1,12 @@
/**
Employment gap analysis for HackMyResume.
@license MIT. See LICENSE.md for details.
@module inspectors/gap-inspector
*/
(function() {
/**
Employment gap analysis for HackMyResume.
@license MIT. See LICENSE.md for details.
@module inspectors/gap-inspector
*/
/**
Identify gaps in the candidate's employment history.
*/
var FluentDate, LO, _, gapInspector, moment;
_ = require('underscore');
@ -16,14 +17,8 @@ Employment gap analysis for HackMyResume.
LO = require('lodash');
/**
Identify gaps in the candidate's employment history.
*/
gapInspector = module.exports = {
moniker: 'gap-inspector',
/**
Run the Gap Analyzer on a resume.
@method run
@ -35,9 +30,10 @@ Employment gap analysis for HackMyResume.
end: // A Moment.js date
duration: // Gap length
}
*/
*/
run: function(rez) {
var coverage, dur, g, gap_start, hist, new_e, num_gaps, o, ref_count, tdur, total_gap_days;
// This is what we'll return
coverage = {
gaps: [],
overlaps: [],
@ -48,10 +44,14 @@ Employment gap analysis for HackMyResume.
gaps: 0
}
};
// Missing employment section? Bye bye.
hist = LO.get(rez, 'employment.history');
if (!hist || !hist.length) {
return coverage;
}
// Convert the candidate's employment history to an array of dates,
// where each element in the array is a start date or an end date of a
// job -- it doesn't matter which.
new_e = hist.map(function(job) {
var obj;
obj = _.pick(job, ['start', 'end']);
@ -64,6 +64,7 @@ Employment gap analysis for HackMyResume.
}
return obj;
});
// Flatten the array, remove empties, and sort
new_e = _.filter(_.flatten(new_e, true), function(v) {
return v && v.length && v[0] && v[0].length;
});
@ -73,6 +74,11 @@ Employment gap analysis for HackMyResume.
new_e = _.sortBy(new_e, function(elem) {
return elem[1].unix();
});
// Iterate over elements in the array. Each time a start date is found,
// increment a reference count. Each time an end date is found, decrement
// the reference count. When the reference count reaches 0, we have a gap.
// When the reference count is > 0, the candidate is employed. When the
// reference count reaches 2, the candidate is overlapped.
num_gaps = 0;
ref_count = 0;
total_gap_days = 0;
@ -81,11 +87,13 @@ Employment gap analysis for HackMyResume.
var inc, lastGap, lastOver;
inc = point[0] === 'start' ? 1 : -1;
ref_count += inc;
// If the ref count just reached 0, start a new GAP
if (ref_count === 0) {
return coverage.gaps.push({
start: point[1],
end: null
});
// If the ref count reached 1 by rising, end the last GAP
} else if (ref_count === 1 && inc === 1) {
lastGap = _.last(coverage.gaps);
if (lastGap) {
@ -93,11 +101,13 @@ Employment gap analysis for HackMyResume.
lastGap.duration = lastGap.end.diff(lastGap.start, 'days');
return total_gap_days += lastGap.duration;
}
// If the ref count reaches 2 by rising, start a new OVERLAP
} else if (ref_count === 2 && inc === 1) {
return coverage.overlaps.push({
start: point[1],
end: null
});
// If the ref count reaches 1 by falling, end the last OVERLAP
} else if (ref_count === 1 && inc === -1) {
lastOver = _.last(coverage.overlaps);
if (lastOver) {
@ -109,6 +119,9 @@ Employment gap analysis for HackMyResume.
}
}
});
// It's possible that the last gap/overlap didn't have an explicit .end
// date.If so, set the end date to the present date and compute the
// duration normally.
if (coverage.overlaps.length) {
o = _.last(coverage.overlaps);
if (o && !o.end) {
@ -123,6 +136,7 @@ Employment gap analysis for HackMyResume.
g.duration = g.end.diff(g.start, 'days');
}
}
// Package data for return to the client
tdur = rez.duration('days');
dur = {
total: tdur,
@ -136,3 +150,5 @@ Employment gap analysis for HackMyResume.
};
}).call(this);
//# sourceMappingURL=gap-inspector.js.map

View File

@ -1,48 +1,63 @@
/**
Keyword analysis for HackMyResume.
@license MIT. See LICENSE.md for details.
@module inspectors/keyword-inspector
*/
(function() {
/**
Keyword analysis for HackMyResume.
@license MIT. See LICENSE.md for details.
@module inspectors/keyword-inspector
*/
/**
Analyze the resume's use of keywords.
TODO: BUG: Keyword search regex is inaccurate, especially for one or two
letter keywords like "C" or "CLI".
@class keywordInspector
*/
var FluentDate, _, keywordInspector;
_ = require('underscore');
FluentDate = require('../core/fluent-date');
/**
Analyze the resume's use of keywords.
TODO: BUG: Keyword search regex is inaccurate, especially for one or two
letter keywords like "C" or "CLI".
@class keywordInspector
*/
keywordInspector = module.exports = {
/** A unique name for this inspector. */
moniker: 'keyword-inspector',
/**
Run the Keyword Inspector on a resume.
@method run
@return An collection of statistical keyword data.
*/
*/
run: function(rez) {
var prefix, regex_quote, searchable, suffix;
// "Quote" or safely escape a keyword so it can be used as a regex. For
// example, if the keyword is "C++", yield "C\+\+".
// http://stackoverflow.com/a/2593661/4942583
regex_quote = function(str) {
return (str + '').replace(/[.?*+^$[\]\\(){}|-]/ig, "\\$&");
};
// Create a searchable plain-text digest of the resume
// TODO: BUG: Don't search within keywords for other keywords. Job A
// declares the "foo" keyword. Job B declares the "foo & bar" keyword. Job
// B's mention of "foobar" should not count as a mention of "foo".
// To achieve this, remove keywords from the search digest and treat them
// separately.
searchable = '';
rez.transformStrings(['imp', 'computed', 'safe'], function(key, val) {
return searchable += ' ' + val;
});
// Assemble a regex skeleton we can use to test for keywords with a bit
// more
prefix = '(?:' + ['^', '\\s+', '[\\.,]+'].join('|') + ')';
suffix = '(?:' + ['$', '\\s+', '[\\.,]+'].join('|') + ')';
return rez.keywords().map(function(kw) {
var count, myArray, regex, regex_str;
// 1. Using word boundary or other regex class is inaccurate
// var regex = new RegExp( '\\b' + regex_quote( kw )/* + '\\b'*/, 'ig');
// 2. Searching for the raw keyword is inaccurate ("C" will match any
// word containing a 'c'!).
// var regex = new RegExp( regex_quote( kw ), 'ig');
// 3. Instead, use a custom regex with special delimeters.
regex_str = prefix + regex_quote(kw) + suffix;
regex = new RegExp(regex_str, 'ig');
myArray = null;
@ -59,3 +74,5 @@ Keyword analysis for HackMyResume.
};
}).call(this);
//# sourceMappingURL=keyword-inspector.js.map

View File

@ -1,32 +1,27 @@
/**
Section analysis for HackMyResume.
@license MIT. See LICENSE.md for details.
@module inspectors/totals-inspector
*/
(function() {
/**
Section analysis for HackMyResume.
@license MIT. See LICENSE.md for details.
@module inspectors/totals-inspector
*/
/**
Retrieve sectional overview and summary information.
@class totalsInspector
*/
var FluentDate, _, totalsInspector;
_ = require('underscore');
FluentDate = require('../core/fluent-date');
/**
Retrieve sectional overview and summary information.
@class totalsInspector
*/
totalsInspector = module.exports = {
moniker: 'totals-inspector',
/**
Run the Totals Inspector on a resume.
@method run
@return An object containing summary information for each section on the
resume.
*/
*/
run: function(rez) {
var sectionTotals;
sectionTotals = {};
@ -47,3 +42,5 @@ Section analysis for HackMyResume.
};
}).call(this);
//# sourceMappingURL=totals-inspector.js.map

View File

@ -1,11 +1,13 @@
/**
Definition of the HandlebarsGenerator class.
@license MIT. See LICENSE.md for details.
@module renderers/handlebars-generator
*/
(function() {
/**
Definition of the HandlebarsGenerator class.
@license MIT. See LICENSE.md for details.
@module renderers/handlebars-generator
*/
/**
Perform template-based resume generation using Handlebars.js.
@class HandlebarsGenerator
*/
var FS, HANDLEBARS, HMSTATUS, HandlebarsGenerator, PATH, READFILES, SLASH, _, parsePath, registerHelpers, registerPartials;
_ = require('underscore');
@ -26,32 +28,28 @@ Definition of the HandlebarsGenerator class.
SLASH = require('slash');
/**
Perform template-based resume generation using Handlebars.js.
@class HandlebarsGenerator
*/
HandlebarsGenerator = module.exports = {
generateSimple: function(data, tpl) {
var template;
var err, template;
try {
// Compile and run the Handlebars template.
template = HANDLEBARS.compile(tpl, {
strict: false,
assumeObjects: false
assumeObjects: false,
noEscape: data.opts.noescape
});
return template(data);
} catch (_error) {
} catch (error1) {
err = error1;
throw {
fluenterror: template ? HMSTATUS.invokeTemplate : HMSTATUS.compileTemplate,
inner: _error
fluenterror: HMSTATUS[template ? 'invokeTemplate' : 'compileTemplate'],
inner: err
};
}
},
generate: function(json, jst, format, curFmt, opts, theme) {
var ctx, encData;
registerPartials(format, theme);
registerHelpers(theme, opts);
// Preprocess text
encData = json;
if (format === 'html' || format === 'pdf') {
encData = json.markdownify();
@ -59,6 +57,10 @@ Definition of the HandlebarsGenerator class.
if (format === 'doc') {
encData = json.xmlify();
}
// Set up partials and helpers
registerPartials(format, theme);
registerHelpers(theme, encData, opts);
// Set up the context
ctx = {
r: encData,
RAW: json,
@ -69,6 +71,7 @@ Definition of the HandlebarsGenerator class.
results: curFmt.files,
headFragment: opts.headFragment || ''
};
// Render the template
return this.generateSimple(ctx, jst);
}
};
@ -76,7 +79,10 @@ Definition of the HandlebarsGenerator class.
registerPartials = function(format, theme) {
var partialsFolder;
if (_.contains(['html', 'doc', 'md', 'txt', 'pdf'], format)) {
// Locate the global partials folder
partialsFolder = PATH.join(parsePath(require.resolve('fresh-themes')).dirname, '/partials/', format === 'pdf' ? 'html' : format);
// Register global partials in the /partials/[format] folder
// TODO: Only do this once per HMR invocation.
_.each(READFILES(partialsFolder, function(error) {
return {};
}), function(el) {
@ -89,6 +95,7 @@ Definition of the HandlebarsGenerator class.
return theme.partialsInitialized = true;
});
}
// Register theme-specific partials
return _.each(theme.partials, function(el) {
var compiledTemplate, tplData;
tplData = FS.readFileSync(el.path, 'utf8');
@ -98,3 +105,5 @@ Definition of the HandlebarsGenerator class.
};
}).call(this);
//# sourceMappingURL=handlebars-generator.js.map

View File

@ -1,11 +1,13 @@
/**
Definition of the JRSGenerator class.
@license MIT. See LICENSE.md for details.
@module renderers/jrs-generator
*/
(function() {
/**
Definition of the JRSGenerator class.
@license MIT. See LICENSE.md for details.
@module renderers/jrs-generator
*/
/**
Perform template-based resume generation for JSON Resume themes.
@class JRSGenerator
*/
var FS, HANDLEBARS, JRSGenerator, MD, MDIN, PATH, READFILES, SLASH, _, parsePath, registerHelpers;
_ = require('underscore');
@ -26,15 +28,10 @@ Definition of the JRSGenerator class.
MD = require('marked');
/**
Perform template-based resume generation for JSON Resume themes.
@class JRSGenerator
*/
JRSGenerator = module.exports = {
generate: function(json, jst, format, cssInfo, opts, theme) {
var org, rezHtml, turnoff;
// Disable JRS theme chatter (console.log, console.error, etc.)
turnoff = ['log', 'error', 'dir'];
org = turnoff.map(function(c) {
var ret;
@ -42,18 +39,23 @@ Definition of the JRSGenerator class.
console[c] = function() {};
return ret;
});
// Freeze and render
rezHtml = theme.render(json.harden());
// Turn logging back on
turnoff.forEach(function(c, idx) {
return console[c] = org[idx];
});
return rezHtml = rezHtml.replace(/@@@@~.*?~@@@@/gm, function(val) {
return MDIN(val.replace(/~@@@@/gm, '').replace(/@@@@~/gm, ''));
// Unfreeze and apply Markdown
return rezHtml = rezHtml.replace(/@@@@~[\s\S]*?~@@@@/g, function(val) {
return MDIN(val.replace(/~@@@@/g, '').replace(/@@@@~/g, ''));
});
}
};
MDIN = function(txt) {
MDIN = function(txt) { // TODO: Move this
return MD(txt || '').replace(/^\s*<p>|<\/p>\s*$/gi, '');
};
}).call(this);
//# sourceMappingURL=jrs-generator.js.map

View File

@ -1,40 +1,43 @@
/**
Definition of the UnderscoreGenerator class.
@license MIT. See LICENSE.md for details.
@module underscore-generator.js
*/
(function() {
var HMSTATUS, UnderscoreGenerator, _, registerHelpers;
/**
Definition of the UnderscoreGenerator class.
@license MIT. See LICENSE.md for details.
@module underscore-generator.js
*/
/**
Perform template-based resume generation using Underscore.js.
@class UnderscoreGenerator
*/
var UnderscoreGenerator, _, escapeLaTeX, registerHelpers;
_ = require('underscore');
registerHelpers = require('../helpers/underscore-helpers');
HMSTATUS = require('../core/status-codes');
require('../utils/string');
/**
Perform template-based resume generation using Underscore.js.
@class UnderscoreGenerator
*/
escapeLaTeX = require('escape-latex');
UnderscoreGenerator = module.exports = {
generateSimple: function(data, tpl) {
var template;
var HMS, err, t;
try {
template = _.template(tpl);
return template(data);
} catch (_error) {
// Compile and run the Handlebars template.
t = _.template(tpl);
return t(data);
} catch (error) {
err = error;
//console.dir _error
HMS = require('../core/status-codes');
throw {
fluenterror: template ? HMSTATUS.invokeTemplate : HMSTATUS.compileTemplate,
inner: _error
fluenterror: HMS[t ? 'invokeTemplate' : 'compileTemplate'],
inner: err
};
}
},
generate: function(json, jst, format, cssInfo, opts, theme) {
var ctx, delims;
var ctx, delims, r, traverse;
// Tweak underscore's default template delimeters
delims = (opts.themeObj && opts.themeObj.delimeters) || opts.template;
if (opts.themeObj && opts.themeObj.delimeters) {
delims = _.mapObject(delims, function(val, key) {
@ -42,19 +45,47 @@ Definition of the UnderscoreGenerator class.
});
}
_.templateSettings = delims;
jst = jst.replace(delims.comment, '');
r = null;
switch (format) {
case 'html':
r = json.markdownify();
break;
case 'pdf':
r = json.markdownify();
break;
case 'png':
r = json.markdownify();
break;
case 'latex':
traverse = require('traverse');
r = traverse(json).map(function(x) {
if (this.isLeaf && String.is(this.node)) {
return escapeLaTeX(this.node);
}
return this.node;
});
break;
default:
r = json;
}
// Set up the context
ctx = {
r: format === 'html' || format === 'pdf' || format === 'png' ? json.markdownify() : json,
r: r,
filt: opts.filters,
XML: require('xml-escape'),
RAW: json,
cssInfo: cssInfo,
//engine: @
headFragment: opts.headFragment || '',
opts: opts
};
// Link to our helpers
registerHelpers(theme, opts, cssInfo, ctx, this);
// Generate!
return this.generateSimple(ctx, jst);
}
};
}).call(this);
//# sourceMappingURL=underscore-generator.js.map

72
dist/utils/class.js vendored
View File

@ -1,72 +0,0 @@
/**
Definition of John Resig's `Class` class.
@module class.js
*/
/* Simple JavaScript Inheritance
* By John Resig http://ejohn.org/
* MIT Licensed.
* http://ejohn.org/blog/simple-javascript-inheritance/
*/
// Inspired by base2 and Prototype
(function(){
var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
// The base Class implementation (does nothing)
this.Class = function(){};
module.exports = Class;
// Create a new Class that inherits from this class
Class.extend = function(prop) {
var _super = this.prototype;
// Instantiate a base class (but only create the instance,
// don't run the init constructor)
initializing = true;
var prototype = new this();
initializing = false;
// Copy the properties over onto the new prototype
for (var name in prop) {
// Check if we're overwriting an existing function
prototype[name] = typeof prop[name] == "function" &&
typeof _super[name] == "function" && fnTest.test(prop[name]) ?
(function(name, fn){
return function() {
var tmp = this._super;
// Add a new ._super() method that is the same method
// but on the super-class
this._super = _super[name];
// The method only need to be bound temporarily, so we
// remove it when we're done executing
var ret = fn.apply(this, arguments);
this._super = tmp;
return ret;
};
})(name, prop[name]) : // jshint ignore:line
prop[name];
}
// The dummy class constructor
function Class() {
// All construction is actually done in the init method
if ( !initializing && this.init )
this.init.apply(this, arguments);
}
// Populate our constructed prototype object
Class.prototype = prototype;
// Enforce the constructor to be what we expect
Class.prototype.constructor = Class;
// And make this class extendable
Class.extend = arguments.callee;
return Class;
};
})();

View File

@ -1,12 +1,12 @@
/**
Definition of the SyntaxErrorEx class.
@module file-contains.js
*/
(function() {
/**
Definition of the SyntaxErrorEx class.
@module file-contains.js
*/
module.exports = function(file, needle) {
return require('fs').readFileSync(file, 'utf-8').indexOf(needle) > -1;
};
}).call(this);
//# sourceMappingURL=file-contains.js.map

27
dist/utils/fresh-version-regex.js vendored Normal file
View File

@ -0,0 +1,27 @@
(function() {
/**
Defines a regex suitable for matching FRESH versions.
@module file-contains.js
*/
// Set up a regex that matches all of the following:
// - FRESH
// - JRS
// - FRESCA
// - FRESH@1.0.0
// - FRESH@1.0
// - FRESH@1
// - JRS@0.16.0
// - JRS@0.16
// - JRS@0
// Don't use a SEMVER regex (eg, NPM's semver-regex) because a) we want to
// support partial semvers like "0" or "1.2" and b) we'll expand this later to
// support fully scoped FRESH versions.
module.exports = function() {
return RegExp('^(FRESH|FRESCA|JRS)(?:@(\\d+(?:\\.\\d+)?(?:\\.\\d+)?))?$');
};
}).call(this);
//# sourceMappingURL=fresh-version-regex.js.map

View File

@ -1,12 +1,12 @@
/**
Definition of the Markdown to WordProcessingML conversion routine.
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
@module utils/html-to-wpml
*/
(function() {
var HTML5Tokenizer, _;
/**
Definition of the Markdown to WordProcessingML conversion routine.
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
@module utils/html-to-wpml
*/
var HTML5Tokenizer, XML, _;
XML = require('xml-escape');
_ = require('underscore');
@ -14,8 +14,12 @@ Definition of the Markdown to WordProcessingML conversion routine.
module.exports = function(html) {
var final, is_bold, is_italic, is_link, link_url, tokens;
// Tokenize the HTML stream.
tokens = HTML5Tokenizer.tokenize(html);
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) {
var style;
switch (tok.type) {
@ -51,7 +55,7 @@ Definition of the Markdown to WordProcessingML conversion routine.
style = is_bold ? '<w:b/>' : '';
style += is_italic ? '<w:i/>' : '';
style += is_link ? '<w:rStyle w:val="Hyperlink"/>' : '';
return 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>' : '');
return final += (is_link ? '<w:hlink w:dest="' + link_url + '">' : '') + '<w:r><w:rPr>' + style + '</w:rPr><w:t>' + XML(tok.chars) + '</w:t></w:r>' + (is_link ? '</w:hlink>' : '');
}
}
});
@ -59,3 +63,5 @@ Definition of the Markdown to WordProcessingML conversion routine.
};
}).call(this);
//# sourceMappingURL=html-to-wpml.js.map

View File

@ -1,11 +1,9 @@
/**
Inline Markdown-to-Chalk conversion routines.
@license MIT. See LICENSE.md for details.
@module utils/md2chalk
*/
(function() {
/**
Inline Markdown-to-Chalk conversion routines.
@license MIT. See LICENSE.md for details.
@module utils/md2chalk
*/
var CHALK, LO, MD;
MD = require('marked');
@ -26,3 +24,5 @@ Inline Markdown-to-Chalk conversion routines.
};
}).call(this);
//# sourceMappingURL=md2chalk.js.map

View File

@ -1,4 +1,7 @@
(function() {
// Exemplar script for generating documents with Phantom.js.
// https://raw.githubusercontent.com/ariya/phantomjs/master/examples/rasterize.js
// Converted to CoffeeScript by hacksalot
"use strict";
var address, output, page, pageHeight, pageWidth, size, system;
@ -50,7 +53,7 @@
} else {
console.log("size:", system.args[3]);
pageWidth = parseInt(system.args[3], 10);
pageHeight = parseInt(pageWidth * 3 / 4, 10);
pageHeight = parseInt(pageWidth * 3 / 4, 10); // it's as good an assumption as any
console.log("pageHeight:", pageHeight);
page.viewportSize = {
width: pageWidth,
@ -75,3 +78,5 @@
}
}).call(this);
//# sourceMappingURL=rasterize.js.map

19
dist/utils/resume-detector.js vendored Normal file
View File

@ -0,0 +1,19 @@
(function() {
/**
Definition of the ResumeDetector class.
@module utils/resume-detector
@license MIT. See LICENSE.md for details.
*/
module.exports = function(rez) {
if (rez.meta && rez.meta.format) { //&& rez.meta.format.substr(0, 5).toUpperCase() == 'FRESH'
return 'fresh';
} else if (rez.basics) {
return 'jrs';
} else {
return 'unk';
}
};
}).call(this);
//# sourceMappingURL=resume-detector.js.map

55
dist/utils/resume-scrubber.js vendored Normal file
View File

@ -0,0 +1,55 @@
(function() {
module.exports = {
/**
Removes ignored or private fields from a resume object
@returns an object with the following structure:
{
scrubbed: the processed resume object
ignoreList: an array of ignored nodes that were removed
privateList: an array of private nodes that were removed
}
*/
scrubResume: function(rep, opts) {
var ignoreList, includePrivates, privateList, scrubbed, traverse;
traverse = require('traverse');
ignoreList = [];
privateList = [];
includePrivates = opts && opts.private;
scrubbed = traverse(rep).map(function() { // [^1]
if (!this.isLeaf) {
if (this.node.ignore === true || this.node.ignore === 'true') {
ignoreList.push(this.node);
this.delete();
} else if ((this.node.private === true || this.node.private === 'true') && !includePrivates) {
privateList.push(this.node);
this.delete();
}
}
if (_.isArray(this.node)) { // [^2]
this.after(function() {
this.update(_.compact(this.node));
});
}
});
return {
scrubbed: scrubbed,
ingoreList: ignoreList,
privateList: privateList
};
}
};
// [^1]: As of v0.6.6, the NPM traverse library has a quirk when attempting
// to remove array elements directly using traverse's `this.remove`. See:
// https://github.com/substack/js-traverse/issues/48
// [^2]: The workaround is to use traverse's 'this.delete' to nullify the value
// first, followed by removal with something like _.compact.
// https://github.com/substack/js-traverse/issues/48#issuecomment-142607200
}).call(this);
//# sourceMappingURL=resume-scrubber.js.map

View File

@ -1,11 +1,9 @@
/**
Definition of the SafeJsonLoader class.
@module utils/safe-json-loader
@license MIT. See LICENSE.md for details.
*/
(function() {
/**
Definition of the SafeJsonLoader class.
@module utils/safe-json-loader
@license MIT. See LICENSE.md for details.
*/
var FS, SyntaxErrorEx;
FS = require('fs');
@ -13,16 +11,19 @@ Definition of the SafeJsonLoader class.
SyntaxErrorEx = require('./syntax-error-ex');
module.exports = function(file) {
var ret, retRaw;
var err, ret, retRaw;
ret = {};
try {
ret.raw = FS.readFileSync(file, 'utf8');
ret.json = JSON.parse(ret.raw);
} catch (_error) {
} catch (error) {
err = error;
// If we get here, either FS.readFileSync or JSON.parse failed.
// We'll return HMSTATUS.readError or HMSTATUS.parseError.
retRaw = ret.raw && ret.raw.trim();
ret.ex = {
operation: retRaw ? 'parse' : 'read',
inner: SyntaxErrorEx.is(_error) ? new SyntaxErrorEx(_error, retRaw) : _error,
op: retRaw ? 'parse' : 'read',
inner: SyntaxErrorEx.is(err) ? new SyntaxErrorEx(err, retRaw) : err,
file: file
};
}
@ -30,3 +31,5 @@ Definition of the SafeJsonLoader class.
};
}).call(this);
//# sourceMappingURL=safe-json-loader.js.map

View File

@ -1,46 +1,43 @@
/**
Safe spawn utility for HackMyResume / FluentCV.
@module utils/safe-spawn
@license MIT. See LICENSE.md for details.
*/
(function() {
module.exports = function(cmd, args, isSync, callback) {
var info, spawn;
/**
Safe spawn utility for HackMyResume / FluentCV.
@module utils/safe-spawn
@license MIT. See LICENSE.md for details.
*/
/** Safely spawn a process synchronously or asynchronously without throwing an
exception */
module.exports = function(cmd, args, isSync, callback, param) {
var ex, info, spawn;
try {
// .spawnSync not available on earlier Node.js, so default to .spawn
spawn = require('child_process')[isSync ? 'spawnSync' : 'spawn'];
info = spawn(cmd, args);
if (!isSync) {
info.on('error', function(err) {
if (callback != null) {
callback(err);
} else {
throw {
cmd: cmd,
inner: err
};
if (typeof callback === "function") {
callback(err, param);
}
});
} else {
if (info.error) {
if (callback != null) {
callback(err);
} else {
throw {
cmd: cmd,
inner: info.error
};
if (typeof callback === "function") {
callback(info.error, param);
}
return {
cmd: cmd,
inner: info.error
};
}
}
} catch (_error) {
if (callback != null) {
return callback(_error);
} else {
throw _error;
} catch (error) {
ex = error;
if (typeof callback === "function") {
callback(ex, param);
}
return ex;
}
};
}).call(this);
//# sourceMappingURL=safe-spawn.js.map

View File

@ -1,26 +1,23 @@
/**
Object string transformation.
@module utils/string-transformer
@license MIT. See LICENSE.md for details.
*/
(function() {
/**
Object string transformation.
@module utils/string-transformer
@license MIT. See LICENSE.md for details.
*/
var _, moment;
_ = require('underscore');
moment = require('moment');
/**
Create a copy of this object in which all string fields have been run through
a transformation function (such as a Markdown filter or XML encoder).
*/
*/
module.exports = function(ret, filt, transformer) {
var that, transformStringsInObject;
that = this;
// TODO: refactor recursion
transformStringsInObject = function(obj, filters) {
if (!obj) {
return;
@ -60,3 +57,5 @@ Object string transformation.
};
}).call(this);
//# sourceMappingURL=string-transformer.js.map

24
dist/utils/string.js vendored
View File

@ -1,17 +1,13 @@
/**
Definitions of string utility functions.
@module utils/string
*/
/**
Determine if the string is null, empty, or whitespace.
See: http://stackoverflow.com/a/32800728/4942583
@method isNullOrWhitespace
*/
(function() {
/**
Definitions of string utility functions.
@module utils/string
*/
/**
Determine if the string is null, empty, or whitespace.
See: http://stackoverflow.com/a/32800728/4942583
@method isNullOrWhitespace
*/
String.isNullOrWhitespace = function(input) {
return !input || !input.trim();
};
@ -25,3 +21,5 @@ See: http://stackoverflow.com/a/32800728/4942583
};
}).call(this);
//# sourceMappingURL=string.js.map

View File

@ -1,35 +1,45 @@
/**
Definition of the SyntaxErrorEx class.
@module utils/syntax-error-ex
@license MIT. See LICENSE.md for details.
*/
/**
Represents a SyntaxError exception with line and column info.
Collect syntax error information from the provided exception object. The
JavaScript `SyntaxError` exception isn't interpreted uniformly across environ-
ments, so we reparse on error to grab the line and column.
See: http://stackoverflow.com/q/13323356
@class SyntaxErrorEx
*/
(function() {
/**
Definition of the SyntaxErrorEx class.
@module utils/syntax-error-ex
@license MIT. See LICENSE.md for details.
*/
var SyntaxErrorEx;
SyntaxErrorEx = function(ex, rawData) {
var JSONLint, colNum, lineNum, lint;
lineNum = null;
colNum = null;
JSONLint = require('json-lint');
lint = JSONLint(rawData, {
comments: false
});
this.line = lint.error ? lint.line : '???';
return this.col = lint.error ? lint.character : '???';
/**
Represents a SyntaxError exception with line and column info.
Collect syntax error information from the provided exception object. The
JavaScript `SyntaxError` exception isn't interpreted uniformly across environ-
ments, so we reparse on error to grab the line and column.
See: http://stackoverflow.com/q/13323356
@class SyntaxErrorEx
*/
SyntaxErrorEx = class SyntaxErrorEx {
constructor(ex, rawData) {
var JSONLint, colNum, err, lineNum, lint;
lineNum = null;
colNum = null;
JSONLint = require('json-lint');
lint = JSONLint(rawData, {
comments: false
});
if (lint.error) {
[this.line, this.col] = [lint.line, lint.character];
}
if (!lint.error) {
JSONLint = require('jsonlint');
try {
JSONLint.parse(rawData);
} catch (error) {
err = error;
this.line = (/on line (\d+)/.exec(err))[1];
}
}
}
};
// Return true if the supplied parameter is a JavaScript SyntaxError
SyntaxErrorEx.is = function(ex) {
return ex instanceof SyntaxError;
};
@ -37,3 +47,5 @@ See: http://stackoverflow.com/q/13323356
module.exports = SyntaxErrorEx;
}).call(this);
//# sourceMappingURL=syntax-error-ex.js.map

102
dist/verbs/analyze.js vendored
View File

@ -1,12 +1,12 @@
/**
Implementation of the 'analyze' verb for HackMyResume.
@module verbs/analyze
@license MIT. See LICENSE.md for details.
*/
(function() {
var AnalyzeVerb, HMEVENT, HMSTATUS, MKDIRP, PATH, ResumeFactory, Verb, _, _analyze, _loadInspectors, analyze, chalk;
/**
Implementation of the 'analyze' verb for HackMyResume.
@module verbs/analyze
@license MIT. See LICENSE.md for details.
*/
/** Private workhorse for the 'analyze' command. */
/** Analyze a single resume. */
var AnalyzeVerb, HMEVENT, HMSTATUS, MKDIRP, PATH, ResumeFactory, Verb, _, _analyze, _analyzeOne, _loadInspectors, chalk;
MKDIRP = require('mkdirp');
@ -24,46 +24,52 @@ Implementation of the 'analyze' verb for HackMyResume.
chalk = require('chalk');
AnalyzeVerb = module.exports = Verb.extend({
init: function() {
return this._super('analyze', analyze);
/** An invokable resume analysis command. */
module.exports = AnalyzeVerb = class AnalyzeVerb extends Verb {
constructor() {
super('analyze', _analyze);
}
});
/**
Run the 'analyze' command.
*/
analyze = function(sources, dst, opts) {
var nlzrs;
if (!sources || !sources.length) {
throw {
fluenterror: HMSTATUS.resumeNotFound,
quit: true
};
}
nlzrs = _loadInspectors();
return _.each(sources, function(src) {
var result;
result = ResumeFactory.loadOne(src, {
format: 'FRESH',
objectify: true
}, this);
if (result.fluenterror) {
return this.setError(result.fluenterror, result);
} else {
return _analyze.call(this, result, nlzrs, opts);
}
}, this);
};
_analyze = function(sources, dst, opts) {
var nlzrs, results;
if (!sources || !sources.length) {
this.err(HMSTATUS.resumeNotFound, {
quit: true
});
return null;
}
nlzrs = _loadInspectors();
results = _.map(sources, function(src) {
var r;
r = ResumeFactory.loadOne(src, {
format: 'FRESH',
objectify: true,
inner: {
private: opts.private === true
}
}, this);
if (opts.assert && this.hasError()) {
return {};
}
if (r.fluenterror) {
r.quit = opts.assert;
this.err(r.fluenterror, r);
return r;
} else {
return _analyzeOne.call(this, r, nlzrs, opts);
}
}, this);
if (this.hasError() && !opts.assert) {
this.reject(this.errorCode);
} else if (!this.hasError()) {
this.resolve(results);
}
return results;
};
/**
Analyze a single resume.
*/
_analyze = function(resumeObject, nlzrs, opts) {
_analyzeOne = function(resumeObject, nlzrs, opts) {
var info, rez, safeFormat;
rez = resumeObject.rez;
safeFormat = rez.meta && rez.meta.format && rez.meta.format.startsWith('FRESH') ? 'FRESH' : 'JRS';
@ -74,16 +80,12 @@ Implementation of the 'analyze' verb for HackMyResume.
info = _.mapObject(nlzrs, function(val, key) {
return val.run(rez);
});
return this.stat(HMEVENT.afterAnalyze, {
this.stat(HMEVENT.afterAnalyze, {
info: info
});
return info;
};
/**
Load inspectors.
*/
_loadInspectors = function() {
return {
totals: require('../inspectors/totals-inspector'),
@ -93,3 +95,5 @@ Implementation of the 'analyze' verb for HackMyResume.
};
}).call(this);
//# sourceMappingURL=analyze.js.map

297
dist/verbs/build.js vendored
View File

@ -1,12 +1,49 @@
/**
Implementation of the 'build' verb for HackMyResume.
@module verbs/build
@license MIT. See LICENSE.md for details.
*/
(function() {
var BuildVerb, FRESHTheme, FS, HMEVENT, HMSTATUS, JRSTheme, MD, MKDIRP, PATH, RConverter, RTYPES, ResumeFactory, Verb, _, _err, _fmts, _log, _opts, _rezObj, addFreebieFormats, build, expand, extend, loadTheme, parsePath, prep, single, verifyOutputs, verifyTheme;
/**
Implementation of the 'build' verb for HackMyResume.
@module verbs/build
@license MIT. See LICENSE.md for details.
*/
/**
Reinforce the chosen theme with "freebie" formats provided by HackMyResume.
A "freebie" format is an output format such as JSON, YML, or PNG that can be
generated directly from the resume model or from one of the theme's declared
output formats. For example, the PNG format can be generated for any theme
that declares an HTML format; the theme doesn't have to provide an explicit
PNG template.
@param theTheme A FRESHTheme or JRSTheme object.
*/
/**
Given a source resume in FRESH or JRS format, a destination resume path, and a
theme file, generate 0..N resumes in the desired formats.
@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 opts Generation options.
*/
/**
Expand output files. For example, "foo.all" should be expanded to
["foo.html", "foo.doc", "foo.pdf", "etc"].
@param dst An array of output files as specified by the user.
@param theTheme A FRESHTheme or JRSTheme object.
*/
/**
Load the specified theme, which could be either a FRESH theme or a JSON Resume
theme (or both).
*/
/**
Prepare for a BUILD run.
*/
/**
Generate a single target resume such as "out/rez.html" or "out/rez.doc".
TODO: Refactor.
@param targInfo Information for the target resume.
@param theme A FRESHTheme or JRSTheme object.
*/
/** Ensure that user-specified outputs/targets are valid. */
/**
Verify the specified theme name/path.
*/
var BuildVerb, FRESHTheme, FS, HMEVENT, HMSTATUS, JRSTheme, MD, MKDIRP, PATH, RConverter, RTYPES, ResumeFactory, Verb, _, _addFreebieFormats, _build, _err, _expand, _fmts, _loadTheme, _log, _opts, _prep, _rezObj, _single, _verifyOutputs, _verifyTheme, addFreebieFormats, build, expand, extend, loadTheme, parsePath, prep, single, verifyOutputs, verifyTheme;
_ = require('underscore');
@ -67,67 +104,66 @@ Implementation of the 'build' verb for HackMyResume.
loadTheme = null;
/** An invokable resume generation command. */
BuildVerb = module.exports = Verb.extend({
module.exports = BuildVerb = class BuildVerb extends Verb {
/** Create a new build verb. */
init: function() {
return this._super('build', build);
constructor() {
super('build', _build);
}
});
};
/**
Given a source resume in FRESH or JRS format, a destination resume path, and a
theme file, generate 0..N resumes in the desired formats.
@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 opts Generation options.
*/
build = function(src, dst, opts) {
var ex, inv, isFRESH, mixed, newEx, orgFormat, problemSheets, rez, sheetObjects, sheets, tFolder, targets, theme, toFormat;
_build = function(src, dst, opts) {
var err, inv, isFRESH, mixed, newEx, orgFormat, problemSheets, results, rez, sheetObjects, sheets, tFolder, targets, theme, toFormat;
if (!src || !src.length) {
this.err(HMSTATUS.resumeNotFound, {
quit: true
});
return null;
}
prep(src, dst, opts);
_prep.call(this, src, dst, opts);
// Load input resumes as JSON...
sheetObjects = ResumeFactory.load(src, {
format: null,
objectify: false,
quit: true,
inner: {
sort: _opts.sort
sort: _opts.sort,
private: _opts.private
}
}, this);
// Explicit check for any resume loading errors...
problemSheets = _.filter(sheetObjects, function(so) {
return so.fluenterror;
});
if (problemSheets && problemSheets.length) {
problemSheets[0].quit = true;
problemSheets[0].quit = true; // can't go on
this.err(problemSheets[0].fluenterror, problemSheets[0]);
return null;
}
// Get the collection of raw JSON sheets
sheets = sheetObjects.map(function(r) {
return r.json;
});
// Load the theme...
theme = null;
this.stat(HMEVENT.beforeTheme, {
theme: _opts.theme
});
try {
tFolder = verifyTheme.call(this, _opts.theme);
theme = _opts.themeObj = loadTheme(tFolder);
addFreebieFormats(theme);
} catch (_error) {
ex = _error;
tFolder = _verifyTheme.call(this, _opts.theme);
if (tFolder.fluenterror) {
tFolder.quit = true;
this.err(tFolder.fluenterror, tFolder);
return;
}
theme = _opts.themeObj = _loadTheme(tFolder);
_addFreebieFormats(theme);
} catch (error) {
err = error;
newEx = {
fluenterror: HMSTATUS.themeLoad,
inner: ex,
inner: err,
attempted: _opts.theme,
quit: true
};
@ -137,7 +173,8 @@ Implementation of the 'build' verb for HackMyResume.
this.stat(HMEVENT.afterTheme, {
theme: theme
});
inv = verifyOutputs.call(this, dst, theme);
// Check for invalid outputs...
inv = _verifyOutputs.call(this, dst, theme);
if (inv && inv.length) {
this.err(HMSTATUS.invalidFormat, {
data: inv,
@ -146,6 +183,7 @@ Implementation of the 'build' verb for HackMyResume.
});
return null;
}
//# Merge input resumes, yielding a single source resume...
rez = null;
if (sheets.length > 1) {
isFRESH = !sheets[0].basics;
@ -182,30 +220,48 @@ Implementation of the 'build' verb for HackMyResume.
fmt: toFormat
});
}
// Announce the theme
this.stat(HMEVENT.applyTheme, {
r: rez,
theme: theme
});
_rezObj = new RTYPES[toFormat]().parseJSON(rez);
targets = expand(dst, theme);
// Load the resume into a FRESHResume or JRSResume object
_rezObj = new RTYPES[toFormat]().parseJSON(rez, {
private: _opts.private
});
targets = _expand(dst, theme);
// Run the transformation!
_.each(targets, function(t) {
return t.final = single.call(this, t, theme, targets);
var ref;
if (this.hasError() && opts.assert) {
return {};
}
t.final = _single.call(this, t, theme, targets);
if ((ref = t.final) != null ? ref.fluenterror : void 0) {
t.final.quit = opts.assert;
this.err(t.final.fluenterror, t.final);
}
}, this);
return {
results = {
sheet: _rezObj,
targets: targets,
processed: targets
};
if (this.hasError() && !opts.assert) {
this.reject(results);
} else if (!this.hasError()) {
this.resolve(results);
}
return results;
};
/**
Prepare for a BUILD run.
*/
prep = function(src, dst, opts) {
_prep = function(src, dst, opts) {
var that;
// Cherry-pick options //_opts = extend( true, _opts, opts );
_opts.theme = (opts.theme && opts.theme.toLowerCase().trim()) || 'modern';
_opts.prettify = opts.prettify === true;
_opts.private = opts.private === true;
_opts.noescape = opts.noescape === true;
_opts.css = opts.css;
_opts.pdf = opts.pdf;
_opts.wrap = opts.wrap || 60;
@ -215,25 +271,31 @@ Implementation of the 'build' verb for HackMyResume.
_opts.noTips = opts.noTips;
_opts.debug = opts.debug;
_opts.sort = opts.sort;
_opts.wkhtmltopdf = opts.wkhtmltopdf;
that = this;
// Set up callbacks for internal generators
_opts.onTransform = function(info) {
that.stat(HMEVENT.afterTransform, info);
};
_opts.beforeWrite = function(info) {
that.stat(HMEVENT.beforeWrite, info);
};
_opts.afterWrite = function(info) {
that.stat(HMEVENT.afterWrite, info);
};
// If two or more files are passed to the GENERATE command and the TO
// keyword is omitted, the last file specifies the output file.
(src.length > 1 && (!dst || !dst.length)) && dst.push(src.pop());
};
/**
Generate a single target resume such as "out/rez.html" or "out/rez.doc".
TODO: Refactor.
@param targInfo Information for the target resume.
@param theme A FRESHTheme or JRSTheme object.
*/
single = function(targInfo, theme, finished) {
_single = function(targInfo, theme, finished) {
var e, ex, f, fName, fType, outFolder, ret, theFormat;
ret = null;
ex = null;
f = targInfo.file;
try {
if (!targInfo.fmt) {
return;
return {};
}
fType = targInfo.fmt.outFormat;
fName = PATH.basename(f, '.' + fType);
@ -243,6 +305,8 @@ Implementation of the 'build' verb for HackMyResume.
file: PATH.relative(process.cwd(), f)
});
_opts.targets = finished;
// If targInfo.fmt.files exists, this format is backed by a document.
// Fluent/FRESH themes are handled here.
if (targInfo.fmt.files && targInfo.fmt.files.length) {
theFormat = _fmts.filter(function(fmt) {
return fmt.name === targInfo.fmt.outFormat;
@ -250,15 +314,17 @@ Implementation of the 'build' verb for HackMyResume.
MKDIRP.sync(PATH.dirname(f));
ret = theFormat.gen.generate(_rezObj, f, _opts);
} else {
// Otherwise this is an ad-hoc format (JSON, YML, or PNG) that every theme
// gets "for free".
theFormat = _fmts.filter(function(fmt) {
return fmt.name === targInfo.fmt.outFormat;
})[0];
outFolder = PATH.dirname(f);
MKDIRP.sync(outFolder);
MKDIRP.sync(outFolder); // Ensure dest folder exists;
ret = theFormat.gen.generate(_rezObj, f, _opts);
}
} catch (_error) {
e = _error;
} catch (error) {
e = error;
ex = e;
}
this.stat(HMEVENT.afterGenerate, {
@ -268,20 +334,18 @@ Implementation of the 'build' verb for HackMyResume.
});
if (ex) {
if (ex.fluenterror) {
this.err(ex.fluenterror, ex);
ret = ex;
} else {
this.err(HMSTATUS.generateError, {
ret = {
fluenterror: HMSTATUS.generateError,
inner: ex
});
};
}
}
return ret;
};
/** Ensure that user-specified outputs/targets are valid. */
verifyOutputs = function(targets, theme) {
_verifyOutputs = function(targets, theme) {
this.stat(HMEVENT.verifyOutputs, {
targets: targets,
theme: theme
@ -297,18 +361,9 @@ Implementation of the 'build' verb for HackMyResume.
});
};
/**
Reinforce the chosen theme with "freebie" formats provided by HackMyResume.
A "freebie" format is an output format such as JSON, YML, or PNG that can be
generated directly from the resume model or from one of the theme's declared
output formats. For example, the PNG format can be generated for any theme
that declares an HTML format; the theme doesn't have to provide an explicit
PNG template.
@param theTheme A FRESHTheme or JRSTheme object.
*/
addFreebieFormats = function(theTheme) {
_addFreebieFormats = function(theTheme) {
// Add freebie formats (JSON, YAML, PNG) every theme gets...
// Add HTML-driven PNG only if the theme has an HTML format.
theTheme.formats.json = theTheme.formats.json || {
freebie: true,
title: 'json',
@ -339,16 +394,10 @@ Implementation of the 'build' verb for HackMyResume.
}
};
/**
Expand output files. For example, "foo.all" should be expanded to
["foo.html", "foo.doc", "foo.pdf", "etc"].
@param dst An array of output files as specified by the user.
@param theTheme A FRESHTheme or JRSTheme object.
*/
expand = function(dst, theTheme) {
_expand = function(dst, theTheme) {
var destColl, targets;
// Set up the destination collection. It's either the array of files passed
// by the user or 'out/resume.all' if no targets were specified.
destColl = (dst && dst.length && dst) || [PATH.normalize('out/resume.all')];
targets = [];
destColl.forEach(function(t) {
@ -373,37 +422,63 @@ Implementation of the 'build' verb for HackMyResume.
return targets;
};
/**
Verify the specified theme name/path.
*/
verifyTheme = function(themeNameOrPath) {
var exists, tFolder;
tFolder = PATH.join(parsePath(require.resolve('fresh-themes')).dirname, '/themes/', themeNameOrPath);
exists = require('path-exists').sync;
if (!exists(tFolder)) {
_verifyTheme = function(themeNameOrPath) {
var exists, tFolder, themesObj;
// First, see if this is one of the predefined FRESH themes. There are only a
// handful of these, but they may change over time, so we need to query
// the official source of truth: the fresh-themes repository, which mounts the
// themes conveniently by name to the module object, and which is embedded
// locally inside the HackMyResume installation.
themesObj = require('fresh-themes');
if (_.has(themesObj.themes, themeNameOrPath)) {
tFolder = PATH.join(parsePath(require.resolve('fresh-themes')).dirname, '/themes/', themeNameOrPath);
} else {
// Otherwsie it's a path to an arbitrary FRESH or JRS theme sitting somewhere
// on the user's system (or, in the future, at a URI).
tFolder = PATH.resolve(themeNameOrPath);
if (!exists(tFolder)) {
this.err(HMSTATUS.themeNotFound, {
data: _opts.theme
});
}
}
return tFolder;
// In either case, make sure the theme folder exists
exists = require('path-exists').sync;
if (exists(tFolder)) {
return tFolder;
} else {
return {
fluenterror: HMSTATUS.themeNotFound,
data: _opts.theme
};
}
};
/**
Load the specified theme, which could be either a FRESH theme or a JSON Resume
theme.
*/
loadTheme = function(tFolder) {
var theTheme;
theTheme = _opts.theme.indexOf('jsonresume-theme-') > -1 ? new JRSTheme().open(tFolder) : new FRESHTheme().open(tFolder);
_loadTheme = function(tFolder) {
var exists, theTheme, themeJsonPath;
themeJsonPath = PATH.join(tFolder, 'theme.json'); // [^1]
exists = require('path-exists').sync;
// Create a FRESH or JRS theme object
theTheme = exists(themeJsonPath) ? new FRESHTheme().open(tFolder) : new JRSTheme().open(tFolder);
// Cache the theme object
_opts.themeObj = theTheme;
return theTheme;
};
// FOOTNOTES
// ------------------------------------------------------------------------------
// [^1] We don't know ahead of time whether this is a FRESH or JRS theme.
// However, all FRESH themes have a theme.json file, so we'll use that as a
// canary for now, as an interim solution.
// Unfortunately, with the exception of FRESH's theme.json, both FRESH and
// JRS themes are free-form and don't have a ton of reliable distinguishing
// marks, which makes a simple task like ad hoc theme detection harder than
// it should be to do cleanly.
// Another complicating factor is that it's possible for a theme to be BOTH.
// That is, a single set of theme files can serve as a FRESH theme -and- a
// JRS theme.
// TODO: The most robust way to deal with all these issues is with a strong
// theme validator. If a theme structure validates as a particular theme
// type, then for all intents and purposes, it IS a theme of that type.
}).call(this);
//# sourceMappingURL=build.js.map

186
dist/verbs/convert.js vendored
View File

@ -1,12 +1,13 @@
/**
Implementation of the 'convert' verb for HackMyResume.
@module verbs/convert
@license MIT. See LICENSE.md for details.
*/
(function() {
var ConvertVerb, HMEVENT, HMSTATUS, ResumeFactory, Verb, _, chalk, convert;
/**
Implementation of the 'convert' verb for HackMyResume.
@module verbs/convert
@license MIT. See LICENSE.md for details.
*/
/** Private workhorse method. Convert 0..N resumes between FRESH and JRS
formats. */
/** Private workhorse method. Convert a single resume. */
var ConvertVerb, HMEVENT, HMSTATUS, ResumeFactory, Verb, _, _convert, _convertOne, chalk;
ResumeFactory = require('../core/resume-factory');
@ -20,69 +21,146 @@ Implementation of the 'convert' verb for HackMyResume.
HMEVENT = require('../core/event-codes');
ConvertVerb = module.exports = Verb.extend({
init: function() {
return this._super('convert', convert);
module.exports = ConvertVerb = class ConvertVerb extends Verb {
constructor() {
super('convert', _convert);
}
});
};
/**
Convert between FRESH and JRS formats.
*/
convert = function(srcs, dst, opts) {
_convert = function(srcs, dst, opts) {
var fmtUp, results, targetVer;
if (!srcs || !srcs.length) {
throw {
fluenterror: 6,
this.err(HMSTATUS.resumeNotFound, {
quit: true
};
});
return null;
}
if (!dst || !dst.length) {
if (srcs.length === 1) {
throw {
fluenterror: HMSTATUS.inputOutputParity,
this.err(HMSTATUS.inputOutputParity, {
quit: true
};
});
} else if (srcs.length === 2) {
dst = dst || [];
dst.push(srcs.pop());
} else {
throw {
fluenterror: HMSTATUS.inputOutputParity,
this.err(HMSTATUS.inputOutputParity, {
quit: true
});
}
}
// Different number of source and dest resumes? Error out.
if (srcs && dst && srcs.length && dst.length && srcs.length !== dst.length) {
this.err(HMSTATUS.inputOutputParity, {
quit: true
});
}
// Validate the destination format (if specified)
targetVer = null;
if (opts.format) {
fmtUp = opts.format.trim().toUpperCase();
if (!_.contains(['FRESH', 'FRESCA', 'JRS', 'JRS@1', 'JRS@edge'], fmtUp)) {
this.err(HMSTATUS.invalidSchemaVersion, {
data: opts.format.trim(),
quit: true
});
}
}
// freshVerRegex = require '../utils/fresh-version-regex'
// matches = fmtUp.match freshVerRegex()
// # null
// # [ 'JRS@1.0', 'JRS', '1.0', index: 0, input: 'FRESH' ]
// # [ 'FRESH', 'FRESH', undefined, index: 0, input: 'FRESH' ]
// if not matches
// @err HMSTATUS.invalidSchemaVersion, data: opts.format.trim(), quit: true
// targetSchema = matches[1]
// targetVer = matches[2] || '1'
// If any errors have occurred this early, we're done.
if (this.hasError()) {
this.reject(this.errorCode);
return null;
}
// Map each source resume to the converted destination resume
results = _.map(srcs, function(src, idx) {
var r;
// Convert each resume in turn
r = _convertOne.call(this, src, dst, idx, fmtUp);
// Handle conversion errors
if (r.fluenterror) {
r.quit = opts.assert;
this.err(r.fluenterror, r);
}
return r;
}, this);
if (this.hasError() && !opts.assert) {
this.reject(results);
} else if (!this.hasError()) {
this.resolve(results);
}
return results;
};
_convertOne = function(src, dst, idx, targetSchema) {
var err, rez, rinfo, srcFmt, targetFormat;
// Load the resume
rinfo = ResumeFactory.loadOne(src, {
format: null,
objectify: true,
inner: {
privatize: false
}
});
// If a load error occurs, report it and move on to the next file (if any)
if (rinfo.fluenterror) {
this.stat(HMEVENT.beforeConvert, {
srcFile: src, //rinfo.file
srcFmt: '???',
dstFile: dst[idx],
dstFmt: '???',
error: true
});
//@err rinfo.fluenterror, rinfo
return rinfo;
}
// Determine the resume's SOURCE format
// TODO: replace with detector component
rez = rinfo.rez;
srcFmt = '';
if (rez.meta && rez.meta.format) { //&& rez.meta.format.substr(0, 5).toUpperCase() == 'FRESH'
srcFmt = 'FRESH';
} else if (rez.basics) {
srcFmt = 'JRS';
} else {
rinfo.fluenterror = HMSTATUS.unknownSchema;
return rinfo;
}
// Determine the TARGET format for the conversion
targetFormat = targetSchema || (srcFmt === 'JRS' ? 'FRESH' : 'JRS');
// Fire the beforeConvert event
this.stat(HMEVENT.beforeConvert, {
srcFile: rinfo.file,
srcFmt: srcFmt,
dstFile: dst[idx],
dstFmt: targetFormat
});
try {
// Save it to the destination format
rez.saveAs(dst[idx], targetFormat);
} catch (error) {
err = error;
if (err.badVer) {
return {
fluenterror: HMSTATUS.invalidSchemaVersion,
quit: true,
data: err.badVer
};
}
}
if (srcs && dst && srcs.length && dst.length && srcs.length !== dst.length) {
throw {
fluenterror: HMSTATUS.inputOutputParity({
quit: true
})
};
}
_.each(srcs, function(src, idx) {
var rinfo, s, srcFmt, targetFormat;
rinfo = ResumeFactory.loadOne(src, {
format: null,
objectify: true,
"throw": false
});
if (rinfo.fluenterror) {
this.err(rinfo.fluenterror, rinfo);
return;
}
s = rinfo.rez;
srcFmt = ((s.basics && s.basics.imp) || s.imp).orgFormat === 'JRS' ? 'JRS' : 'FRESH';
targetFormat = srcFmt === 'JRS' ? 'FRESH' : 'JRS';
this.stat(HMEVENT.beforeConvert, {
srcFile: rinfo.file,
srcFmt: srcFmt,
dstFile: dst[idx],
dstFmt: targetFormat
});
s.saveAs(dst[idx], targetFormat);
}, this);
return rez;
};
}).call(this);
//# sourceMappingURL=convert.js.map

86
dist/verbs/create.js vendored
View File

@ -1,12 +1,12 @@
/**
Implementation of the 'create' verb for HackMyResume.
@module verbs/create
@license MIT. See LICENSE.md for details.
*/
(function() {
var CreateVerb, HMEVENT, HMSTATUS, MKDIRP, PATH, Verb, _, chalk, create;
/**
Implementation of the 'create' verb for HackMyResume.
@module verbs/create
@license MIT. See LICENSE.md for details.
*/
/** Create a new empty resume in either FRESH or JRS format. */
/** Create a single new resume */
var CreateVerb, HMEVENT, HMSTATUS, MKDIRP, PATH, Verb, _, _create, _createOne, chalk;
MKDIRP = require('mkdirp');
@ -22,39 +22,71 @@ Implementation of the 'create' verb for HackMyResume.
HMEVENT = require('../core/event-codes');
CreateVerb = module.exports = Verb.extend({
init: function() {
return this._super('new', create);
module.exports = CreateVerb = class CreateVerb extends Verb {
constructor() {
super('new', _create);
}
});
};
/**
Create a new empty resume in either FRESH or JRS format.
*/
create = function(src, dst, opts) {
_create = function(src, dst, opts) {
var results;
if (!src || !src.length) {
throw {
fluenterror: HMSTATUS.createNameMissing,
this.err(HMSTATUS.createNameMissing, {
quit: true
};
});
return null;
}
_.each(src, function(t) {
var RezClass, safeFmt;
results = _.map(src, function(t) {
var r;
if (opts.assert && this.hasError()) {
return {};
}
r = _createOne.call(this, t, opts);
if (r.fluenterror) {
r.quit = opts.assert;
this.err(r.fluenterror, r);
}
return r;
}, this);
if (this.hasError() && !opts.assert) {
this.reject(this.errorCode);
} else if (!this.hasError()) {
this.resolve(results);
}
return results;
};
_createOne = function(t, opts) {
var RezClass, err, newRez, ret, safeFmt;
try {
ret = null;
safeFmt = opts.format.toUpperCase();
this.stat(HMEVENT.beforeCreate, {
fmt: safeFmt,
file: t
});
MKDIRP.sync(PATH.dirname(t));
MKDIRP.sync(PATH.dirname(t)); // Ensure dest folder exists;
RezClass = require('../core/' + safeFmt.toLowerCase() + '-resume');
RezClass["default"]().save(t);
return this.stat(HMEVENT.afterCreate, {
newRez = RezClass.default();
newRez.save(t);
ret = newRez;
} catch (error) {
err = error;
ret = {
fluenterror: HMSTATUS.createError,
inner: err
};
} finally {
this.stat(HMEVENT.afterCreate, {
fmt: safeFmt,
file: t
file: t,
isError: ret.fluenterror
});
}, this);
return ret;
}
};
}).call(this);
//# sourceMappingURL=create.js.map

116
dist/verbs/peek.js vendored
View File

@ -1,12 +1,12 @@
/**
Implementation of the 'peek' verb for HackMyResume.
@module verbs/peek
@license MIT. See LICENSE.md for details.
*/
(function() {
var HMEVENT, HMSTATUS, PeekVerb, Verb, _, __, peek, safeLoadJSON;
/**
Implementation of the 'peek' verb for HackMyResume.
@module verbs/peek
@license MIT. See LICENSE.md for details.
*/
/** Peek at a resume, resume section, or resume field. */
/** Peek at a single resume, resume section, or resume field. */
var HMEVENT, HMSTATUS, PeekVerb, Verb, _, __, _peek, _peekOne, safeLoadJSON;
Verb = require('../verbs/verb');
@ -20,51 +20,81 @@ Implementation of the 'peek' verb for HackMyResume.
HMEVENT = require('../core/event-codes');
PeekVerb = module.exports = Verb.extend({
init: function() {
return this._super('peek', peek);
module.exports = PeekVerb = class PeekVerb extends Verb {
constructor() {
super('peek', _peek);
}
});
};
/** Peek at a resume, resume section, or resume field. */
peek = function(src, dst, opts) {
var objPath;
_peek = function(src, dst, opts) {
var objPath, results;
if (!src || !src.length) {
({
"throw": {
fluenterror: HMSTATUS.resumeNotFound
}
this.err(HMSTATUS.resumeNotFound, {
quit: true
});
return null;
}
objPath = (dst && dst[0]) || '';
_.each(src, function(t) {
var errCode, obj, tgt;
this.stat(HMEVENT.beforePeek, {
file: t,
target: objPath
});
obj = safeLoadJSON(t);
tgt = null;
if (!obj.ex) {
tgt = objPath ? __.get(obj.json, objPath) : obj.json;
results = _.map(src, function(t) {
var tgt;
if (opts.assert && this.hasError()) {
return {};
}
this.stat(HMEVENT.afterPeek, {
file: t,
requested: objPath,
target: tgt,
error: obj.ex
});
if (obj.ex) {
errCode = obj.ex.operation === 'parse' ? HMSTATUS.parseError : HMSTATUS.readError;
if (errCode === HMSTATUS.readError) {
obj.ex.quiet = true;
}
this.setError(errCode, obj.ex);
return this.err(errCode, obj.ex);
tgt = _peekOne.call(this, t, objPath);
if (tgt.error) {
this.setError(tgt.error.fluenterror, tgt.error);
}
//tgt.error.quit = opts.assert
//@err tgt.error.fluenterror, tgt.error
return tgt;
}, this);
if (this.hasError() && !opts.assert) {
this.reject(this.errorCode);
} else if (!this.hasError()) {
this.resolve(results);
}
return results;
};
_peekOne = function(t, objPath) {
var errCode, obj, pkgError, tgt;
this.stat(HMEVENT.beforePeek, {
file: t,
target: objPath
});
// Load the input file JSON 1st
obj = safeLoadJSON(t);
// Fetch the requested object path (or the entire file)
tgt = null;
if (!obj.ex) {
tgt = objPath ? __.get(obj.json, objPath) : obj.json;
}
//# safeLoadJSON can only return a READ error or a PARSE error
pkgError = null;
if (obj.ex) {
errCode = obj.ex.op === 'parse' ? HMSTATUS.parseError : HMSTATUS.readError;
if (errCode === HMSTATUS.readError) {
obj.ex.quiet = true;
}
pkgError = {
fluenterror: errCode,
inner: obj.ex
};
}
// Fire the 'afterPeek' event with collected info
this.stat(HMEVENT.afterPeek, {
file: t,
requested: objPath,
target: obj.ex ? void 0 : tgt,
error: pkgError
});
return {
val: obj.ex ? void 0 : tgt,
error: pkgError
};
};
}).call(this);
//# sourceMappingURL=peek.js.map

150
dist/verbs/validate.js vendored
View File

@ -1,12 +1,21 @@
/**
Implementation of the 'validate' verb for HackMyResume.
@module verbs/validate
@license MIT. See LICENSE.md for details.
*/
(function() {
var FS, HMEVENT, HMSTATUS, ResumeFactory, SyntaxErrorEx, ValidateVerb, Verb, _, chalk, safeLoadJSON, validate;
/**
Implementation of the 'validate' verb for HackMyResume.
@module verbs/validate
@license MIT. See LICENSE.md for details.
*/
/**
Validate a single resume.
@returns {
file: <fileName>,
isValid: <validFlag>,
status: <validationStatus>,
violations: <validationErrors>,
schema: <schemaType>,
error: <errorObject>
}
*/
var FS, HMEVENT, HMSTATUS, ResumeFactory, SyntaxErrorEx, ValidateVerb, Verb, _, _validate, _validateOne, chalk, safeLoadJSON;
FS = require('fs');
@ -26,77 +35,98 @@ Implementation of the 'validate' verb for HackMyResume.
safeLoadJSON = require('../utils/safe-json-loader');
/** An invokable resume validation command. */
ValidateVerb = module.exports = Verb.extend({
init: function() {
return this._super('validate', validate);
module.exports = ValidateVerb = class ValidateVerb extends Verb {
constructor() {
super('validate', _validate);
}
});
};
/** Validate 1 to N resumes in FRESH or JSON Resume format. */
validate = function(sources, unused, opts) {
var schemas, validator;
// Validate 1 to N resumes in FRESH or JSON Resume format.
_validate = function(sources, unused, opts) {
var results, schemas, validator;
if (!sources || !sources.length) {
throw {
fluenterror: HMSTATUS.resumeNotFoundAlt,
this.err(HMSTATUS.resumeNotFoundAlt, {
quit: true
};
});
return null;
}
validator = require('is-my-json-valid');
schemas = {
fresh: require('fresca'),
fresh: require('fresh-resume-schema'),
jars: require('../core/resume.json')
};
return _.map(sources, function(t) {
var errCode, errors, fmt, json, obj, ret;
ret = {
file: t,
isValid: false
};
results = _.map(sources, function(t) {
var r;
r = _validateOne.call(this, t, validator, schemas, opts);
if (r.error) {
this.err(r.error.fluenterror, r.error);
}
return r;
}, this);
if (this.hasError() && !opts.assert) {
this.reject(this.errorCode);
} else if (!this.hasError()) {
this.resolve(results);
}
return results;
};
_validateOne = function(t, validator, schemas, opts) {
var err, errCode, obj, ret, validate;
ret = {
file: t,
isValid: false,
status: 'unknown',
schema: '-----'
};
try {
// Read and parse the resume JSON. Won't throw.
obj = safeLoadJSON(t);
if (!obj.ex) {
json = obj.json;
fmt = json.basics ? 'jrs' : 'fresh';
errors = [];
try {
validate = validator(schemas[fmt], {
formats: {
date: /^\d{4}(?:-(?:0[0-9]{1}|1[0-2]{1})(?:-[0-9]{2})?)?$/
}
});
ret.isValid = validate(json);
if (!ret.isValid) {
errors = validate.errors;
if (obj.json.basics) {
ret.schema = 'jars';
} else {
ret.schema = 'fresh';
}
validate = validator(schemas[ret.schema], {
formats: {
date: /^\d{4}(?:-(?:0[0-9]{1}|1[0-2]{1})(?:-[0-9]{2})?)?$/
}
} catch (_error) {
ret.ex = _error;
});
ret.isValid = validate(obj.json);
ret.status = ret.isValid ? 'valid' : 'invalid';
if (!ret.isValid) {
ret.violations = validate.errors;
}
} else {
errCode = obj.ex.operation === 'parse' ? HMSTATUS.parseError : HMSTATUS.readError;
if (errCode === HMSTATUS.readError) {
obj.ex.quiet = true;
// If failure, package JSON read/parse errors
if (obj.ex.op === 'parse') {
errCode = HMSTATUS.parseError;
ret.status = 'broken';
} else {
errCode = HMSTATUS.readError;
ret.status = 'missing';
}
this.setError(errCode, obj.ex);
this.err(errCode, obj.ex);
}
this.stat(HMEVENT.afterValidate, {
file: t,
isValid: ret.isValid,
fmt: fmt != null ? fmt.replace('jars', 'JSON Resume') : void 0,
errors: errors
});
if (opts.assert && !ret.isValid) {
throw {
fluenterror: HMSTATUS.invalid,
shouldExit: true
ret.error = {
fluenterror: errCode,
inner: obj.ex.inner,
quiet: errCode === HMSTATUS.readError
};
}
return ret;
}, this);
} catch (error) {
err = error;
// Package any unexpected exceptions
ret.error = {
fluenterror: HMSTATUS.validateError,
inner: err
};
}
this.stat(HMEVENT.afterValidate, ret);
return ret;
};
}).call(this);
//# sourceMappingURL=validate.js.map

87
dist/verbs/verb.js vendored
View File

@ -1,83 +1,100 @@
/**
Definition of the Verb class.
@module verbs/verb
@license MIT. See LICENSE.md for details.
*/
(function() {
var Class, EVENTS, HMEVENT, Verb;
Class = require('../utils/class');
/**
Definition of the Verb class.
@module verbs/verb
@license MIT. See LICENSE.md for details.
*/
var EVENTS, HMEVENT, Promise, Verb;
EVENTS = require('events');
HMEVENT = require('../core/event-codes');
Promise = require('pinkie-promise');
/**
An instantiation of a HackMyResume command.
An abstract invokable verb.
Provides base class functionality for verbs. Provide common services such as
error handling, event management, and promise support.
@class Verb
*/
Verb = module.exports = Class.extend({
*/
module.exports = Verb = class Verb {
/** Constructor. Automatically called at creation. */
init: function(moniker, workhorse) {
constructor(moniker, workhorse) {
this.moniker = moniker;
this.emitter = new EVENTS.EventEmitter();
this.workhorse = workhorse;
},
this.emitter = new EVENTS.EventEmitter();
return;
}
/** Invoke the command. */
invoke: function() {
var ret;
invoke() {
var argsArray, that;
// Sent the 'begin' notification for this verb
this.stat(HMEVENT.begin, {
cmd: this.moniker
});
ret = this.workhorse.apply(this, arguments);
this.stat(HMEVENT.end);
return ret;
},
// Prepare command arguments
argsArray = Array.prototype.slice.call(arguments);
// Create a promise for this verb instance
that = this;
return this.promise = new Promise(function(res, rej) {
that.resolve = res;
that.reject = rej;
that.workhorse.apply(that, argsArray);
});
}
/** Forward subscriptions to the event emitter. */
on: function() {
on() {
return this.emitter.on.apply(this.emitter, arguments);
},
}
/** Fire an arbitrary event, scoped to "hmr:". */
fire: function(evtName, payload) {
fire(evtName, payload) {
payload = payload || {};
payload.cmd = this.moniker;
this.emitter.emit('hmr:' + evtName, payload);
return true;
},
}
/** Handle an error condition. */
err: function(errorCode, payload, hot) {
err(errorCode, payload, hot) {
payload = payload || {};
payload.sub = payload.fluenterror = errorCode;
payload["throw"] = hot;
payload.throw = hot;
this.setError(errorCode, payload);
if (payload.quit) {
this.reject(errorCode);
}
this.fire('error', payload);
if (hot) {
throw payload;
}
return true;
},
}
/** Fire the 'hmr:status' error event. */
stat: function(subEvent, payload) {
stat(subEvent, payload) {
payload = payload || {};
payload.sub = subEvent;
this.fire('status', payload);
return true;
},
}
/** Has an error occurred during this verb invocation? */
hasError() {
return this.errorCode || this.errorObj;
}
/** Associate error info with the invocation. */
setError: function(code, obj) {
setError(code, obj) {
this.errorCode = code;
this.errorObj = obj;
}
});
};
}).call(this);
//# sourceMappingURL=verb.js.map

2800
npm-shrinkwrap.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,13 +1,13 @@
{
"name": "hackmyresume",
"version": "1.7.4",
"version": "1.9.0-beta",
"description": "Generate polished résumés and CVs in HTML, Markdown, LaTeX, MS Word, PDF, plain text, JSON, XML, YAML, smoke signal, and carrier pigeon.",
"repository": {
"type": "git",
"url": "https://github.com/hacksalot/HackMyResume.git"
},
"scripts": {
"test": "grunt clean:test && mocha",
"test": "grunt clean:test && mocha --exit",
"grunt": "grunt"
},
"keywords": [
@ -31,6 +31,7 @@
"author": "hacksalot <hacksalot@indevious.com> (https://github.com/hacksalot)",
"contributors": [
"aruberto (https://github.com/aruberto)",
"daniele-rapagnani (https://github.com/daniele-rapagnani)",
"jjanusch (https://github.com/driftdev)",
"robertmain (https://github.com/robertmain)",
"tomheon (https://github.com/tomheon)",
@ -45,31 +46,36 @@
"bin": {
"hackmyresume": "dist/cli/index.js"
},
"main": "src/index.js",
"main": "dist/index.js",
"homepage": "https://github.com/hacksalot/HackMyResume",
"dependencies": {
"chalk": "^1.1.1",
"chalk": "^2.3.1",
"commander": "^2.9.0",
"copy": "^0.1.3",
"copy": "^0.3.1",
"escape-latex": "^1.0.0",
"extend": "^3.0.0",
"fresca": "~0.6.0",
"fresh-jrs-converter": "^0.2.0",
"fresh-resume-starter": "^0.2.2",
"fresh-themes": "^0.14.1-beta",
"fs-extra": "^0.26.4",
"fresh-jrs-converter": "^1.0.0",
"fresh-resume-schema": "^1.0.0-beta",
"fresh-resume-starter": "^0.3.1",
"fresh-resume-validator": "^0.2.0",
"fresh-themes": "^0.17.0-beta",
"fs-extra": "^5.0.0",
"glob": "^7.1.2",
"handlebars": "^4.0.5",
"html": "0.0.10",
"html": "^1.0.0",
"is-my-json-valid": "^2.12.4",
"json-lint": "^0.1.0",
"lodash": "^3.10.1",
"jsonlint": "^1.6.2",
"lodash": "^4.17.5",
"marked": "^0.3.5",
"mkdirp": "^0.5.1",
"moment": "^2.11.1",
"parse-filepath": "^0.6.3",
"path-exists": "^2.1.0",
"parse-filepath": "^1.0.2",
"path-exists": "^3.0.0",
"pinkie-promise": "^2.0.0",
"printf": "^0.2.3",
"recursive-readdir-sync": "^1.0.6",
"simple-html-tokenizer": "^0.2.1",
"simple-html-tokenizer": "^0.4.3",
"slash": "^1.0.0",
"string-padding": "^1.0.2",
"string.prototype.endswith": "^0.2.0",
@ -78,26 +84,26 @@
"underscore": "^1.8.3",
"word-wrap": "^1.1.0",
"xml-escape": "^1.0.0",
"yamljs": "^0.2.4"
"yamljs": "^0.3.0"
},
"devDependencies": {
"chai": "*",
"fresh-test-resumes": "^0.6.0",
"chai-as-promised": "^7.1.1",
"dir-compare": "^1.4.0",
"fresh-test-resumes": "^0.9.2",
"fresh-test-themes": "^0.2.0",
"fresh-theme-underscore": "^0.1.1",
"grunt": "*",
"grunt-cli": "^0.1.13",
"grunt-contrib-clean": "^0.7.0",
"grunt-contrib-coffee": "^0.13.0",
"grunt-contrib-copy": "^0.8.2",
"grunt-contrib-jshint": "^0.11.3",
"grunt-contrib-yuidoc": "^0.10.0",
"grunt-jsdoc": "^1.1.0",
"grunt-contrib-clean": "^1.1.0",
"grunt-contrib-coffee": "^2.0.0",
"grunt-contrib-copy": "^1.0.0",
"grunt-eslint": "^20.1.0",
"grunt-simple-mocha": "*",
"jsonresume-theme-boilerplate": "^0.1.2",
"jsonresume-theme-classy": "^1.0.9",
"jsonresume-theme-modern": "0.0.18",
"jsonresume-theme-sceptile": "^1.0.5",
"mocha": "*",
"resample": "fluentdesk/resample",
"stripcolorcodes": "^0.1.0"
}
}

View File

@ -7,26 +7,21 @@ Error-handling routines for HackMyResume.
HMSTATUS = require '../core/status-codes'
PKG = require '../../package.json'
FS = require 'fs'
FCMD = require '../index'
PATH = require 'path'
WRAP = require 'word-wrap'
M2C = require '../utils/md2chalk'
chalk = require 'chalk'
extend = require 'extend'
YAML = require 'yamljs'
printf = require 'printf'
SyntaxErrorEx = require '../utils/syntax-error-ex'
require 'string.prototype.startswith'
###*
Error handler for HackMyResume. All errors are handled here.
@class ErrorHandler
###
ErrorHandler = module.exports =
###* Error handler for HackMyResume. All errors are handled here.
@class ErrorHandler ###
module.exports =
init: ( debug, assert, silent ) ->
@debug = debug
@ -38,7 +33,7 @@ ErrorHandler = module.exports =
err: ( ex, shouldExit ) ->
# Short-circuit logging output if --silent is on
o = if this.silent then () -> else _defaultLog
o = if @silent then () -> else _defaultLog
# Special case; can probably be removed.
throw ex if ex.pass
@ -51,7 +46,7 @@ ErrorHandler = module.exports =
# Output the error message
objError = assembleError.call @, ex
o( this[ 'format_' + objError.etype ]( objError.msg ))
o( @[ 'format_' + objError.etype ]( objError.msg ))
# Output the stack (sometimes)
if objError.withStack
@ -59,20 +54,20 @@ ErrorHandler = module.exports =
stack && o( chalk.gray( stack ) );
# Quit if necessary
if ex.quit || objError.quit
if shouldExit or ex.exit
if @debug
o chalk.cyan('Exiting with error code ' + ex.fluenterror.toString())
if this.assert
if @assert
ex.pass = true
throw ex
process.exit ex.fluenterror
# Handle raw exceptions
else
o( ex )
o ex
stackTrace = ex.stack || (ex.inner && ex.inner.stack)
if stackTrace && this.debug
o( M2C(ex.stack || ex.inner.stack, 'gray') )
o M2C(ex.stack || ex.inner.stack, 'gray')
@ -88,8 +83,7 @@ ErrorHandler = module.exports =
format_custom: ( msg ) -> msg
_defaultLog = () -> console.log.apply console.log, arguments
_defaultLog = () -> console.log.apply console.log, arguments # eslint-disable-line no-console
@ -112,17 +106,19 @@ assembleError = ( ex ) ->
quit = false
when HMSTATUS.resumeNotFound
msg = M2C( this.msgs.resumeNotFound.msg, 'yellow' );
#msg = M2C( this.msgs.resumeNotFound.msg, 'yellow' );
msg += M2C(FS.readFileSync(
PATH.resolve(__dirname, 'help/' + ex.verb + '.txt'), 'utf8' ), 'white', 'yellow')
when HMSTATUS.missingCommand
msg = M2C( this.msgs.missingCommand.msg + " (", 'yellow');
msg += Object.keys( FCMD.verbs ).map( (v, idx, ar) ->
return ( if idx == ar.length - 1 then chalk.yellow('or ') else '') +
chalk.yellow.bold(v.toUpperCase());
).join( chalk.yellow(', ')) + chalk.yellow(").\n\n");
# msg = M2C( this.msgs.missingCommand.msg + " (", 'yellow');
# msg += Object.keys( FCMD.verbs ).map( (v, idx, ar) ->
# return ( if idx == ar.length - 1 then chalk.yellow('or ') else '') +
# chalk.yellow.bold(v.toUpperCase());
# ).join( chalk.yellow(', ')) + chalk.yellow(").\n\n");
msg += chalk.gray(FS.readFileSync(
PATH.resolve(__dirname, '../cli/use.txt'), 'utf8' ))
msg += M2C(FS.readFileSync(
PATH.resolve(__dirname, 'help/use.txt'), 'utf8' ), 'white', 'yellow')
when HMSTATUS.invalidCommand
msg = printf( M2C( this.msgs.invalidCommand.msg, 'yellow'), ex.attempted )
@ -139,7 +135,6 @@ assembleError = ( ex ) ->
when HMSTATUS.pdfGeneration
msg = M2C( this.msgs.pdfGeneration.msg, 'bold' )
msg += chalk.red('\n' + ex.inner) if ex.inner
withStack = true
quit = false
etype = 'error'
@ -181,6 +176,7 @@ assembleError = ( ex ) ->
when HMSTATUS.readError
if !ex.quiet
# eslint-disable-next-line no-console
console.error(printf( M2C(this.msgs.readError.msg, 'red'), ex.file))
msg = ex.inner.toString()
etype = 'error'
@ -205,16 +201,67 @@ assembleError = ( ex ) ->
etype = 'custom'
when HMSTATUS.parseError
if SyntaxErrorEx.is( ex.inner )
if SyntaxErrorEx.is ex.inner
# eslint-disable-next-line no-console
console.error printf( M2C(this.msgs.readError.msg, 'red'), ex.file )
se = new SyntaxErrorEx ex, ex.raw
msg = printf M2C( this.msgs.parseError.msg, 'red' ), se.line, se.col
else if ex.inner && ex.inner.line != undefined && ex.inner.col != undefined
msg = printf( M2C( this.msgs.parseError.msg, 'red' ), ex.inner.line, ex.inner.col)
if se.line? and se.col?
msg = printf M2C( this.msgs.parseError.msg[0], 'red' ), se.line, se.col
else if se.line?
msg = printf M2C( this.msgs.parseError.msg[1], 'red' ), se.line
else
msg = M2C @msgs.parseError.msg[2], 'red'
else if ex.inner && ex.inner.line? && ex.inner.col?
msg = printf( M2C( this.msgs.parseError.msg[0], 'red' ), ex.inner.line, ex.inner.col)
else
msg = ex
etype = 'error'
when HMSTATUS.createError
# inner.code could be EPERM, EACCES, etc
msg = printf M2C( this.msgs.createError.msg ), ex.inner.path
etype = 'error'
when HMSTATUS.validateError
msg = printf M2C( @msgs.validateError.msg ), ex.inner.toString()
etype = 'error'
when HMSTATUS.invalidOptionsFile
msg = M2C @msgs.invalidOptionsFile.msg[0]
if SyntaxErrorEx.is ex.inner
# eslint-disable-next-line no-console
console.error printf( M2C(this.msgs.readError.msg, 'red'), ex.file )
se = new SyntaxErrorEx ex, ex.raw
if se.line? and se.col?
msg += printf M2C( this.msgs.parseError.msg[0], 'red' ), se.line, se.col
else if se.line?
msg += printf M2C( this.msgs.parseError.msg[1], 'red' ), se.line
else
msg += M2C @msgs.parseError.msg[2], 'red'
else if ex.inner && ex.inner.line? && ex.inner.col?
msg += printf( M2C( this.msgs.parseError.msg[0], 'red' ), ex.inner.line, ex.inner.col)
else
msg += ex
msg += @msgs.invalidOptionsFile.msg[1]
etype = 'error'
when HMSTATUS.optionsFileNotFound
msg = M2C( @msgs.optionsFileNotFound.msg )
etype = 'error'
when HMSTATUS.unknownSchema
msg = M2C( @msgs.unknownSchema.msg[0] )
#msg += "\n" + M2C( @msgs.unknownSchema.msg[1], 'yellow' )
etype = 'error'
when HMSTATUS.themeHelperLoad
msg = printf M2C( @msgs.themeHelperLoad.msg ), ex.glob
etype = 'error'
when HMSTATUS.invalidSchemaVersion
msg = printf M2C( @msgs.invalidSchemaVersion.msg ), ex.data
etype = 'error'
msg: msg # The error message to display
withStack: withStack # Whether to include the stack
quit: quit

25
src/cli/help/analyze.txt Normal file
View File

@ -0,0 +1,25 @@
**analyze** | Analyze a resume for statistical insight
Usage:
**hackmyresume ANALYZE <resume>**
The ANALYZE command evaluates the specified resume(s) for
coverage, duration, gaps, keywords, and other metrics.
This command can be run against multiple resumes. Each
will be analyzed in turn.
Parameters:
**<resume>**
Path to a FRESH or JRS resume. Multiple resumes can be
specified, separated by spaces.
hackmyresume ANALYZE resume.json
hackmyresume ANALYZE r1.json r2.json r3.json
Options:
**None.**

69
src/cli/help/build.txt Normal file
View File

@ -0,0 +1,69 @@
**build** | Generate themed resumes in multiple formats
Usage:
**hackmyresume BUILD <resume> TO <target> [--theme]**
**[--pdf] [--no-escape] [--private]**
The BUILD command generates themed resumes and CVs in
multiple formats. Use it to create outbound resumes in
specific formats such HTML, MS Word, and PDF.
Parameters:
**<resume>**
Path to a FRESH or JRS resume (*.json) containing your
resume data. Multiple resumes may be specified.
If multiple resumes are specified, they will be merged
into a single resume prior to transformation.
**<target>**
Path to the desired output resume. Multiple resumes
may be specified. The file extension will determine
the format.
.all Generate all supported formats
.html HTML 5
.doc MS Word
.pdf Adobe Acrobat PDF
.txt plain text
.md Markdown
.png PNG Image
.latex LaTeX
Note: not all formats are supported by all themes!
Check the theme's documentation for details or use
the .all extension to build all available formats.
Options:
**--theme -t <theme-name-or-path>**
Path to a FRESH or JSON Resume theme OR the name of a
built-in theme. Valid theme names are 'modern',
'positive', 'compact', 'awesome', and 'basis'.
**--pdf -p <engine>**
Specify the PDF engine to use. Legal values are
'none', 'wkhtmltopdf', 'phantom', or 'weasyprint'.
**--no-escape**
Disable escaping / encoding of resume data during
resume generation. Handlebars themes only.
**--private**
Include resume fields marked as private.
Notes:
The BUILD command can be run against multiple source as well
as multiple target resumes. If multiple source resumes are
provided, they will be merged into a single source resume
before generation. If multiple output resumes are provided,
each will be generated in turn.

33
src/cli/help/convert.txt Normal file
View File

@ -0,0 +1,33 @@
**convert** | Convert resumes between FRESH and JRS formats
Usage:
**hackmyresume CONVERT <resume> TO <target> [--format]**
The CONVERT command converts one or more resume documents
between the FRESH Resume Schema and JSON Resume formats.
Parameters:
**<resume>**
Path to a FRESH or JRS resume. Multiple resumes can be
specified.
**<targets>**
The path of the converted resume. Multiple resumes can
be specified, one per provided input resume.
Options:
**--format -f <fmt>**
The desired format for the new resume(s). Valid values
are 'FRESH', 'JRS', or, to target the latest edge
version of the JSON Resume Schema, 'JRS@1'.
If this parameter is omitted, the destination format
will be inferred from the source resume's format. If
the source format is FRESH, the destination format
will be JSON Resume, and vice-versa.

23
src/cli/help/help.txt Normal file
View File

@ -0,0 +1,23 @@
**help** | View help on a specific HackMyResume command
Usage:
**hackmyresume HELP [<command>]**
The HELP command displays help information for a specific
HackMyResume command, including the HELP command itself.
Parameters:
**<command>**
The HackMyResume command to view help information for.
Must be BUILD, NEW, CONVERT, ANALYZE, VALIDATE, PEEK,
or HELP.
hackmyresume help convert
hackmyresume help help
Options:
**None.**

29
src/cli/help/new.txt Normal file
View File

@ -0,0 +1,29 @@
**new** | Create a new FRESH or JRS resume document
Usage:
**hackmyresume NEW <fileName> [--format]**
The NEW command generates a new resume document in FRESH
or JSON Resume format. This document can serve as an
official source of truth for your resume and career data
as well an input to tools like HackMyResume.
Parameters:
**<fileName>**
The filename (relative or absolute path) of the resume
to be created. Multiple resume paths can be specified,
and each will be created in turn.
hackmyresume NEW resume.json
hackmyresume NEW r1.json foo/r2.json ../r3.json
Options:
**--format -f <fmt>**
The desired format for the new resume(s). Valid values
are 'FRESH', 'JRS', or, to target the latest edge
version of the JSON Resume Schema, 'JRS@1'.

31
src/cli/help/peek.txt Normal file
View File

@ -0,0 +1,31 @@
**peek** | View portions of a resume from the command line
Usage:
**hackmyresume PEEK <resume> <at>**
The PEEK command displays a specific piece or part of the
resume without requiring the resume to be opened in an
editor.
Parameters:
**<resume>**
Path to a FRESH or JRS resume. Multiple resumes can be
specified, separated by spaces.
hackmyresume PEEK r1.json r2.json r3.json "employment.history[2]"
**<at>**
The resume property or field to be displayed. Can be
any valid resume path, for example:
education[0]
info.name
employment.history[3].start
Options:
**None.**

70
src/cli/help/use.txt Normal file
View File

@ -0,0 +1,70 @@
**HackMyResume** | A Swiss Army knife for resumes and CVs
Usage:
**hackmyresume [--version] [--help] [--silent] [--debug]**
**[--options] [--no-colors] <command> [<args>]**
Commands: (type "hackmyresume help COMMAND" for details)
**BUILD** Build your resume to the destination format(s).
**ANALYZE** Analyze your resume for keywords, gaps, and metrics.
**VALIDATE** Validate your resume for errors and typos.
**NEW** Create a new resume in FRESH or JSON Resume format.
**CONVERT** Convert your resume between FRESH and JSON Resume.
**PEEK** View a specific field or element on your resume.
**HELP** View help on a specific HackMyResume command.
Common Tasks:
Generate a resume in a specific format (HTML, Word, PDF, etc.)
**hackmyresume build rez.json to out/rez.html**
**hackmyresume build rez.json to out/rez.doc**
**hackmyresume build rez.json to out/rez.pdf**
**hackmyresume build rez.json to out/rez.txt**
**hackmyresume build rez.json to out/rez.md**
**hackmyresume build rez.json to out/rez.png**
**hackmyresume build rez.json to out/rez.tex**
Build a resume to ALL available formats:
**hackmyresume build rez.json to out/rez.all**
Build a resume with a specific theme:
**hackmyresume build rez.json to out/rez.all -t themeName**
Create a new empty resume:
**hackmyresume new rez.json**
Convert a resume between FRESH and JRS formats:
**hackmyresume convert rez.json converted.json**
Analyze a resume for important metrics
**hackmyresume analyze rez.json**
Find more resume themes:
**https://www.npmjs.com/search?q=jsonresume-theme**
**https://www.npmjs.com/search?q=fresh-theme**
**https://github.com/fresh-standard/fresh-themes**
Validate a resume's structure and syntax:
**hackmyresume validate resume.json**
View help on a specific command:
**hackmyresume help [build|convert|new|analyze|validate|peek|help]**
Submit a bug or request:
**https://githut.com/hacksalot/HackMyResume/issues**
HackMyResume is free and open source software published
under the MIT license. For more information, visit the
HackMyResume website or GitHub project page.

26
src/cli/help/validate.txt Normal file
View File

@ -0,0 +1,26 @@
**validate** | Validate a resume for correctness
Usage:
**hackmyresume VALIDATE <resume> [--assert]**
The VALIDATE command validates a FRESH or JRS document
against its governing schema, verifying that the resume
is correctly structured and formatted.
Parameters:
**<resume>**
Path to a FRESH or JRS resume. Multiple resumes can be
specified.
hackmyresume ANALYZE resume.json
hackmyresume ANALYZE r1.json r2.json r3.json
Options:
**--assert -a**
Tell HackMyResume to return a non-zero process exit
code if a resume fails to validate.

View File

@ -13,16 +13,19 @@ EXTEND = require 'extend'
chalk = require 'chalk'
PATH = require 'path'
HMSTATUS = require '../core/status-codes'
HME = require '../core/event-codes'
safeLoadJSON = require '../utils/safe-json-loader'
StringUtils = require '../utils/string.js'
#StringUtils = require '../utils/string.js'
_ = require 'underscore'
OUTPUT = require './out'
PAD = require 'string-padding'
Command = require('commander').Command
M2C = require '../utils/md2chalk'
printf = require 'printf'
_opts = { }
_title = chalk.white.bold('\n*** HackMyResume v' +PKG.version+ ' ***')
_out = new OUTPUT( _opts )
_err = require('./error')
_exitCallback = null
@ -33,9 +36,12 @@ line interface as a single method accepting a parameter array.
@param rawArgs {Array} An array of command-line parameters. Will either be
process.argv (in production) or custom parameters (in test).
###
main = module.exports = (rawArgs) ->
module.exports = ( rawArgs, exitCallback ) ->
initInfo = initialize( rawArgs, exitCallback )
if initInfo is null
return
initInfo = initialize( rawArgs )
args = initInfo.args
# Create the top-level (application) command...
@ -77,6 +83,7 @@ main = module.exports = (rawArgs) ->
program
.command('convert')
.description('Convert a resume to/from FRESH or JSON RESUME format.')
.option('-f --format <fmt>', 'FRESH or JRS format and optional version', undefined)
.action(->
x = splitSrcDest.call( this );
execute.call( this, x.src, x.dst, this.opts(), logMsg)
@ -87,6 +94,7 @@ main = module.exports = (rawArgs) ->
program
.command('analyze')
.arguments('<sources...>')
.option('--private', 'Include resume fields marked as private', false)
.description('Analyze one or more resumes.')
.action(( sources ) ->
execute.call( this, sources, [], this.opts(), logMsg)
@ -98,7 +106,8 @@ main = module.exports = (rawArgs) ->
.command('peek')
.arguments('<sources...>')
.description('Peek at a resume field or section')
.action(( sources, sectionOrField ) ->
#.action(( sources, sectionOrField ) ->
.action(( sources ) ->
dst = if (sources && sources.length > 1) then [sources.pop()] else []
execute.call( this, sources, dst, this.opts(), logMsg)
return
@ -114,25 +123,51 @@ main = module.exports = (rawArgs) ->
.option('-p --pdf <engine>', 'PDF generation engine')
.option('--no-sort', 'Sort resume sections by date', false)
.option('--tips', 'Display theme tips and warnings.', false)
.option('--private', 'Include resume fields marked as private', false)
.option('--no-escape', 'Turn off encoding in Handlebars themes.', false)
.description('Generate resume to multiple formats')
.action(( sources, targets, options ) ->
#.action(( sources, targets, options ) ->
.action(->
x = splitSrcDest.call( this );
execute.call( this, x.src, x.dst, this.opts(), logMsg)
return
)
# Create the HELP command
program
.command('help')
.arguments('[command]')
.description('Get help on a HackMyResume command')
.action ( cmd ) ->
cmd = cmd || 'use'
manPage = FS.readFileSync(
PATH.join(__dirname, 'help/' + cmd + '.txt'),
'utf8')
_out.log M2C(manPage, 'white', 'yellow.bold')
return
program.parse( args )
if !program.args.length
throw { fluenterror: 4 }
throw fluenterror: 4
### Massage command-line args and setup Commander.js. ###
initialize = ( ar ) ->
o = initOptions( ar );
initialize = ( ar, exitCallback ) ->
_exitCallback = exitCallback || process.exit
o = initOptions ar
if o.ex
_err.init false, true, false
if( o.ex.op == 'parse' )
_err.err
fluenterror: if o.ex.op == 'parse' then HMSTATUS.invalidOptionsFile else HMSTATUS.optionsFileNotFound,
inner: o.ex.inner,
quit: true
else
_err.err fluenterror: HMSTATUS.optionsFileNotFound, inner: o.ex.inner, quit: true
return null
o.silent || logMsg( _title )
# Emit debug prelude if --debug was specified
@ -147,20 +182,26 @@ initialize = ( ar ) ->
#_out.log(chalk.cyan(PAD(' fresh-jrs-converter:',25, null, PAD.RIGHT)) + chalk.cyan.bold( PKG.dependencies['fresh-jrs-converter'] ))
_out.log('')
_err.init o.debug, o.assert, o.silent
# Handle invalid verbs here (a bit easier here than in commander.js)...
if o.verb && !HMR.verbs[ o.verb ] && !HMR.alias[ o.verb ]
throw { fluenterror: HMSTATUS.invalidCommand, quit: true, attempted: o.orgVerb }
if o.verb && !HMR.verbs[ o.verb ] && !HMR.alias[ o.verb ] && o.verb != 'help'
_err.err fluenterror: HMSTATUS.invalidCommand, quit: true, attempted: o.orgVerb, true
# Override the .missingArgument behavior
Command.prototype.missingArgument = (name) ->
if this.name() != 'new'
throw { fluenterror: HMSTATUS.resumeNotFound, quit: true }
Command.prototype.missingArgument = (### unused ###) ->
if this.name() != 'help'
_err.err
verb: @name()
fluenterror: HMSTATUS.resumeNotFound
, true
return
# Override the .helpInformation behavior
Command.prototype.helpInformation = ->
manPage = FS.readFileSync(
PATH.join(__dirname, 'use.txt'), 'utf8' )
return chalk.green.bold(manPage)
PATH.join(__dirname, 'help/use.txt'), 'utf8' )
return M2C(manPage, 'white', 'yellow')
return {
args: o.args,
@ -196,6 +237,7 @@ initOptions = ( ar ) ->
if optStr && (optStr = optStr.trim())
#var myJSON = JSON.parse(optStr);
if( optStr[0] == '{')
# TODO: remove use of evil(). - hacksalot
### jshint ignore:start ###
oJSON = eval('(' + optStr + ')') # jshint ignore:line <-- no worky
### jshint ignore:end ###
@ -203,25 +245,22 @@ initOptions = ( ar ) ->
inf = safeLoadJSON( optStr )
if( !inf.ex )
oJSON = inf.json
# TODO: Error handling
else
return inf
# Grab the --debug flag
isDebug = _.some( args, (v) ->
return v == '-d' || v == '--debug'
)
# Grab the --silent flag
isSilent = _.some( args, (v) ->
return v == '-s' || v == '--silent'
)
# Grab the --no-color flag
# Grab the --debug flag, --silent, --assert and --no-color flags
isDebug = _.some args, (v) -> v == '-d' || v == '--debug'
isSilent = _.some args, (v) -> v == '-s' || v == '--silent'
isAssert = _.some args, (v) -> v == '-a' || v == '--assert'
isMono = _.some args, (v) -> v == '--no-color'
isNoEscape = _.some args, (v) -> v == '--no-escape'
return {
color: !isMono,
debug: isDebug,
silent: isSilent,
assert: isAssert,
noescape: isNoEscape,
orgVerb: oVerb,
verb: verb,
json: oJSON,
@ -233,19 +272,51 @@ initOptions = ( ar ) ->
### Invoke a HackMyResume verb. ###
execute = ( src, dst, opts, log ) ->
loadOptions.call( this, opts, this.parent.jsonArgs )
hand = require( './error' )
hand.init( _opts.debug, _opts.assert, _opts.silent )
v = new HMR.verbs[ this.name() ]()
_opts.errHandler = v
_out.init( _opts )
v.on( 'hmr:status', -> _out.do.apply( _out, arguments ) )
v.on( 'hmr:error', -> hand.err.apply( hand, arguments ) )
v.invoke.call( v, src, dst, _opts, log )
if v.errorCode
console.log 'Exiting with error code ' + v.errorCode
process.exit(v.errorCode)
# Create the verb
v = new HMR.verbs[ @name() ]()
# Initialize command-specific options
loadOptions.call this, opts, this.parent.jsonArgs
# Set up error/output handling
_opts.errHandler = v
_out.init _opts
# Hook up event notifications
v.on 'hmr:status', -> _out.do.apply _out, arguments
v.on 'hmr:error', -> _err.err.apply _err, arguments
# Invoke the verb using promise syntax
prom = v.invoke.call v, src, dst, _opts, log
prom.then executeSuccess, executeFail
return
### Success handler for verb invocations. Calls process.exit by default ###
executeSuccess = (###obj###) ->
# Can't call _exitCallback here (process.exit) when PDF is running in BK
#_exitCallback 0; return
### Failure handler for verb invocations. Calls process.exit by default ###
executeFail = (err) ->
#console.dir err
finalErrorCode = -1
if err
if err.fluenterror
finalErrorCode = err.fluenterror
else if err.length
finalErrorCode = err[0].fluenterror
else
finalErrorCode = err
if _opts.debug
msgs = require('./msg').errors;
logMsg printf M2C( msgs.exiting.msg, 'cyan' ), finalErrorCode
logMsg err.stack if err.stack
_exitCallback finalErrorCode
return
@ -291,7 +362,8 @@ splitSrcDest = () ->
params = this.parent.args.filter((j) -> return String.is(j) )
if params.length == 0
throw { fluenterror: HMSTATUS.resumeNotFound, quit: true }
#tmpName = @name()
throw { fluenterror: HMSTATUS.resumeNotFound, verb: @name(), quit: true }
# Find the TO keyword, if any
splitAt = _.findIndex( params, (p) -> return p.toLowerCase() == 'to'; )
@ -314,4 +386,5 @@ splitSrcDest = () ->
### Simple logging placeholder. ###
logMsg = () ->
# eslint-disable-next-line no-console
_opts.silent || console.log.apply( console.log, arguments )

View File

@ -3,6 +3,8 @@ events:
msg: Invoking **%s** command.
beforeCreate:
msg: Creating new **%s** resume: **%s**
afterCreate:
msg: Creating new **%s** resume: **%s**
afterRead:
msg: Reading **%s** resume: **%s**
beforeTheme:
@ -41,6 +43,8 @@ events:
- "VALID!"
- "INVALID"
- "BROKEN"
- "MISSING"
- "ERROR"
beforePeek:
msg:
- Peeking at **%s** in **%s**
@ -79,7 +83,10 @@ errors:
readError:
msg: Reading **???** resume: **%s**
parseError:
msg: Invalid or corrupt JSON on line %s column %s.
msg:
- Invalid or corrupt JSON on line %s column %s.
- Invalid or corrupt JSON on line %s.
- Invalid or corrupt JSON.
invalidHelperUse:
msg: "**Warning**: Incorrect use of the **%s** theme helper."
fileSaveError:
@ -96,3 +103,39 @@ errors:
msg: "Invalid number of parameters. Expected: **%s**."
missingParam:
msg: The '**%s**' parameter was needed but not supplied.
createError:
msg: Failed to create **'%s'**.
exiting:
msg: Exiting with status code **%s**.
validateError:
msg: "An error occurred during validation:\n%s"
invalidOptionsFile:
msg:
- "The specified options file is invalid:\n"
- "\nMake sure the options file contains valid JSON."
optionsFileNotFound:
msg: "The specified options file is missing or inaccessible."
unknownSchema:
msg:
- "Unknown resume schema. Did you specify a valid FRESH or JRS resume?"
- |
At a minimum, a FRESH resume must include a "name" field and a "meta"
property.
"name": "John Doe",
"meta": {
"format": "FRESH@0.1.0"
}
JRS-format resumes must include a "basics" section with a "name":
"basics": {
"name": "John Doe"
}
themeHelperLoad:
msg: >-
An error occurred while attempting to load the '%s' theme helper. Is the
theme correctly installed?
dummy: dontcare
invalidSchemaVersion:
msg: "'%s' is not recognized as a valid schema version."

View File

@ -9,10 +9,8 @@ Output routines for HackMyResume.
chalk = require('chalk')
HME = require('../core/event-codes')
_ = require('underscore')
Class = require('../utils/class.js')
M2C = require('../utils/md2chalk.js')
PATH = require('path')
LO = require('lodash')
FS = require('fs')
EXTEND = require('extend')
HANDLEBARS = require('handlebars')
@ -22,21 +20,30 @@ pad = require('string-padding')
dbgStyle = 'cyan';
###* A stateful output module. All HMR console output handled here. ###
OutputHandler = module.exports = Class.extend
init: ( opts ) ->
@opts = EXTEND( true, this.opts || { }, opts )
###* A stateful output module. All HMR console output handled here. ###
class OutputHandler
constructor: ( opts ) ->
@init opts
return
init: (opts) ->
@opts = EXTEND( true, @opts || { }, opts )
@msgs = YAML.load(PATH.join( __dirname, 'msg.yml' )).events
return
log: ( msg ) ->
msg = msg || ''
log: ->
printf = require('printf')
finished = printf.apply( printf, arguments )
@opts.silent || console.log( finished )
@opts.silent || console.log( finished ) # eslint-disable-line no-console
do: ( evt ) ->
@ -50,8 +57,12 @@ OutputHandler = module.exports = Class.extend
this.opts.debug &&
L( M2C( this.msgs.begin.msg, dbgStyle), evt.cmd.toUpperCase() )
when HME.beforeCreate
L( M2C( this.msgs.beforeCreate.msg, 'green' ), evt.fmt, evt.file )
#when HME.beforeCreate
#L( M2C( this.msgs.beforeCreate.msg, 'green' ), evt.fmt, evt.file )
#break;
when HME.afterCreate
L( M2C( @msgs.beforeCreate.msg, if evt.isError then 'red' else 'green' ), evt.fmt, evt.file )
break;
when HME.beforeTheme
@ -81,7 +92,6 @@ OutputHandler = module.exports = Class.extend
if evt.cmd == 'build'
themeName = this.theme.name.toUpperCase()
if this.opts.tips && (this.theme.message || this.theme.render)
WRAP = require('word-wrap')
if this.theme.message
L( M2C( this.msgs.afterBuild.msg[0], 'cyan' ), themeName )
L( M2C( this.theme.message, 'white' ))
@ -118,7 +128,7 @@ OutputHandler = module.exports = Class.extend
@log( chalk.cyan(output) )
when HME.beforeConvert
L( M2C( this.msgs.beforeConvert.msg, 'green' ),
L( M2C( this.msgs.beforeConvert.msg, if evt.error then 'red' else 'green' ),
evt.srcFile, evt.srcFmt, evt.dstFile, evt.dstFmt
);
@ -127,29 +137,46 @@ OutputHandler = module.exports = Class.extend
evt.file, evt.fmt );
when HME.afterValidate
style = if evt.isValid then 'green' else 'yellow'
L(
M2C( this.msgs.afterValidate.msg[0], 'white' ) +
chalk[style].bold(
if evt.isValid
then this.msgs.afterValidate.msg[1]
else this.msgs.afterValidate.msg[2] ),
evt.file, evt.fmt
);
style = 'red'
adj = ''
msgs = @msgs.afterValidate.msg;
switch evt.status
when 'valid' then style = 'green'; adj = msgs[1]
when 'invalid' then style = 'yellow'; adj = msgs[2]
when 'broken' then style = 'red'; adj = msgs[3]
when 'missing' then style = 'red'; adj = msgs[4]
when 'unknown' then style = 'red'; adj = msgs[5]
evt.schema = evt.schema.replace('jars','JSON Resume').toUpperCase()
L(M2C( msgs[0], 'white' ) + chalk[style].bold(adj), evt.file, evt.schema)
if evt.errors
_.each( evt.errors, (err,idx) ->
L( chalk.yellow.bold('--> ') + chalk.yellow(err.field.replace('data.','resume.').toUpperCase() + ' ' + err.message))
, @)
if evt.violations
_.each evt.violations, (err) ->
L( chalk.yellow.bold('--> ') +
chalk.yellow(err.field.replace('data.','resume.').toUpperCase() +
' ' + err.message))
return
, @
return
when HME.afterPeek
sty = if evt.error then 'red' else ( if evt.target != undefined then 'green' else 'yellow' )
# "Peeking at 'someKey' in 'someFile'."
if evt.requested
L(M2C(this.msgs.beforePeek.msg[0], sty), evt.requested, evt.file)
else
L(M2C(this.msgs.beforePeek.msg[1], sty), evt.file)
if evt.target != undefined
# If the key was present, print it
if evt.target != undefined and !evt.error
# eslint-disable-next-line no-console
console.dir( evt.target, { depth: null, colors: true } )
# If the key was not present, but no error occurred, print it
else if !evt.error
L(M2C( this.msgs.afterPeek.msg, 'yellow'), evt.requested, evt.file);
L M2C( this.msgs.afterPeek.msg, 'yellow'), evt.requested, evt.file
else if evt.error
L chalk.red( evt.error.inner.inner )
module.exports = OutputHandler

Some files were not shown because too many files have changed in this diff Show More