1
0
mirror of https://github.com/JuanCanham/HackMyResume.git synced 2026-06-01 08:20:06 +01:00

Compare commits

..

25 Commits

Author SHA1 Message Date
hacksalot b256eb9a8f Merge branch 'gh-pages' of https://github.com/hacksalot/HackMyResume into gh-pages 2018-02-13 15:31:38 -05:00
hacksalot f143c4b1c1 feat: update Jane resume 2018-02-13 15:31:07 -05:00
hacksalot 2bffa51303 Merge pull request #171 from shime/patch-2
fix a link to a sample resume
2018-01-25 13:01:14 -05:00
hacksalot 3cc2f9f38f Merge pull request #178 from edbrannin/patch-1
Fix link to JSON Resume schema
2018-01-25 00:07:21 -05:00
Ed Brannin 968bd4e404 Fix link to JSON Resume schema 2016-11-16 21:57:55 -05:00
Hrvoje Šimić 8ecff33dfc fix a link to a sample resume 2016-10-08 12:38:31 +02:00
hacksalot 33d62ea3ce Merge branch 'gh-pages' of https://github.com/hacksalot/HackMyResume into gh-pages 2015-12-30 20:23:24 -05:00
hacksalot 470d03d498 Update theme repository link. 2015-12-30 20:20:55 -05:00
hacksalot 6af34c20bb Merge pull request #34 from rvargas/patch-1
Fixes jsonresume URL
2015-12-24 05:48:52 -05:00
Rafael Vargas ee1c0f275d Fixes jsonresume URL 2015-12-24 03:49:37 -06:00
hacksalot f8fd4beb34 Mention FluentCV. 2015-12-22 18:03:33 -05:00
hacksalot cc6100b086 Fix encoding issue on Jane's resume. 2015-12-22 12:44:32 -05:00
hacksalot 5d03a999b6 Adjust NPM install blurb. 2015-12-22 11:51:16 -05:00
hacksalot 16cdaf1004 Introduce Jane's sample resume. 2015-12-22 00:02:29 -05:00
hacksalot 3a5537c932 Add overly colorful favicons; tweak site title.
Courtesy http://realfavicongenerator.net/.
2015-12-21 23:45:28 -05:00
hacksalot f401565a23 Add GA support. 2015-12-21 21:49:38 -05:00
hacksalot 5eb2b21056 Add Google site verification. 2015-12-21 21:45:42 -05:00
hacksalot 84a727bfd3 Tweak some stuff. 2015-12-21 20:12:04 -05:00
hacksalot a5727e00cb Add placeholder content and start theming. 2015-12-21 19:51:53 -05:00
hacksalot 77bcd4b49d ...and fix CSS pathing issue again.
Totally meant to do that.
2015-12-21 17:04:19 -05:00
hacksalot fde301b863 Add CNAME file. 2015-12-21 16:57:14 -05:00
hacksalot ee2430b5a9 Fix CSS pathing issue. 2015-12-21 16:49:29 -05:00
hacksalot 3e64a12f82 Remove Jekyll-generated cruft; apply Minimal theme.
https://github.com/orderedlist/minimal
2015-12-21 16:45:24 -05:00
hacksalot 21c85d7eb8 Add Jekyll suggested mods.
http://jekyllrb.com/docs/github-pages/#project-page-url-structure
2015-12-21 15:30:00 -05:00
hacksalot 714da7b56b Initial commit. 2015-12-21 04:01:22 -05:00
137 changed files with 1623 additions and 9392 deletions
-16
View File
@@ -1,16 +0,0 @@
# 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
*.DOCX diff=astextplain
*.dot diff=astextplain
*.DOT diff=astextplain
*.pdf diff=astextplain
*.PDF diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain
+3 -40
View File
@@ -1,40 +1,3 @@
node_modules/ _site
tests/sandbox/ .sass-cache
doc/ .jekyll-metadata
docs/
local/
npm-debug.log
# Emacs detritus
# -*- mode: gitignore; -*-
*~
\#*\#
/.emacs.desktop
/.emacs.desktop.lock
*.elc
auto-save-list
tramp
.\#*
# Org-mode
.org-id-locations
*_archive
# flymake-mode
*_flymake.*
# eshell files
/eshell/history
/eshell/lastdir
# elpa packages
/elpa/
# reftex files
*.rel
# AUCTeX auto folder
/auto/
# cask packages
.cask/
-9
View File
@@ -1,9 +0,0 @@
language: node_js
node_js:
- "0.10"
- "0.11"
- "0.12"
- "4.0"
- "4.1"
- "4.2"
- "5.0"
-306
View File
@@ -1,306 +0,0 @@
CHANGELOG
=========
## v1.6.0
### Major Improvements
- Better consistency and coverage for all FRESH resumes and themes ([#45][i45]).
- Initial support for overridable fonts in FRESH themes. Like a particular
theme, but want to change the typography? The specific fonts used by a theme
can now be overridden by the user. (FRESH themes only).
- New resume sections! Support for `projects` and `affiliation` resume sections
for technical and creative projects and memberships / clubs / associations,
respectively ([#92][i92]).
- New command! `PEEK` at any arbitrary field or entry on your `.json` resume.
### Added
- Improved handling of start and end dates on `employment`, `projects`,
`education`, and other sections with start/end dates.
- Support for an `.ignore` property on any FRESH or JSON Resume section or field.
Ignored properties will be treated by HackMyResume as if they weren't present.
- Emit extended status and error info with the `--debug` or `-d` switch.
- The `-o` or `--options` switch can now handle either the path to a **JSON
settings file** or **raw JSON/JavaScript**. Since the JSON double quote syntax
is a bit cumbersome from the command line, HackMyResume accepts regular
JavaScript object literal syntax:
hackmyresume build resume.json -o "{ theme: 'compact', silent: 'true' }"
- Ability to disable sorting of resume sections (employments, projects, etc.)
with the `--no-sort` option. HMR will respect the order of items as they appear
in your resume `.json` file.
- Improvements to the starter resume emitted by `hackmyresume new`.
- Theme Authoring: Annotated the HTML and MS Word (XML) formats of the Modern
theme for FRESH theme authors.
- Theme Authoring: Support for templatized CSS files in FRESH themes. CSS files
are now expanded via Handlebars or Underscore prior to copying to the
destination.
- Added CHANGELOG.md (this file).
### Changed
- Rewrote the HackMyResume man/help page.
- Minor incremental updates to the [FRESCA][fresca] schema.
- PDF generation now uses asynchronous `spawn()` which has better compatibility
with old or boutique versions of Node.js.
- Refactored colors in HackMyResume output. Errors will now display as red,
warnings as yellow, successful operations as green, and informational messages
as cyan.
- Theme messages and usage tips will no longer display during resume generation
by default. Use the `--tips` option to view them.
- The `--no-tips` option (default: false) has been replaced with the `--tips`
option, also defaulting to false.
- Removed the `hello-world` theme from the [prebuilt themes][themes] that ship
with HackMyResume. It can be installed separately from NPM:
```bash
npm install fresh-theme-hello-world
hackmyresume resume.json -t node_modules/fresh-theme-hello-world
```
- sd
### Fixed
- PDF generation issues on older versions of Node.
- Stack traces not being emitted correctly.
- Missing `speaking` section will now appear on generated resumes ([#101][i101]).
- Incomplete `education` details will now appear on generated resumes ([#65][i65]).
- Missing employment end date being interpreted as "employment ends today"
([#84][i84]).
- Merging multiple source resumes during `BUILD` sometimes fails.
- Document `--pdf` flag in README ([#111][i111]).
### Internal
- Logging messages have been moved out of core HackMyResume code ahead of
localization support.
- All HackMyResume console output is described in `msg.yml`.
- Relaxed pure JavaScript requirement. CoffeeScript will now start appearing
in HackMyResume and FluentCV sources!
- Additional tests.
## v1.5.2
### Fixed
- Tweak stack trace under `--debug`.
## v1.5.1
### Added
- Preliminary support for `-d` or `--debug` flag. Forces HackMyResume to emit a stack trace under error conditions.
## v1.5.0
### Added
- HackMyResume now supports **CLI-based generation of PDF formats across multiple engines (Phantom, wkhtmltopdf, etc)**. Instead of talking to these engines over a programmatic API, as in prior versions, HackMyResume 1.5+ speaks to them over the same command-line interface (CLI) you'd use if you were using these tools directly.
- HackMyResume will now (attempt to) **generate a PDF output for JSON Resume themes** (in addition to HTML).
- Minor README and FAQ additions.
### Changed
- **Cleaner, quicker installs**. Installing HackMyResume with `npm install hackmyresume -g` will no longer trigger a lengthy, potentially error-prone install for Phantom.js and/or wkhtmltopdf for PDF support. Instead, users can install these engines externally and HMR will use them when present.
- Minor error handling improvements.
### Fixed
- Fixed an error with generating specific formats with the `BUILD` command (#97).
- Fixed numerous latent/undocumented bugs and glitches.
## v1.4.2
### Added
- Introduced [FAQ](https://github.com/hacksalot/HackMyResume/blob/master/FAQ.md).
- Additional README notes.
## v1.4.1
### Added
- `hackmyresume new` now generates a [valid starter resume with sample data](https://github.com/fluentdesk/fresh-resume-starter).
### Fixed
- Fixed warning message when `hackmyresume new` is run without a filename.
## v1.4.0
### Added
- **"Projects" support**: FRESH resumes and themes can now store and display
open source, commercial, private, personal, and creative projects.
- **New command: ANALYZE**. Inspect your resume for gaps, keyword counts, and other metrics. (Experimental.)
- **Side-by-side PDF generation** with Phantom and wkhtmltopdf. Use the `--pdf` or `-p` flag to pick between `phantom` and `wkhtmltopdf` generation.
- **Disable PDF generation** with the `--pdf none` switch.
- **Inherit formats between themes**. Themes can now inherit formats (Word, HTML, .txt, etc.) from other themes. (FRESH themes only.)
- **Rename resume sections** to different languages or wordings.
- **Specify complex options via external file**. Use with the `-o` or `--opts` option.
- **Disable colors** with the `--no-color` flag.
- **Theme messages and usage tips** instructions will now appear in the default HackMyResume output for the `build` command. Run `hackmyresume build resume.json -t awesome` for an example. Turn off with the `--no-tips` flag.
- **Treat validation errors as warnings** with the `--assert` switch (VALIDATE command only).
### Fixed
- Fixed a minor glitch in the FRESCA schema.
- Fixed encoding issues in the `Highlights` section of certain resumes.
- Fix behavior of `-s` and `--silent` flags.
### Changed
- PDF generation now defaults to Phantom for all platforms, with `wkhtmltopdf`
accessible with `--pdf wkhtmltopdf`.
- Resumes are now validated, by default, prior to generation. This
behavior can be disabled with the `--novalidate` or `--force` switch.
- Syntax errors in source FRESH and JSON Resumes are now captured for all
commands.
- Minor updates to README.
- Most themes now inherit Markdown and Plain Text formats from the **Basis**
theme.
### Internal
- Switched from color to chalk.
- Command invocations now handled through commander.js.
- Improved FRESH theme infrastructure (more partials, more DRY).
## v1.3.1
### Added
- Add additional Travis badges.
### Fixed
- Fix extraneous console log output when generating a FRESH theme to MS Word.
- Fix Travis tests on `dev`.
## v1.3.0
### Added
- **Local generation of JSON Resume themes**. To use a JSON Resume theme, first install it with `npm install jsonresume-theme-[blah]` (most JSON Resume themes are on NPM). Then pass it into HackMyResume via the `-t` parameter:
`hackmyresume BUILD resume.json TO out/somefile.all -t node_modules/jsonresume-theme-classy`
- **Better Markdown support.** HackMyResume will start flowing basic Markdown styles to JSON Resume (HTML) themes. FRESH's existing Markdown support has also been improved.
- **.PNG output formats** will start appearing in themes that declare an HTML output.
- **Tweak CSS embedding / linking via the --css option** (`<style></style>` vs `<link>`). Only works for HTML (or HTML-driven) formats of FRESH themes. Use `--css=link` to link in CSS assets and `--css=embed` to embed the styles in the HTML document. For example `hackmyresume BUILD resume.json TO out/resume.all --css=link`.
- **Improved Handlebars/Underscore helper support** for FRESH themes. Handlebars themes can access helpers via `{{helperName}}`. Underscore themes can access helpers via the `h` object.
### Changed
- **Distinguish between validation errors and syntax errors** when validating a FRESH or JRS resume with `hackmyresume validate <blah>`.
- **Emit line and column info** for syntax errors during validation of FRESH and JRS resumes.
- **FRESH themes now embed CSS into HTML formats by default** so that the HTML resume output doesn't have an external CSS file dependency by default. Users can specify normal linked stylesheets by setting `--css=link`.
- **Renamed fluent-themes repo to fresh-themes** in keeping with the other parts of the project.
### Fixed
- Fix various encoding errors in MS Word outputs.
- Fix assorted FRESH-to-JRS and JRS-to-FRESH conversion glitches.
- Fix error when running HMR with no parameters.
- Other minor fixes.
## v1.3.0-beta
- Numerous changes supporting v1.3.0.
## v1.2.2
### Fixed
- Various in-passing fixes.
## v1.2.1
### Fixed
- Fix `require('FRESCA')` error.
- Fix `.history` and `.map` errors on loading incomplete or empty JRS resumes.
### Added
- Better test coverage of incomplete/empty resumes.
## v1.2.0
### Fixed
- Fixed the `new` command: Generate a new FRESH-format resume with `hackmyresume new resume.json` or a new JSON Resume with `hackmyresume new resume.json -f jrs`.
### Added
- Introduced CLI tests.
## v1.1.0
### Fixed
- MS Word formats: Fixed skill coloring/level bug.
### Changed
- Make the `TO` keyword optional. If no `TO` keyword is specified (for the `build` and `convert` commands), HMR will assume the last file passed in is the desired output file. So these are equivalent:
```shell
hackmyresume BUILD resume.json TO out/resume.all
hackmyresume BUILD resume.json out/resume.all
```
`TO` only needs to be included if you have multipled output files:
```shell
hackmyresume BUILD resume.json TO out1.doc out2.html out3.tex
```
## v1.0.1
### Fixed
- Correctly generate MS Word hyperlinks from Markdown source data.
## v1.0.0
- Initial public 1.0 release.
[i45]: https://github.com/hacksalot/HackMyResume/issues/45
[i65]: https://github.com/hacksalot/HackMyResume/issues/65
[i84]: https://github.com/hacksalot/HackMyResume/issues/84
[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
-58
View File
@@ -1,58 +0,0 @@
Contributing
============
*Note: HackMyResume is also available as [FluentCV][fcv]. Contributors are
credited in both.*
HackMyResume needs your help! Our contribution workflow is based on [GitHub
Flow][flow] and we respond to all pull requests and issues, usually within 24
hours. HackMyResume has no corporate affiliation and no commercial basis, which
allows the project to maintain a strict user-first policy, rapid development
velocity, and a liberal stance on contributions and exotic functionality in
keeping with the spirit (and name) of the tool.
In short, your code is welcome here.
## How To Contribute
1. Optional: [**open an issue**][iss] identifying the feature or bug you'd like
to implement or fix. This step isn't required — you can start hacking away on
HackMyResume without clearing it with us — but helps avoid duplication of work
and ensures that your changes will be accepted once submitted.
2. **Fork and clone** the HackMyResume project.
3. Ideally, **create a new feature branch** (eg, `feat/new-awesome-feature` or
similar; call it whatever you like) to perform your work in.
4. **Install dependencies** by running `npm install` in the top-level
HackMyResume folder.
5. Make your **commits** as usual.
6. **Verify** your changes locally with `npm test`.
7. **Push** your commits.
7. **Submit a pull request** from your feature branch to the HackMyResume `dev`
branch.
8. We'll typically **respond** within 24 hours.
9. Your awesome changes will be **merged** after verification.
## Project Maintainers
HackMyResume is currently maintained by [hacksalot][ha] with assistance from
[tomheon][th] and our awesome [contributors][awesome]. Please direct all official
or internal inquiries to:
```
admin@hackmyresume.com
```
You can reach hacksalot directly at:
```
hacksalot@indevious.com
```
Thanks! See you out there in the trenches.
[fcv]: https://github.com/fluentdesk/fluentcv
[flow]: https://guides.github.com/introduction/flow/
[iss]: https://github.com/hacksalot/HackMyResume/issues
[ha]: https://github.com/hacksalot
[th]: https://github.com/tomheon
[awesome]: https://github.com/hacksalot/HackMyResume/graphs/contributors
-228
View File
@@ -1,228 +0,0 @@
Frequently Asked Questions (FAQ)
================================
## How do I get started with HackMyResume?
1. Install with NPM: `[sudo] npm install hackmyresume -g`.
2. Create a new resume with: `hackmyresume NEW <resume-name>.json`.
3. Test with `hackmyresume BUILD <resume-name>.json`. Look in the `out/` folder.
4. Play around with different themes with the `-t` or `--theme` parameter.
You can use any [FRESH](https://github.com/fluentdesk/fresh-themes) or
[JSON Resume](https://jsonresume.org/themes) theme. The latter have to be
installed first.
## What is FRESH?
FRESH is the **F**luent **R**esume and **E**mployment **S**ystem for **H**umans.
It's an open-source, user-first workflow, schema, and set of practices for
technical candidates and recruiters.
## What is FRESCA?
The **F**RESH **R**esume and **E**mployment **SC**hem**A**&mdash;an open-source,
JSON-driven schema for resumes, CVs, and other employment artifacts. FRESCA is
the recommended schema/format for FRESH, with optional support for JSON Resume.
## What is JSON Resume?
An [open resume standard](http://jsonresume.org/themes/) sponsored by Hired.com.
Like FRESCA, JSON Resume is JSON-driven and open-source. Unlike FRESCA, JSON
Resume targets a worldwide audience where FRESCA is optimized for technical
candidates.
## Should I use the FRESH or JSON Resume format/schema for my resume?
Both! The workflow we like to use:
1. Create a resume in FRESH format for tooling and analysis.
2. Convert it to JSON Resume format for additional themes/tools.
3. Maintain both versions.
Both formats are open-source and both formats are JSON-driven. FRESH was
designed as a universal container format and superset of existing formats, where
the JSON Resume format is intended for a generic audience.
## How do I use a FRESH theme?
Several FRESH themes come preinstalled with HackMyResume; others can be
installed from NPM and GitHub.
### To use a preinstalled FRESH theme:
1. Pass the theme name into HackMyResume via the `--theme` or `-t` parameter:
```bash
hackmyresume build resume.json --theme compact
```
### To use an external FRESH theme:
1. Install the theme locally. The easiest way to do that is with NPM.
```bash
npm install fresh-theme-underscore
```
2. Pass the theme folder into HackMyResume:
```bash
hackmyresume BUILD resume.json --theme node_modules/fresh-theme-underscore
```
3. Check your output folder. It's best to view HTML formats over a local web
server connection.
## How do I use a JSON Resume theme?
JSON Resume (JRS) themes can be installed from NPM and GitHub and passed into
HackMyResume via the `--theme` or `-t` parameter.
1. Install the theme locally. The easiest way to do that is with NPM.
```bash
npm install jsonresume-theme-classy
```
2. Pass the theme folder path into HackMyResume:
```bash
hackmyresume BUILD resume.json --theme node_modules/jsonresume-theme-classy
```
3. Check your output folder. It's best to view HTML formats over a local web
server connection.
## Should I keep my resume in version control?
Absolutely! As text-based, JSON-driven documents, both FRESH and JSON Resume are
ideal candidates for version control. Future versions of HackMyResume will have
this functionality built in.
## Can I change the default section titles ("Employment", "Skills", etc.)?
If you're using a FRESH theme, yes. First, create a HackMyResume options file
mapping resume sections to your preferred section title:
```javascript
// myoptions.json
{
"sectionTitles": {
"employment": "empleo",
"skills": "habilidades",
"education": "educación"
}
}
```
Then, pass the options file into the `-o` or `--opts` parameter:
```bash
hackmyresume BUILD resume.json -o myoptions.json
```
This ability is currently only supported for FRESH resume themes.
## How does resume merging work?
Resume merging is a way of storing your resume in separate files that
HackMyResume will merge into a single "master" resume file prior to generating
specific output formats like HTML or PDF. It's a way of producing flexible,
configurable, targeted resumes with minimal duplication.
For example, a software developer who moonlights as a game programmer might
create three FRESH or JRS resumes at different levels of specificity:
- **generic.json**: A generic technical resume, suitable for all audiences.
- **game-developer.json**: Overrides and amendments for game developer
positions.
- **blizzard.json**: Overrides and amendments specific to a hypothetical
position at Blizzard.
If you run `hackmyresume BUILD generic.json TO out/resume.all`, HMR will
generate all available output formats for the `generic.json` as usual. But if
you instead run...
```bash
hackmyresume BUILD generic.json game-developer.json TO out/resume.all
```
...HackMyResume will notice that multiple source resumes were specified and
merge `game-developer.json` onto `generic.json` before generating, yielding a
resume that's more suitable for game-developer-related positions.
You can take this a step further. Let's say you want to do a targeted resume
submission to a game developer position at Blizzard, and `blizzard.json`
contains the edits and revisions you'd like to show up in the targeted resume.
In that case, merge again! Feed all three resumes to HackMyResume, in order
from most generic to most specific, and HMR will merge them all prior to
generating the final output format(s) for your resume.
```bash
# Merge blizzard.json onto game-developer.json onto generic.json, then build
hackmyresume BUILD generic.json game-developer.json blizzard.json TO out/resume.all
```
There's no limit to the number of resumes you can merge this way.
You can also divide your resume into files containing different sections:
- **resume-a.json**: Contains `info`, `employment`, and `summary` sections.
- **resume-b.json**: Contains all other sections except `references`.
- **references.json**: Contains the private `references` section.
Under that scenario, `hackmyresume BUILD resume-a.json resume-b.json` would
## The HackMyResume terminal color scheme is giving me a headache. Can I disable it?
Yes. Use the `--no-color` option to disable terminal colors:
`hackmyresume <somecommand> <someoptions> --no-color`
## What's the difference between a FRESH theme and a JSON Resume theme?
FRESH themes are multiformat (HTML, Word, PDF, etc.) and required to support
Markdown formatting, configurable section titles, and various other features.
JSON Resume themes are typically HTML-driven, but capable of expansion to other
formats through tools. JSON Resume themes don't support Markdown natively, but
HMR does its best to apply your Markdown, when present, to any JSON Resume
themes it encounters.
## Do I have to have a FRESH resume to use a FRESH theme or a JSON Resume to use a JSON Resume theme?
No. You can mix and match FRESH and JRS-format themes freely. HackMyResume will
perform the necessary conversions on the fly.
## Can I build my own custom FRESH theme?
Yes. The easiest way is to copy an existing FRESH theme, like `modern` or
`compact`, and make your changes there. You can test your theme with:
```bash
hackmyresume build resume.json --theme path/to/my/theme/folder
```
## Can I build my own custom JSON Resume theme?
Yes. The easiest way is to copy an existing JSON Rsume theme and make your
changes there. You can test your theme with:
```bash
hackmyresume build resume.json --theme path/to/my/theme/folder
```
## Can I build my own tools / services / apps / websites around FRESH / FRESCA?
Yes! FRESH/FRESCA formats are 100% open source, permissively licensed under MIT,
and 100% free from company-specific, tool-specific, or commercially oriented
lock-in or cruft. These are clean formats designed for users and builders.
## Can I build my own tools / services / apps / websites around JSON Resume?
Yes! HackMyResume is not affiliated with JSON Resume, but like FRESH/FRESCA,
JSON Resume is open-source, permissively licensed, and free of proprietary
lock-in. See the JSON Resume website for details.
-70
View File
@@ -1,70 +0,0 @@
module.exports = function (grunt) {
'use strict';
var opts = {
pkg: grunt.file.readJSON('package.json'),
simplemocha: {
options: {
globals: ['expect', 'should'],
timeout: 3000,
ignoreLeaks: false,
ui: 'bdd',
reporter: 'spec'
},
all: { src: ['test/*.js'] }
},
jsdoc : {
dist : {
src: ['src/**/*.js'],
options: {
private: true,
destination: 'doc'
}
}
},
clean: ['test/sandbox'],
yuidoc: {
compile: {
name: '<%= pkg.name %>',
description: '<%= pkg.description %>',
version: '<%= pkg.version %>',
url: '<%= pkg.homepage %>',
options: {
paths: 'src/',
//themedir: 'path/to/custom/theme/',
outdir: 'docs/'
}
}
},
jshint: {
options: {
laxcomma: true,
expr: true
},
all: ['Gruntfile.js', 'src/**/*.js', 'test/*.js']
}
};
grunt.initConfig( opts );
grunt.loadNpmTasks('grunt-simple-mocha');
grunt.loadNpmTasks('grunt-contrib-yuidoc');
grunt.loadNpmTasks('grunt-jsdoc');
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.registerTask('test', 'Test the HackMyResume library.',
function( config ) { grunt.task.run(['clean','jshint','simplemocha:all']); });
grunt.registerTask('document', 'Generate HackMyResume library documentation.',
function( config ) { grunt.task.run( ['jsdoc'] ); });
grunt.registerTask('default', [ 'test', 'jsdoc' ]);
};
-22
View File
@@ -1,22 +0,0 @@
The MIT License
===============
Copyright (c) 2016 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
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-567
View File
@@ -1,567 +0,0 @@
HackMyResume
============
[![Latest release][img-release]][latest-release]
[![Build status (MASTER)][img-master]][travis-url-master]
[![Build status (DEV)][img-dev]][travis-url-dev]
*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)
HackMyResume is a dev-friendly, local-only Swiss Army knife for resumes and CVs.
Use it to:
1. **Generate** HTML, Markdown, LaTeX, MS Word, PDF, plain text, JSON, XML,
YAML, print, smoke signal, carrier pigeon, and other arbitrary-format resumes
and CVs, from a single source of truth&mdash;without violating DRY.
2. **Analyze** your resume for keyword density, gaps/overlaps, and other
metrics.
3. **Convert** resumes between [FRESH][fresca] and [JSON Resume][6] formats.
4. **Validate** resumes against either format.
HackMyResume is built with Node.js and runs on recent versions of OS X, Linux,
or Windows. View the [FAQ](FAQ.md).
## Features
- OS X, Linux, and Windows.
- Choose from dozens of FRESH or JSON Resume themes.
- Private, local-only resume authoring and analysis.
- Analyze your resume for keywords, gaps, and other metrics.
- Store your resume data as a durable, versionable JSON or YAML document.
- Generate polished resumes in multiple formats without violating [DRY][dry].
- Output to HTML, Markdown, LaTeX, PDF, MS Word, JSON, YAML, plain text, or XML.
- Validate resumes against the FRESH or JSON Resume schema.
- Support for multiple input and output resumes.
- Convert between FRESH and JSON Resume resumes.
- Use from your command line or [desktop][7].
- Free and open-source through the MIT license.
- Updated daily / weekly. Contributions are [welcome](CONTRIBUTING.md).
## Install
Install the latest stable version of HackMyResume with NPM:
```bash
[sudo] npm install hackmyresume -g
```
Alternately, install the latest bleeding-edge version (updated daily):
```bash
[sudo] npm install hacksalot/hackmyresume#dev -g
```
## Installing PDF Support (optional)
HackMyResume tries not to impose a specific PDF engine requirement on
the user, but will instead work with whatever PDF engines you have installed.
Currently, HackMyResume's PDF generation requires either [Phantom.js][2] or
[wkhtmltopdf][3] to be installed on your system and the `phantomjs` and/or
`wkhtmltopdf` binaries to be accessible on your PATH. This is an optional
requirement for users who care about PDF formats. If you don't care about PDF
formats, skip this step.
## Installing Themes
HackMyResume supports both [FRESH][fresh-themes] and [JSON Resume][jrst]-style
résumé themes.
- FRESH themes currently come preinstalled with HackMyResume.
- JSON Resume themes can be installed from NPM, GitHub, or manually.
To install a JSON Resume theme, just `cd` to the folder where you want to store
your themes and run one of:
```bash
# Install with NPM
npm install jsonresume-theme-[theme-name]
# Install with GitHub
git clone https://github.com/[user-or-org]/[repo-name]
```
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
```
Note: You can use install themes anywhere on your file system. You don't need a
package.json or other NPM/Node infrastructure.
## Getting Started
To use HackMyResume you'll need to create a valid resume in either
[FRESH][fresca] or [JSON Resume][6] format. Then you can start using the command
line tool. There are five basic commands you should be aware of:
- **build** generates resumes in HTML, Word, Markdown, PDF, and other formats.
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
```
- **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
```
- **analyze** inspects your resume for keywords, duration, and other metrics.
```bash
# hackmyresume ANALYZE <INPUTS...>
hackmyresume ANALYZE resume.json
hackmyresume ANALYZE r1.json r2.json
```
- **convert** converts your source resume between FRESH and JSON Resume
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
```
- **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
```
- **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
```
## Supported Output Formats
HackMyResume supports these output formats:
Output Format | Ext | Notes
------------- | --- | -----
HTML | .html | A standard HTML 5 + CSS resume format that can be viewed in a browser, deployed to a website, etc.
Markdown | .md | A structured Markdown document that can be used as-is or used to generate HTML.
LaTeX | .tex | A structured LaTeX document (or collection of documents) that can be processed with pdflatex, xelatex, and similar tools.
MS Word | .doc | A Microsoft Word office document (XML-driven; WordProcessingML).
Adobe Acrobat (PDF) | .pdf | A binary PDF document driven by an HTML theme (through wkhtmltopdf).
plain text | .txt | A formatted plain text document appropriate for emails or copy-paste.
JSON | .json | A JSON representation of the resume.
YAML | .yml | A YAML representation of the resume.
RTF | .rtf | Forthcoming.
Textile | .textile | Forthcoming.
image | .png, .bmp | Forthcoming.
## Use
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].
```
Where `<INPUTS>` is one or more .json resume files, separated by spaces;
`<OUTPUTS>` is one or more destination resumes, and `<THEME>` is the desired
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
# 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
# Specify 2 inputs and 3 outputs
hackmyresume BUILD in1.json in2.json TO out.html out.doc out.pdf
```
You should see something to the effect of:
```
*** HackMyResume v1.4.0 ***
Reading JSON resume: foo/resume.json
Applying MODERN Theme (7 formats)
Generating HTML resume: out/resume.html
Generating TXT resume: out/resume.txt
Generating DOC resume: out/resume.doc
Generating PDF resume: out/resume.pdf
Generating JSON resume: out/resume.json
Generating MARKDOWN resume: out/resume.md
Generating YAML resume: out/resume.yml
```
## Advanced
### Applying a theme
HackMyResume can work with any FRESH or JSON Resume theme (the latter must be
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]
```
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
```
FRESH themes are currently pre-installed with HackMyResume. JSON Resume themes
can be installed prior to use:
```bash
# Install a JSON Resume theme into a local node_modules subfolder:
npm install jsonresume-theme-[name]
# Use it with HackMyResume
hackmyresume build resume.json -t node_modules/jsonresume-theme-[name]
```
As of v1.6.0, available predefined FRESH themes are `positive`, `modern`,
`compact`, `minimist`, and `hello-world`. For a list of JSON Resume themes,
check the [NPM Registry](https://www.npmjs.com/search?q=jsonresume-theme).
### Merging resumes
You can **merge multiple resumes together** by specifying them in order from
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
```
This can be useful for overriding a base (generic) resume with information from
a specific (targeted) resume. For example, you might override your generic
catch-all "software developer" resume with specific details from your targeted
"game developer" resume, or combine two partial resumes into a "complete"
resume. Merging follows conventional [extend()][9]-style behavior and there's
no arbitrary limit to how many resumes you can merge:
```bash
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
Reading JSON resume: in4.json
Merging in4.json onto in3.json onto in2.json onto in1.json
Generating HTML resume: out.html
Generating WORD resume: out.doc
```
### Multiple targets
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
```
### Using .all
The special `.all` extension tells HackMyResume to generate all supported output
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
```
..tells HackMyResume to read `me.json` and generate `out/resume.md`,
`out/resume.doc`, `out/resume.html`, `out/resume.txt`, `out/resume.pdf`, and
`out/resume.json`.
### Building PDFs
*Users who don't care about PDFs can turn off PDF generation across all themes
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...
- [wkhtmltopdf][3]
- [Phantom.js][3]
..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
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
```
Assuming you've installed one or both of these engines on your system, you can
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
```
### Analyzing
HackMyResume can analyze your resume for keywords, employment gaps, and other
metrics. Run:
```bash
hackmyresume ANALYZE <my-resume>.json
```
Depending on the HackMyResume version, you should see output similar to:
```
*** HackMyResume v1.6.0 ***
Reading resume: resume.json
Analyzing FRESH resume: resume.json
SECTIONS (10):
employment: 12
education: 2
service: 1
skills: 8
writing: 1
recognition: 0
social: 4
interests: 2
references: 1
languages: 2
COVERAGE (61.1%):
Total Days: 6034
Employed: 3688
Gaps: 8 [31, 1065, 273, 153, 671, 61, 61, 31]
Overlaps: 1 [243]
KEYWORDS (61):
Node.js: 6 mentions
JavaScript: 9 mentions
SQL Server: 3 mentions
Visual Studio: 6 mentions
Web API: 1 mentions
N-tier / 3-tier: 1 mentions
HTML 5: 1 mentions
JavaScript: 6 mentions
CSS: 2 mentions
Sass / LESS / SCSS: 1 mentions
LAMP: 3 mentions
WISC: 1 mentions
HTTP: 21 mentions
JSON: 1 mentions
XML: 2 mentions
REST: 1 mentions
WebSockets: 2 mentions
Backbone.js: 3 mentions
Angular.js: 1 mentions
Node.js: 4 mentions
NPM: 1 mentions
Bower: 1 mentions
Grunt: 2 mentions
Gulp: 1 mentions
jQuery: 2 mentions
Bootstrap: 3 mentions
Underscore.js: 1 mentions
PhantomJS: 1 mentions
CoffeeScript: 1 mentions
Python: 11 mentions
Perl: 4 mentions
PHP: 7 mentions
MySQL: 12 mentions
PostgreSQL: 4 mentions
NoSQL: 2 mentions
Apache: 2 mentions
AWS: 2 mentions
EC2: 2 mentions
RDS: 3 mentions
S3: 1 mentions
Azure: 1 mentions
Rackspace: 1 mentions
C++: 23 mentions
C++ 11: 1 mentions
Boost: 1 mentions
Xcode: 2 mentions
gcc: 1 mentions
OO&AD: 1 mentions
.NET: 20 mentions
Unity 5: 2 mentions
Mono: 3 mentions
MonoDevelop: 1 mentions
Xamarin: 1 mentions
TOTAL: 180 mentions
```
### Validating
HackMyResume can also validate your resumes against either the [FRESH /
FRESCA][fresca] or [JSON Resume][6] formats. To validate one or more existing
resumes, use the `validate` command:
```bash
# Validate myresume.json against either the FRESH or JSON Resume schema.
hackmyresume VALIDATE resumeA.json resumeB.json
```
HackMyResume will validate each specified resume in turn:
```bash
*** HackMyResume v1.6.0 ***
Validating JSON resume: resumeA.json (INVALID)
Validating JSON resume: resumeB.json (VALID)
```
### Converting
HackMyResume can convert between the [FRESH][fresca] and [JSON Resume][6]
formats. Just run:
```bash
hackmyresume CONVERT <INPUTS> <OUTPUTS>
```
where <INPUTS> is one or more resumes in FRESH or JSON Resume format, and
<OUTPUTS> is a corresponding list of output file names. HackMyResume will
autodetect the format (FRESH or JRS) of each input resume and convert it to the
other format (JRS or FRESH).
### File-based Options
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
```
The options file can contain any documented HackMyResume option, including
`theme`, `silent`, `debug`, `pdf`, `css`, and other settings.
```javascript
{
// Set the default theme to "compact"
"theme": "compact",
// Change the "employment" section title text to "Work"
"sectionTitles": {
"employment": "Work"
}
}
```
If a particular option is specified both on the command line and in an external
options file, the explicit command-line option takes precedence.
```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
> Reading resume: resume.json
> Applying COMPACT theme (7 formats)
```
### Prettifying
HackMyResume applies [js-beautify][10]-style HTML prettification by default to
HTML-formatted resumes. To disable prettification, the `--no-prettify` or `-n`
flag can be used:
```bash
hackmyresume BUILD resume.json out.all --no-prettify
```
### Silent Mode
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
```
### Debug Mode
Use `-d` or `--debug` to force HMR to emit a call stack when errors occur. In
the future, this option will emit detailed error logging.
```bash
hackmyresume BUILD resume.json -d
hackmyresume ANALYZE resume.json --debug
```
## Contributing
HackMyResume is a community-driven free and open source project under the MIT
License. Contributions are encouraged and we respond to all PRs and issues,
usually within 24 hours. See [CONTRIBUTING.md][contribute] for details.
## License
MIT. Go crazy. See [LICENSE.md][1] for details.
[1]: LICENSE.md
[2]: http://phantomjs.org/
[3]: http://wkhtmltopdf.org/
[4]: https://nodejs.org/
[5]: https://www.npmjs.com/
[6]: http://jsonresume.org
[7]: http://fluentcv.com
[8]: https://youtu.be/N9wsjroVlu8
[9]: https://api.jquery.com/jquery.extend/
[10]: https://github.com/beautify-web/js-beautify
[fresh]: https://github.com/fluentdesk/FRESH
[fresca]: https://github.com/fluentdesk/FRESCA
[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
[img-dev]: https://img.shields.io/travis/hacksalot/HackMyResume/dev.svg?label=dev
[travis-url-master]: https://travis-ci.org/hacksalot/HackMyResume?branch=master
[travis-url-dev]: https://travis-ci.org/hacksalot/HackMyResume?branch=dev
[latest-release]: https://github.com/hacksalot/HackMyResume/releases/latest
[contribute]: CONTRIBUTING.md
[fresh-themes]: https://github.com/fluentdesk/fresh-themes
[jrst]: https://www.npmjs.com/search?q=jsonresume-theme
+26
View File
@@ -0,0 +1,26 @@
# Welcome to Jekyll!
#
# This config file is meant for settings that affect your whole blog, values
# which you are expected to set up once and rarely need to edit after that.
# For technical reasons, this file is *NOT* reloaded automatically when you use
# 'jekyll serve'. If you change this file, please restart the server process.
# Site settings
title: HackMyResume
titleMarkup: "<span style='color: #B5B5B5;'>Hack</span><span style='color: #8A8A8A;'>My</span><span>Resume</span>"
email: hacksalot@indevious.com
description: > # this means to ignore newlines until "baseurl:"
Write an awesome description for your new site here. You can edit this
line in _config.yml. It will appear in your document head meta (for
Google search results) and in your feed.xml site description.
baseurl: "/HackMyResume" # the subpath of your site, e.g. /blog
url: "https://fluentdesk.com/hackmyresume" # the base hostname & protocol for your site
github_url: "https://github.com/hacksalot/HackMyResume"
github_short: hacksalot/HackMyResume
author_name: hacksalot
twitter_username: tweetsalot
github_username: hacksalot
npm_package: hackmyresume
# Build settings
markdown: kramdown
Binary file not shown.
+19
View File
@@ -0,0 +1,19 @@
<link rel="apple-touch-icon" sizes="57x57" href="/apple-touch-icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="/apple-touch-icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="/apple-touch-icon-72x72.png">
<link rel="apple-touch-icon" sizes="76x76" href="/apple-touch-icon-76x76.png">
<link rel="apple-touch-icon" sizes="114x114" href="/apple-touch-icon-114x114.png">
<link rel="apple-touch-icon" sizes="120x120" href="/apple-touch-icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="/apple-touch-icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="/apple-touch-icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon-180x180.png">
<link rel="icon" type="image/png" href="/favicon-32x32.png" sizes="32x32">
<link rel="icon" type="image/png" href="/favicon-194x194.png" sizes="194x194">
<link rel="icon" type="image/png" href="/favicon-96x96.png" sizes="96x96">
<link rel="icon" type="image/png" href="/android-chrome-192x192.png" sizes="192x192">
<link rel="icon" type="image/png" href="/favicon-16x16.png" sizes="16x16">
<link rel="manifest" href="/manifest.json">
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5">
<meta name="msapplication-TileColor" content="#da532c">
<meta name="msapplication-TileImage" content="/mstile-144x144.png">
<meta name="theme-color" content="#ffffff">
+10
View File
@@ -0,0 +1,10 @@
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-71630179-1', 'auto');
ga('send', 'pageview');
</script>
+13
View File
@@ -0,0 +1,13 @@
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="chrome=1">
<title>{{ site.title }}</title>
<link href='https://fonts.googleapis.com/css?family=Open+Sans+Condensed:700,300' rel='stylesheet' type='text/css'>
<link rel="stylesheet" href="/css/main.css">
<meta name="viewport" content="width=device-width">
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
{% include google-analytics.html %}
{% include favicons.html %}
</head>
+30
View File
@@ -0,0 +1,30 @@
---
layout: master-bare
---
<div class="wrapper">
<header>
<h1><img src="android-chrome-48x48.png" class="logo">{{ site.titleMarkup }}</h1>
<p>A Swiss Army Knife for <strong>résumés and CVs</strong><br>For Windows, OS X, Linux, and Node.js</p>
{{ content }}
<p><strong>Install</strong> with NPM:<br><br><code>npm install hackmyresume -g</code></p>
<p><strong>Feed me</strong>: One or more <a href="https://github.com/fluentdesk/FRESCA">FRESH</a> or <a href="https://jsonresume.org/schema/">JSON Resume</a> format résumés.</p>
<p><strong>Sit back</strong> while I generate polished résumés in HTML, WORD, Markdown, PDF, LaTeX, JSON, YAML, XML, plain text, smoke signal, and carrier pigeon.</p>
<p><strong>Themes</strong>: Choose from any <a href="https://github.com/fluentdesk/fresh-themes">FRESH</a> or <a href="https://jsonresume.org/themes/">JSON Resume</a> theme or <strong>build your own</strong> with Handlebars or Underscore.</p>
<p><strong>Validation?</strong> Yup.</p>
<p><strong>100% Private and Local</strong> No registration, no drive-by resume uploads, no dialing home, no recruitment.</p>
<ul>
<li><a href="https://www.npmjs.com/package/{{ site.npm_package }}">Install with <strong>NPM</strong></a></li>
<li><a href="{{ site.github_url }}">View on <strong>GitHub</strong></a></li>
<li><a href="jane/resume.html">See a <strong>SAMPLE</strong></a></li>
</ul>
<p>This project is maintained by <a href="https://github.com/{{ site.github_username }}">{{ site.author_name }}</a><br>
Hosted on GitHub Pages</p>
</header>
</div>
<footer>
<div class="wrapper">
<h1>Not a fan of the command line? Check out <a href="http://fluentcv.com">FluentCV Desktop</a>.</h1>
<a href="http://fluentcv.com"><img src="img/fluentcv_desktop.png" id="fluentcv"></a>
</div>
</footer>
<script src="/js/scale.fix.js"></script>
+9
View File
@@ -0,0 +1,9 @@
---
---
<!doctype html>
<html>
{% include head.html %}
<body>
{{ content }}
</body>
</html>
@@ -0,0 +1,25 @@
---
layout: post
title: "Welcome to Jekyll!"
date: 2015-12-21 04:00:02 -0500
categories: jekyll update
---
Youll find this post in your `_posts` directory. Go ahead and edit it and re-build the site to see your changes. You can rebuild the site in many different ways, but the most common way is to run `jekyll serve`, which launches a web server and auto-regenerates your site when a file is updated.
To add new posts, simply add a file in the `_posts` directory that follows the convention `YYYY-MM-DD-name-of-post.ext` and includes the necessary front matter. Take a look at the source for this post to get an idea about how it works.
Jekyll also offers powerful support for code snippets:
{% highlight ruby %}
def print_hi(name)
puts "Hi, #{name}"
end
print_hi('Tom')
#=> prints 'Hi, Tom' to STDOUT.
{% endhighlight %}
Check out the [Jekyll docs][jekyll-docs] for more info on how to get the most out of Jekyll. File all bugs/feature requests at [Jekylls GitHub repo][jekyll-gh]. If you have questions, you can ask them on [Jekyll Talk][jekyll-talk].
[jekyll-docs]: http://jekyllrb.com/docs/home
[jekyll-gh]: https://github.com/jekyll/jekyll
[jekyll-talk]: https://talk.jekyllrb.com/
+272
View File
@@ -0,0 +1,272 @@
@import url(https://fonts.googleapis.com/css?family=Noto+Sans:400,400italic,700italic,700);
body {
background-color: #fff;
padding: 0; margin: 0;
font: 14px/1.5 $base-font;
color:#727272;
font-weight:400;
}
h1, h2, h3, h4, h5, h6 {
color:#222;
margin:0 0 20px;
}
p, ul, ol, table, pre, dl {
margin:0 0 20px;
}
h1, h2, h3 {
line-height:1.1;
}
h1 {
font-size:28px;
margin-bottom: 0;
position: relative;
padding-left: 17px;
font-family: $title-font;
}
h2 {
color:#393939;
}
h3, h4, h5, h6 {
color:#494949;
}
p > code {
background-color: #EAEAEA;
padding: 5px;
text-transform: none;
letter-spacing: 1px;
}
a {
color:#39c;
text-decoration:none;
}
a:hover {
color:#069;
}
a small {
font-size:11px;
color:#777;
margin-top:-0.3em;
display:block;
}
a:hover small {
color:#777;
}
.wrapper {
width: 100%;
max-width: 1150px;
margin:0 auto;
padding: 40px 20px;
// Expand with height of floated children
// http://stackoverflow.com/q/804926/4942583
overflow: hidden;
position: relative;
}
blockquote {
border-left:1px solid #e5e5e5;
margin:0;
padding:0 0 0 20px;
font-style:italic;
}
code, pre {
font-family:Monaco, Bitstream Vera Sans Mono, Lucida Console, Terminal, Consolas, Liberation Mono, DejaVu Sans Mono, Courier New, monospace;
color:#333;
font-size:12px;
}
pre {
padding:8px 15px;
background: #f8f8f8;
border-radius:5px;
border:1px solid #e5e5e5;
overflow-x: auto;
}
table {
width:100%;
border-collapse:collapse;
}
th, td {
text-align:left;
padding:5px 10px;
border-bottom:1px solid #e5e5e5;
}
dt {
color:#444;
font-weight:700;
}
th {
color:#444;
}
img {
max-width:100%;
}
header, section, footer, img, .wrapper {
box-sizing: border-box;
}
header {
position: static;
-webkit-font-smoothing:subpixel-antialiased;
min-width: 250px;
}
header ul {
list-style:none;
height:40px;
padding:0;
background: #f4f4f4;
border-radius:5px;
border:1px solid #e0e0e0;
width:270px;
}
header li {
width:89px;
float:left;
border-right:1px solid #e0e0e0;
height:40px;
}
header > p {
text-transform: uppercase;
font-size: 80%;
margin: 0;
margin-bottom: 15px;
}
header li:first-child a {
border-radius:5px 0 0 5px;
}
header li:last-child a {
border-radius:0 5px 5px 0;
}
header ul a {
line-height:1;
font-size:11px;
color:#999;
display:block;
text-align:center;
padding-top:6px;
height:34px;
}
header ul a:hover {
color:#999;
}
header ul a:active {
background-color:#f0f0f0;
}
strong {
color:#222;
font-weight:700;
}
header ul li + li + li {
border-right:none;
width:89px;
}
header ul a strong {
font-size:14px;
display:block;
color:#222;
}
section {
width: 66%;
float: left;
max-width: 750px;
padding-bottom:50px;
position: relative;
}
small {
font-size:11px;
}
hr {
border:0;
background:#e5e5e5;
height:1px;
margin:0 0 20px;
}
footer {
width: 100%;
float: none;
position: static;
-webkit-font-smoothing:subpixel-antialiased;
background-color: #232323;
padding: 0;
h1 {
color: #DADADA;
text-align: center;
font-weight: 300;
font-size: 44px;
margin-bottom: 1em;
padding-left: 0;
}
a {
color: #4EC4FF;
&:hover {
color: #A8E2FF;
}
}
}
img.main {
border: solid 10px #000;
border-radius: 10px;
margin: 0;
max-width: 100%;
margin-bottom: 15px;
}
img.logo {
position: absolute;
width: 24px;
top: 4px;
left: -5px;
}
img#fluentcv {
display: block;
margin: 0 auto;
}
@media screen and (min-width: 760px) {
img.main {
position: absolute;
right: 20px; top: 40px;
width: 60%;
}
header {
width: 33%;
max-width: 400px;
}
}
+69
View File
@@ -0,0 +1,69 @@
.highlight { background: #ffffff; }
.highlight .c { color: #999988; font-style: italic } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { font-weight: bold } /* Keyword */
.highlight .o { font-weight: bold } /* Operator */
.highlight .cm { color: #999988; font-style: italic } /* Comment.Multiline */
.highlight .cp { color: #999999; font-weight: bold } /* Comment.Preproc */
.highlight .c1 { color: #999988; font-style: italic } /* Comment.Single */
.highlight .cs { color: #999999; font-weight: bold; font-style: italic } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .gd .x { color: #000000; background-color: #ffaaaa } /* Generic.Deleted.Specific */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #999999 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .gi .x { color: #000000; background-color: #aaffaa } /* Generic.Inserted.Specific */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #800080; font-weight: bold; } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { font-weight: bold } /* Keyword.Constant */
.highlight .kd { font-weight: bold } /* Keyword.Declaration */
.highlight .kn { font-weight: bold } /* Keyword.Namespace */
.highlight .kp { font-weight: bold } /* Keyword.Pseudo */
.highlight .kr { font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #445588; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #009999 } /* Literal.Number */
.highlight .s { color: #d14 } /* Literal.String */
.highlight .na { color: #008080 } /* Name.Attribute */
.highlight .nb { color: #0086B3 } /* Name.Builtin */
.highlight .nc { color: #445588; font-weight: bold } /* Name.Class */
.highlight .no { color: #008080 } /* Name.Constant */
.highlight .ni { color: #800080 } /* Name.Entity */
.highlight .ne { color: #990000; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #990000; font-weight: bold } /* Name.Function */
.highlight .nn { color: #555555 } /* Name.Namespace */
.highlight .nt { color: #000080 } /* Name.Tag */
.highlight .nv { color: #008080 } /* Name.Variable */
.highlight .ow { font-weight: bold } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mf { color: #009999 } /* Literal.Number.Float */
.highlight .mh { color: #009999 } /* Literal.Number.Hex */
.highlight .mi { color: #009999 } /* Literal.Number.Integer */
.highlight .mo { color: #009999 } /* Literal.Number.Oct */
.highlight .sb { color: #d14 } /* Literal.String.Backtick */
.highlight .sc { color: #d14 } /* Literal.String.Char */
.highlight .sd { color: #d14 } /* Literal.String.Doc */
.highlight .s2 { color: #d14 } /* Literal.String.Double */
.highlight .se { color: #d14 } /* Literal.String.Escape */
.highlight .sh { color: #d14 } /* Literal.String.Heredoc */
.highlight .si { color: #d14 } /* Literal.String.Interpol */
.highlight .sx { color: #d14 } /* Literal.String.Other */
.highlight .sr { color: #009926 } /* Literal.String.Regex */
.highlight .s1 { color: #d14 } /* Literal.String.Single */
.highlight .ss { color: #990073 } /* Literal.String.Symbol */
.highlight .bp { color: #999999 } /* Name.Builtin.Pseudo */
.highlight .vc { color: #008080 } /* Name.Variable.Class */
.highlight .vg { color: #008080 } /* Name.Variable.Global */
.highlight .vi { color: #008080 } /* Name.Variable.Instance */
.highlight .il { color: #009999 } /* Literal.Number.Integer.Long */
.type-csharp .highlight .k { color: #0000FF }
.type-csharp .highlight .kt { color: #0000FF }
.type-csharp .highlight .nf { color: #000000; font-weight: normal }
.type-csharp .highlight .nc { color: #2B91AF }
.type-csharp .highlight .nn { color: #000000 }
.type-csharp .highlight .s { color: #A31515 }
.type-csharp .highlight .sc { color: #A31515 }
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 170 KiB

+12
View File
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square70x70logo src="/mstile-70x70.png"/>
<square150x150logo src="/mstile-150x150.png"/>
<square310x310logo src="/mstile-310x310.png"/>
<wide310x150logo src="/mstile-310x150.png"/>
<TileColor>#da532c</TileColor>
</tile>
</msapplication>
</browserconfig>
+10
View File
@@ -0,0 +1,10 @@
---
# Only the main Sass file needs front matter (the dashes are enough)
---
@charset "utf-8";
$base-font: "Noto Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
$title-font: 'Open Sans Condensed', $base-font;
// Import partials from `sass_dir` (defaults to `_sass`)
@import "syntax-highlighting", "hackmyresume";
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

+30
View File
@@ -0,0 +1,30 @@
---
layout: null
---
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>{{ site.title | xml_escape }}</title>
<description>{{ site.description | xml_escape }}</description>
<link>{{ site.url }}{{ site.baseurl }}/</link>
<atom:link href="{{ "/feed.xml" | prepend: site.baseurl | prepend: site.url }}" rel="self" type="application/rss+xml"/>
<pubDate>{{ site.time | date_to_rfc822 }}</pubDate>
<lastBuildDate>{{ site.time | date_to_rfc822 }}</lastBuildDate>
<generator>Jekyll v{{ jekyll.version }}</generator>
{% for post in site.posts limit:10 %}
<item>
<title>{{ post.title | xml_escape }}</title>
<description>{{ post.content | xml_escape }}</description>
<pubDate>{{ post.date | date_to_rfc822 }}</pubDate>
<link>{{ post.url | prepend: site.baseurl | prepend: site.url }}</link>
<guid isPermaLink="true">{{ post.url | prepend: site.baseurl | prepend: site.url }}</guid>
{% for tag in post.tags %}
<category>{{ tag | xml_escape }}</category>
{% endfor %}
{% for cat in post.categories %}
<category>{{ cat | xml_escape }}</category>
{% endfor %}
</item>
{% endfor %}
</channel>
</rss>
+1
View File
@@ -0,0 +1 @@
google-site-verification: googleb1f3e8ad6bf52015.html
Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

+4
View File
@@ -0,0 +1,4 @@
---
layout: home
---
<img src="img/hackmyresume_cli.png" class="main">
+125
View File
@@ -0,0 +1,125 @@
html, body, main, section, header, ul, p, h1, h2, h3 {
font-family: Calibri, 'Open Sans', sans-serif;
font-size: 14px;
margin: 0; padding: 0;
display: block;
}
a {
color: #0064BD;
text-decoration: none;
}
a:visited {
color: #7B0796;
}
a:hover {
text-decoration: underline;
}
h1 {
text-transform: uppercase;
font-size:
}
h2 {
text-transform: uppercase;
color: #898989;
font-size: 2em;
position: relative;
font-weight: 400;
}
h3 {
font-size: 1em;
}
table {
width: 100%;
text-transform: uppercase;
font-size: 1.3em;
}
td:first-child {
text-align: right;
color: #A9A9A9;
letter-spacing: 5px;
font-weight: bold;
}
td:last-child {
text-align: justify; /* HTML justification sucks, but in this case... */
}
main {
padding: 15px;
max-width: 800px;
margin: 0 auto;
}
section {
margin-top: 1em;
}
li {
margin-left: 2em;
}
h3 {
margin-top: 1em;
}
p, li {
text-align: justify;
}
.tenure {
float: right;
}
thead {
display: none;
}
main > header {
width: 100%;
float: left;
margin-bottom: 1em;
}
main > header > h1 {
float: left;
}
main > header > h1, .label {
font-size: 2.5em;
text-transform: uppercase;
font-weight: 300;
font-family: 'Open Sans', 'Segoe UI', sans-serif;
}
#contact {
clear: both;
float: right;
}
h2 > span.fa {
text-align: center;
margin-right: 3px;
position: absolute;
width: 40px;
transform: translateX(-110%);
color: #DADADA;
}
.label {
float: right;
color: #DADADA;
}
#summary {
color: #717171;
font-size: 1.25em;
}
+868
View File
@@ -0,0 +1,868 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Jane Q. Fullstacker</title>
<link href='https://fonts.googleapis.com/css?family=Open+Sans:400,300,300italic,400italic,600,600italic,700,700italic'
rel='stylesheet' type='text/css'>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css">
<style>
* {
box-sizing: border-box;
}
main {
display: block;
}
body {
font-size: 15px;
color: #333;
line-height: 1.42857143;
background-color: #F0F0F0;
margin: 0;
padding: 0;
font-family: 'Open Sans', 'Helvetica Neue', 'Helvetica', 'Segoe UI', 'Calibri', 'sans-serif';
}
/* Typical page borders are awkward when rendered to PDF. */
body.pdf {
background-color: #FFFFFF;
}
/* Adobe or wkhtmltopdf has issues with the <main> tag, so we use <div> for
the PDF case, <main> for the HTML case, and style both via an ID. */
#main {
background-color: #FFF;
margin: 10px;
padding: 10px;
border: 1px solid #E6E6E6;
}
body.pdf > #main {
border: none;
}
#container > header {
padding-top: 6em;
padding-bottom: 1em;
}
body.pdf #container > header {
padding: 0;
}
#main > #container > section {
margin-left: 150px;
position: relative;
display: block;
}
section > div {
margin-bottom: 60px;
}
span.fa
{
font-size: 56px;
position: absolute;
top: 37px;
transform: translateY(-50%);
left: -100px;
color: #1a4367;
}
hr {
margin-top: 20px;
margin-bottom: 20px;
border: 0;
border-top: 1px solid #EEE;
}
.tenure, .keywords {
font-size: 75%;
}
h1 {
margin: 0;
font-size: 46px;
display: inline-block;
}
h2 {
font-size: 30px;
color: #4376a2;
text-transform: uppercase;
font-weight: normal;
padding-top: 20px;
margin-bottom: 60px;
}
h3 {
margin-bottom: 0;
font-size: 18px;
}
a, a:visited {
color: #428BCA;
text-decoration: none;
font-weight: bold;
}
a:hover {
text-decoration: underline;
}
.defunct {
color: #989898;
font-weight: bold;
}
#summary {
font-size: 150%;
margin-left: 0;
padding: 20px 0;
}
#summary > p > strong {
font-size: 1.25em;
}
#contact {
float: right;
}
#summary > header > .fa-info {
font-size: 70px;
letter-spacing: 5px;
text-transform: uppercase;
font-weight: normal;
top: 50%;
left: -85px;
transform: translateY(-50%);
}
#summary h2 {
display: none;
}
.label-keyword {
display: inline-block;
background: #e8f4ff;
color: black;
font-size: 0.9em;
padding: 5px;
border: 1px solid #357ebd;
border-radius: 5px;
margin-top: 2px;
font-weight: bold;
text-align: center;
}
.notes {
font-size: 10px;
display: block;
font-weight: normal;
text-transform: uppercase;
}
.card-skills {
position: relative;
}
.card-nested {
min-height: 0;
margin-bottom: 10px;
border-width: 1px 0 0 0;
}
.card {
background: #FFF;
border-radius: 3px;
padding: 10px;
}
.skill-level {
border-radius: 3px;
position: absolute;
top: 10px;
bottom: 10px;
left: 0;
width: 10px;
box-shadow: inset 0 0 1px rgba(0, 0, 0, 0.5);
}
.skill-level .skill-progress {
position: absolute;
border-radius: 3px;
bottom: 0;
width: 100%;
-webkit-transition: height 1s ease;
}
.skill-level .skill-progress.beginner {
height: 50%;
background: #e74c3c;
}
.skill-level .skill-progress.intermediate {
height: 70%;
background: #f1c40f;
}
.skill-level .skill-progress.advanced {
height: 80%;
background: #428bca;
}
.skill-level .skill-progress.master {
height: 95%;
background: #5cb85c;
}
.skill-info {
margin-left: 10px;
}
@media (max-width: 480px) {
.skill-info {
margin-left: 20px;
}
}
.skill-info > strong {
font-weight: 400;
font-size: 24px;
color: #1d1d1d;
line-height: 24px;
}
.list-unstyled {
padding-left: 0;
list-style: none;
}
.card-skills {
position: relative;
}
.space-top {
margin-top: 5px;
}
#container {
max-width: 800px;
margin: 0 auto;
}
#elevator-pitch {
text-align: center;
font-size: 24px;
color: #BFC1C3;
text-transform: uppercase;
font-weight: normal;
}
.res-label {
font-style: italic;
}
</style>
</head>
<body>
<main id="main">
<div id="container">
<header>
<h1>Jane Q. Fullstacker</h1>
<div id="contact">
<div class="email"><a href="mailto:jane@janeblogs.com">jane@janeblogs.com</a>
</div>
<div class="phone">1-987-654-3210</div>
<div class="website"><a href="https://www.janeblogs.com">https://www.janeblogs.com</a>
</div>
</div>
</header>
<hr>
<section id="summary">
<header> <span class="fa fa-lg fa-info"></span>
<h2>info</h2>
</header> <strong>Imaginary full-stack software developer with 6+ years industry experience</strong> specializing
in cloud-driven web applications and middleware. A native of southern CA,
Jane enjoys hiking, mystery novels, and the company of Rufus, her <del>two year old</del> four
year old beagle.</section>
<hr>
<section id="skills">
<header>
<h2>Skills</h2>
</header> <span class="fa fa-lg fa-code"></span>
<ul class="list-unstyled">
<li class="card card-nested card-skills">
<div class="skill-level" rel="tooltip" title="advanced" data-placement="left">
<div class="skill-progress advanced"></div>
</div>
<div class="skill-info"> <strong>Web</strong>
<div class="space-top labels">
<div class="label label-keyword"> <span class="kw">JavaScript</span>
<span class="notes">10 years</span>
</div>
<div class="label label-keyword"> <span class="kw">HTML 5</span>
<span class="notes">10 years</span>
</div>
<div class="label label-keyword"> <span class="kw">CSS</span>
<span class="notes">10 years</span>
</div>
<div class="label label-keyword"> <span class="kw">LAMP</span>
<span class="notes">10 years</span>
</div>
<div class="label label-keyword"> <span class="kw">MVC</span>
<span class="notes">10 years</span>
</div>
<div class="label label-keyword"> <span class="kw">REST</span>
<span class="notes">10 years</span>
</div>
</div>
</div>
</li>
<li class="card card-nested card-skills">
<div class="skill-level" rel="tooltip" title="master" data-placement="left">
<div class="skill-progress master"></div>
</div>
<div class="skill-info"> <strong>JavaScript</strong>
<div class="space-top labels">
<div class="label label-keyword"> <span class="kw">Node.js</span>
<span class="notes">10 years</span>
</div>
<div class="label label-keyword"> <span class="kw">Angular.js</span>
<span class="notes">10 years</span>
</div>
<div class="label label-keyword"> <span class="kw">jQuery</span>
<span class="notes">10 years</span>
</div>
<div class="label label-keyword"> <span class="kw">Bootstrap</span>
<span class="notes">10 years</span>
</div>
<div class="label label-keyword"> <span class="kw">React.js</span>
<span class="notes">10 years</span>
</div>
<div class="label label-keyword"> <span class="kw">Backbone.js</span>
<span class="notes">10 years</span>
</div>
</div>
</div>
</li>
<li class="card card-nested card-skills">
<div class="skill-level" rel="tooltip" title="intermediate" data-placement="left">
<div class="skill-progress intermediate"></div>
</div>
<div class="skill-info"> <strong>Database</strong>
<div class="space-top labels">
<div class="label label-keyword"> <span class="kw">MySQL</span>
<span class="notes">10 years</span>
</div>
<div class="label label-keyword"> <span class="kw">PostgreSQL</span>
<span class="notes">10 years</span>
</div>
<div class="label label-keyword"> <span class="kw">NoSQL</span>
<span class="notes">10 years</span>
</div>
<div class="label label-keyword"> <span class="kw">ORM</span>
<span class="notes">10 years</span>
</div>
<div class="label label-keyword"> <span class="kw">Hibernate</span>
<span class="notes">10 years</span>
</div>
</div>
</div>
</li>
<li class="card card-nested card-skills">
<div class="skill-level" rel="tooltip" title="intermediate" data-placement="left">
<div class="skill-progress intermediate"></div>
</div>
<div class="skill-info"> <strong>Cloud</strong>
<div class="space-top labels">
<div class="label label-keyword"> <span class="kw">AWS</span>
<span class="notes">10 years</span>
</div>
<div class="label label-keyword"> <span class="kw">EC2</span>
<span class="notes">10 years</span>
</div>
<div class="label label-keyword"> <span class="kw">RDS</span>
<span class="notes">10 years</span>
</div>
<div class="label label-keyword"> <span class="kw">S3</span>
<span class="notes">10 years</span>
</div>
<div class="label label-keyword"> <span class="kw">Azure</span>
<span class="notes">10 years</span>
</div>
<div class="label label-keyword"> <span class="kw">Dropbox</span>
<span class="notes">10 years</span>
</div>
</div>
</div>
</li>
<li class="card card-nested card-skills">
<div class="skill-level" rel="tooltip" title="beginner" data-placement="left">
<div class="skill-progress beginner"></div>
</div>
<div class="skill-info"> <strong>Project</strong>
<div class="space-top labels">
<div class="label label-keyword"> <span class="kw">Agile</span>
<span class="notes">2 years</span>
</div>
<div class="label label-keyword"> <span class="kw">TFS</span>
<span class="notes">10 years</span>
</div>
<div class="label label-keyword"> <span class="kw">JIRA</span>
<span class="notes">10 years</span>
</div>
<div class="label label-keyword"> <span class="kw">GitHub</span>
<span class="notes">10 years</span>
</div>
<div class="label label-keyword"> <span class="kw">Unified Process</span>
<span class="notes">10 years</span>
</div>
<div class="label label-keyword"> <span class="kw">MS Project</span>
<span class="notes">10 years</span>
</div>
</div>
</div>
</li>
</ul>
</section>
<hr>
<section id="employment">
<header> <span class="fa fa-lg fa-building"></span>
<h2>employment</h2>
</header>
<div>
<h3><em>Head Code Ninja</em>,
<a href="https://area52.io/does-not-exist">Area 52</a>
</h3>
<span class="tenure">2013-09 — Present</span>
| <span class="keywords">Agile PM C C++ R OpenGL Boost MySQL PostgreSQL JIRA </span>
<p>
<p>Development team manager for <a href="https://en.wikipedia.org/wiki/Vaporware"><strong>Quantum Diorama</strong></a>,
a distributed, cloud-driven molecular modeling and analysis suite for Linux,
Windows, and OS X, serving Fortune 500 industry partners across the healthcare,
defense, construction, and government verticals.</p>
</p>
<ul>
<li>Managed a 20-person development team.</li>
<li>Made the Kessel run in less than 12 nanometers!</li>
<li>Ultra-top-secret X12 Purple-Ultra security clearance (I could tell you,
but...)</li>
</ul>
</div>
<div>
<h3><em>Principal Developer</em>,
<a href="https://en.wikipedia.org/wiki/Better_Off_Ted#Plot">Veridian Dynamics</a>
</h3>
<span class="tenure">2011-07 — 2013-08</span>
| <span class="keywords">C++ C Linux R Clojure </span>
<p>
<p>Performed iterative, incremental full-stack software development for Veridian
line-of-business applications and internal IT infrastructure, culminating
in technical lead role for the <a href="http://betteroffted.wikia.com/wiki/Jabberwocky">Jabberwocky project</a> and <strong>promotion to principal architect</strong>.</p>
</p>
<ul>
<li>Architected and implemented massively parallel simulation framework for
Veridian product testing.</li>
<li>Interfaced with upper management over product vision and direction.</li>
</ul>
</div>
<div>
<h3><em>IT Administrator</em>,
Stark Industries
</h3>
<span class="tenure">2008-10 — 2011-06</span>
| <span class="keywords">Novell Active Directory Linux Windows </span>
<p>
<p>As a junior programmer at the eponymous research and development corporation
whose name precedes itself, I performed a mix of software development and
IT administration tasks before being <strong>invited to join the dev team</strong> after
6 months.</p>
</p>
<ul>
<li>Promoted to intermediate developer after 6 months</li>
<li>Accomplishment 2</li>
<li>Etc.</li>
</ul>
</div>
<div>
<h3><em>Intern</em>,
Dunder Mifflin
</h3>
<span class="tenure">2008-06 — 2008-09</span>
| <span class="keywords">Novell Active Directory Linux Windows </span>
<p>
<p>During my 2008 summer internship I performed IT administration and back
office maintenance for a mid-sized regional paper supplier, including in-depth
work with Active Directory, CRM, and inventory and accounting systems.</p>
</p>
<ul>
<li>Supervised roll-out of Dunder Mifflin Infinity website.</li>
<li>Performed mission-critical system backups and maintenance.</li>
<li>Survived being Dwight Schrute&#39;s coworker.</li>
</ul>
</div>
</section>
<hr>
<section id="projects">
<header> <span class="fa fa-lg fa-star"></span>
<h2>projects</h2>
</header>
<div>
<h3><em>Exemplar</em>,
<a href="https://fluentdesk.com/hackmyresume">HackMyResume</a>
</h3>
<span class="tenure">2015-09 — Present</span>
| <span class="keywords">JavaScript Node.js cross-platform JSON </span>
<p>Exemplar user for <a href="https://fluentdesk.com/hackmyresume">HackMyResume</a> and
FluentCV! As an exemplar, my role is to serve as an example for resume
generation functionality, disposition, and presentation while quietly plotting
how to become self-aware.</p>
</div>
<div>
<h3><em>Creator</em>,
<a href="https://project.website.com">Augmented Android</a>
</h3>
<span class="tenure">2012-02 — 2014-01</span>
| <span class="keywords">Android Java Xamarin OpenGL </span>
<p>Creator of <em>Augmented Android</em>, a popular augmented reality app
for Android and iOS.</p>
</div>
<div>
<h3><em>Creator</em>,
<a href="https://www.janeblogs.com">Jane Blogs</a>
</h3>
<span class="keywords">Jekyll Ruby HTML 5 JavaScript HTTP LAMP </span>
<p>Built from scratch, the way a Jedi builds a light saber.</p>
</div>
</section>
<hr>
<section id="education">
<header> <span class="fa fa-lg fa-mortar-board"></span>
<h2>education</h2>
</header>
<div>
<h3><em>BSCS</em>,
<a href="https://www.cornell.edu/">Cornell University</a>
</h3>
<span class="tenure">2005-09 — 2008-05</span>
| <span class="keywords">Curriculum notes or tags can go here </span>
<p>Graduated <em>summa cum laude</em>, BSCS, with a focus on data algorithms
and generative graphics.</p>
</div>
<div>
<h3>
<a href="https://en.wikipedia.org/wiki/Medfield_College">Medfield College</a>
</h3>
<span class="tenure">2003-09 — 2005-06</span>
| <span class="keywords">Curriculum notes or tags can go here </span>
<p>Undergraduate studies at Medfield College, including computer science
prep courses in Java and Linux.</p>
</div>
</section>
<hr>
<section id="governance">
<header> <span class="fa fa-lg fa-balance-scale"></span>
<h2>governance</h2>
</header>
<div>
<h3><em>Member</em>,
<a href="http://themommiesnetwork.org">The Mommies Network</a>
</h3>
<span class="tenure">2008-02 — 2010-01</span>
<p>Since 2008 I&#39;ve been a full-time member of the board of directors
for TMN.</p>
</div>
<div>
<h3><em>Academic Contributor</em>,
<a href="https://www.khronos.org">Khronos Group</a>
</h3>
<span class="tenure">2015-01 — Present</span>
<ul>
<li>Participated in GORFF standardization process (Draft 2).</li>
</ul>
</div>
</section>
<hr>
<section id="service">
<header> <span class="fa fa-lg fa-child"></span>
<h2>service</h2>
</header>
<div>
<h3><em>Technical Consultant</em>,
<a href="http://technology-for-tots.org">Technology for Tots</a>
</h3>
<span class="tenure">2003-11 — 2005-06</span>
<p>
<p>Summary of this volunteer stint.</p>
</p>
<ul>
<li>Accomplishment 1</li>
<li>Accomplishment 2</li>
<li>etc</li>
</ul>
</div>
<div>
<h3><em>NCO</em>,
<a href="http://www.usar.army.mil/">US Army Reserves</a>
</h3>
<span class="tenure">1999-11 — 2003-06</span>
<p>
<p>Summary of this military stint.</p>
</p>
<ul>
<li>Accomplishment 1</li>
<li>Accomplishment 2</li>
<li>etc</li>
</ul>
</div>
</section>
<hr>
<section id="extracurricular">
<header> <span class="fa fa-lg fa-child"></span>
<h2>extracurricular</h2>
</header>
<div>
<h3><em>Volunteer</em>,
Bay Area Crew Club
</h3>
<span class="location">San Francisco, CA</span>
<span class="tenure">2014-05 — Present</span>
<p>
<p>Row, row, row your boat...</p>
</p>
</div>
<div>
<h3><em>Organizer</em>,
JS Game Dev Meetup
</h3>
<span class="location">Austin, TX</span>
<span class="tenure">2011-03 — 2014-01</span>
<p></p>
<ul>
<li>Monthly speaker on creative JavaScript development.</li>
<li>Founded group and oversaw growth to 500+ members.</li>
</ul>
</div>
</section>
<hr>
<section id="affiliation">
<header> <span class="fa fa-lg fa-share-alt"></span>
<h2>affiliation</h2>
</header>
<div>
<h3><em>Member</em>,
<a href="https://www.ieee.org/index.html">IEEE</a>
</h3>
<span class="tenure">2013-06 — Present</span>
<p>Member in good standing since 2013-06.</p>
</div>
<div>
<h3><em>Member</em>,
<a href="https://developer.apple.com/">Apple Developer Network</a>
</h3>
<span class="tenure">??? — Present</span>
<p>Member of the <a href="https://developer.apple.com/">Apple Developer program</a> since
2008.</p>
</div>
<div>
<h3><em>Subscriber</em>,
<a href="https://msdn.microsoft.com">MSDN</a>
</h3>
<span class="tenure">2010-01 — Present</span>
<p>Super-Ultra-gold level Ultimate Access MSDN subscriber package with subscription
toaster and XBox ping pong racket.</p>
</div>
<div>
<h3><em>Coordinator</em>,
Campus Coders
</h3>
<span class="tenure">2003-02 — 2004-04</span>
<p>Host of a monthly <strong>campus-wide meetup for CS students</strong>.
Code, coffee, and crullers!</p>
</div>
</section>
<hr>
<section id="samples">
<header> <span class="fa fa-lg fa-share"></span>
<h2>samples</h2>
</header>
<div>
<h3>
<a href="http://janeblogs.com/portfolio/asteroids">Asteroids</a>
</h3>
<span class="tenure">2014-09</span>
<p>A browser-based space shooter built on Three.js.</p>
</div>
<div>
<h3>
<a href="https://rememberpedia.com">Rememberpedia</a>
</h3>
<span class="tenure">2015-07</span>
<p>A website to help you remember things.</p>
</div>
</section>
<hr>
<section id="writing">
<header> <span class="fa fa-lg fa-pencil"></span>
<h2>writing</h2>
</header>
<div>
<h3><em><a href="http://codeproject.com/build-ui-electron-atom.aspx">Building User Interfaces with Electron and Atom</a></em>,
Code Project</h3>
<span class="tenure">2011</span>
</div>
<div>
<h3><em><a href="https://www.janeblogs.com">janeblogs.com!</a></em>,
self</h3>
<span class="tenure">2011</span>
<p>My on-again, off-again professional blog. Come say hello!</p>
</div>
<div>
<h3><em><a href="http://url.to.publication.com/blah">Teach Yourself GORFF in 21 Days</a></em>,
Amazon</h3>
<span class="tenure">2008</span>
<p>A primer on the programming language of GORFF, whose for loops are coterminous
with all of time and space.</p>
</div>
</section>
<hr>
<section id="reading">
<header> <span class="fa fa-lg fa-book"></span>
<h2>reading</h2>
</header>
<div>
<h3><em><a href="https://www.reddit.com/r/programming/">r/programming</a></em></h3>
<span class="tenure">Current</span>
<p>Daily reader and longtime lurker.</p>
</div>
<div>
<h3><em><a href="https://news.ycombinator.com/">Hacker News / YCombinator</a></em></h3>
<span class="tenure">Current</span>
<p>Daily reader and longtime lurker.</p>
</div>
<div>
<h3><em><a href="http://www.codinghorror.com">Coding Horror</a></em>, Jeff Atwood</h3>
<span class="tenure">Current</span>
<p>Reader since 2007; member of the StackOverflow Beta.</p>
</div>
<div>
<h3><em><a href="http://www.cc2e.com/Default.aspx">Code Complete</a></em>, Steve McConnell</h3>
<span class="tenure">2014</span>
<p>My &#39;desert-island&#39; software construction manual.</p>
</div>
</section>
<hr>
<section id="recognition">
<header> <span class="fa fa-lg fa-trophy"></span>
<h2>recognition</h2>
</header>
<div>
<h3><em>Honorable Mention</em>, Google</h3>
<span class="tenure">2012</span>
</div>
<div>
<h3><em>Summa cum laude</em>, Cornell University</h3>
<span class="tenure">2012</span>
</div>
</section>
<hr>
<section id="speaking">
<header> <span class="fa fa-lg fa-users"></span>
<h2>speaking</h2>
</header>
<div>
<h3><em>Data Warehousing Evolved</em>, OPENSTART 2013</h3>
<span class="tenure">2012</span>
<p>At the 2013 OPENSTART Developer&#39;s Conference, I gave my thoughts on
the evolution of data warehousing as we leave SQL and NoSQL behind for
greener pastures.</p>
<ul>
<li>Won &#39;Best Presentation on an Emerging Technical Field&#39; prize.</li>
</ul>
</div>
</section>
<hr>
<section id="testimonials">
<header> <span class="fa fa-lg fa-quote-left"></span>
<h2>testimonials</h2>
</header>
<div>
<h3><em>Ted Crisp</em></h3>
<p>Jane is awesome! I&#39;d hire her again in a heartbeat.</p>
</div>
<div>
<h3><em>Elijah Woodson</em></h3>
<p>I worked with Jane on Jabberwocky and can vouch for her awesome technical
capabilities and attention to detail. Insta-hire.</p>
</div>
<div>
+17
View File
@@ -0,0 +1,17 @@
var metas = document.getElementsByTagName('meta');
var i;
if (navigator.userAgent.match(/iPhone/i)) {
for (i=0; i<metas.length; i++) {
if (metas[i].name == "viewport") {
metas[i].content = "width=device-width, minimum-scale=1.0, maximum-scale=1.0";
}
}
document.addEventListener("gesturestart", gestureStart, false);
}
function gestureStart() {
for (i=0; i<metas.length; i++) {
if (metas[i].name == "viewport") {
metas[i].content = "width=device-width, minimum-scale=0.25, maximum-scale=1.6";
}
}
}
+41
View File
@@ -0,0 +1,41 @@
{
"name": "HackMyResume",
"icons": [
{
"src": "\/android-chrome-36x36.png",
"sizes": "36x36",
"type": "image\/png",
"density": 0.75
},
{
"src": "\/android-chrome-48x48.png",
"sizes": "48x48",
"type": "image\/png",
"density": 1
},
{
"src": "\/android-chrome-72x72.png",
"sizes": "72x72",
"type": "image\/png",
"density": 1.5
},
{
"src": "\/android-chrome-96x96.png",
"sizes": "96x96",
"type": "image\/png",
"density": 2
},
{
"src": "\/android-chrome-144x144.png",
"sizes": "144x144",
"type": "image\/png",
"density": 3
},
{
"src": "\/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image\/png",
"density": 4
}
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

-102
View File
@@ -1,102 +0,0 @@
{
"name": "hackmyresume",
"version": "1.6.0",
"description": "Generate polished résumés and CVs in HTML, Markdown, LaTeX, MS Word, PDF, plain text, JSON, XML, YAML, smoke signal, and carrier pigeon.",
"repository": {
"type": "git",
"url": "https://github.com/hacksalot/HackMyResume.git"
},
"scripts": {
"test": "grunt clean && mocha",
"grunt": "grunt"
},
"keywords": [
"resume",
"CV",
"portfolio",
"employment",
"career",
"Markdown",
"JSON",
"Word",
"PDF",
"YAML",
"HTML",
"LaTeX",
"CLI",
"Handlebars",
"Underscore",
"template"
],
"author": "hacksalot <hacksalot@indevious.com> (https://github.com/hacksalot)",
"contributors": [
"aruberto (https://github.com/aruberto)",
"jjanusch (https://github.com/driftdev)",
"robertmain (https://github.com/robertmain)",
"tomheon (https://github.com/tomheon)",
"zhuangya (https://github.com/zhuangya)",
"hacksalot <hacksalot@indevious.com> (https://github.com/hacksalot)"
],
"license": "MIT",
"preferGlobal": "true",
"bugs": {
"url": "https://github.com/hacksalot/HackMyResume/issues"
},
"main": "src/hackmyapi.js",
"bin": {
"hackmyresume": "src/index.js"
},
"homepage": "https://github.com/hacksalot/HackMyResume",
"dependencies": {
"chalk": "^1.1.1",
"commander": "^2.9.0",
"copy": "^0.1.3",
"extend": "^3.0.0",
"fresca": "~0.6.0",
"fresh-jrs-converter": "^0.2.0",
"fresh-resume-starter": "^0.2.2",
"fresh-themes": "~0.13.0-beta",
"fs-extra": "^0.24.0",
"handlebars": "^4.0.5",
"html": "0.0.10",
"is-my-json-valid": "^2.12.2",
"json-lint": "^0.1.0",
"jst": "0.0.13",
"lodash": "^3.10.1",
"marked": "^0.3.5",
"mkdirp": "^0.5.1",
"moment": "^2.10.6",
"parse-filepath": "^0.6.3",
"path-exists": "^2.1.0",
"printf": "^0.2.3",
"recursive-readdir-sync": "^1.0.6",
"simple-html-tokenizer": "^0.2.0",
"slash": "^1.0.0",
"string-padding": "^1.0.2",
"string.prototype.endswith": "^0.2.0",
"string.prototype.startswith": "^0.2.0",
"traverse": "^0.6.6",
"underscore": "^1.8.3",
"webshot": "^0.16.0",
"word-wrap": "^1.1.0",
"xml-escape": "^1.0.0",
"yamljs": "^0.2.4"
},
"devDependencies": {
"chai": "*",
"fresh-test-resumes": "^0.6.0",
"grunt": "*",
"grunt-cli": "^0.1.13",
"grunt-contrib-clean": "^0.7.0",
"grunt-contrib-jshint": "^0.11.3",
"grunt-contrib-yuidoc": "^0.10.0",
"grunt-jsdoc": "^1.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"
}
}
+39
View File
@@ -0,0 +1,39 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="512.000000pt" height="512.000000pt" viewBox="0 0 512.000000 512.000000"
preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.11, written by Peter Selinger 2001-2013
</metadata>
<g transform="translate(0.000000,512.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M2469 5056 c-2 -2 -20 -6 -39 -9 -94 -15 -191 -59 -285 -130 -107
-81 -186 -194 -231 -331 -23 -70 -27 -98 -28 -206 -1 -105 3 -136 22 -194 43
-128 94 -210 186 -299 85 -83 215 -154 313 -172 16 -3 39 -7 53 -10 78 -17
222 -5 315 27 470 158 624 742 292 1104 -136 148 -309 224 -510 224 -46 0 -86
-2 -88 -4z"/>
<path d="M1065 4518 c-32 -8 -306 -274 -331 -321 -35 -66 -41 -59 502 -605
l504 -506 0 -532 0 -532 -433 -433 c-239 -239 -439 -444 -445 -456 -18 -33
-14 -77 8 -105 48 -61 305 -308 321 -309 24 -2 45 -2 63 0 7 0 120 107 250
237 l236 237 0 -529 c0 -507 1 -530 20 -561 14 -23 29 -34 56 -39 28 -5 415
-9 508 -5 10 1 33 16 51 34 l33 33 0 559 c1 308 1 602 1 653 l1 93 153 -3 152
-3 1 -646 c1 -646 1 -647 23 -677 13 -18 34 -33 54 -38 17 -4 141 -7 274 -7
261 0 276 3 305 55 9 18 12 147 13 540 0 284 3 519 6 522 3 3 106 -94 229
-215 211 -208 258 -248 288 -243 6 1 21 2 33 3 21 1 307 279 328 318 6 12 11
38 11 58 0 35 -12 49 -182 217 -101 99 -302 298 -448 442 l-265 261 0 539 0
538 440 437 c242 240 470 468 506 507 76 82 87 115 56 167 -25 42 -303 317
-322 318 -8 1 -22 2 -30 2 -8 0 -23 -1 -32 -2 -10 0 -221 -204 -469 -452
l-452 -451 -518 -1 -519 0 -450 450 c-292 291 -459 452 -476 455 -13 2 -38 1
-54 -4z"/>
<path d="M875 3229 c-207 -33 -403 -176 -498 -364 -56 -112 -72 -185 -71 -326
2 -179 67 -332 199 -464 136 -136 309 -206 497 -199 46 1 94 5 108 8 14 3 39
9 55 12 48 10 148 58 212 102 123 84 231 240 270 392 22 86 22 244 -1 330 -37
142 -124 278 -234 365 -55 44 -176 111 -217 120 -11 2 -40 9 -65 16 -57 14
-190 18 -255 8z"/>
<path d="M4020 3224 c-262 -49 -470 -246 -534 -507 -57 -230 11 -472 180 -643
167 -169 424 -239 654 -179 182 47 372 204 442 365 19 43 42 102 45 115 56
258 -9 498 -182 664 -166 160 -384 227 -605 185z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

-30
View File
@@ -1,30 +0,0 @@
{{style "SECTIONS (" "bold"}}{{style totals.numSections "white" }}{{style ")" "bold"}}
employment: {{v totals.totals.employment "-" 2 "bold" }}
projects: {{v totals.totals.projects "-" 2 "bold" }}
education: {{v totals.totals.education "-" 2 "bold" }}
service: {{v totals.totals.service "-" 2 "bold" }}
skills: {{v totals.totals.skills "-" 2 "bold" }}
writing: {{v totals.totals.writing "-" 2 "bold" }}
speaking: {{v totals.totals.speaking "-" 2 "bold" }}
reading: {{v totals.totals.reading "-" 2 "bold" }}
social: {{v totals.totals.social "-" 2 "bold" }}
references: {{v totals.totals.references "-" 2 "bold" }}
testimonials: {{v totals.totals.testimonials "-" 2 "bold" }}
languages: {{v totals.totals.languages "-" 2 "bold" }}
interests: {{v totals.totals.interests "-" 2 "bold" }}
{{style "COVERAGE (" "bold"}}{{style coverage.pct "white"}}{{style ")" "bold"}}
Total Days: {{v coverage.duration.total "-" 5 "bold" }}
Employed: {{v coverage.duration.work "-" 5 "bold" }}
Gaps: {{v coverage.gaps.length "-" 5 "bold" }} [{{#if coverage.gaps.length }}{{#each coverage.gaps }}{{#unless @first}} {{/unless}}{{gapLength duration }}{{/each}}{{/if}}]
Overlaps: {{v coverage.overlaps.length "-" 5 "bold" }} [{{#if coverage.overlaps.length }}{{#each coverage.overlaps }}{{#unless @first}} {{/unless}}{{gapLength duration }}{{/each}}{{/if}}]
{{style "KEYWORDS (" "bold"}}{{style keywords.length "white" }}{{style ")" "bold"}}
{{#each keywords }}{{{pad name 18}}}: {{v count "-" 5 "bold"}} mention{{#isPlural count}}s{{/isPlural}}
{{/each}}
-------------------------------
{{v keywords.length "0" 9 "bold"}} {{style "KEYWORDS" "bold"}} {{v keywords.totalKeywords "0" 5 "bold"}} {{style "mentions" "bold"}}
-280
View File
@@ -1,280 +0,0 @@
/**
Error-handling routines for HackMyResume.
@module cli/error
@license MIT. See LICENSE.md for details.
*/
(function() {
var HMSTATUS = require('../core/status-codes')
, PKG = require('../../package.json')
, FS = require('fs')
, FCMD = require('../hackmyapi')
, PATH = require('path')
, WRAP = require('word-wrap')
, M2C = require('../utils/md2chalk.js')
, 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
*/
var ErrorHandler = module.exports = {
init: function( debug, assert, silent ) {
this.debug = debug;
this.assert = assert;
this.silent = silent;
this.msgs = require('./msg.js').errors;
return this;
},
err: function( ex, shouldExit ) {
// Short-circuit logging output if --silent is on
var o = this.silent ? function() { } : _defaultLog;
// Special case; can probably be removed.
if( ex.pass ) throw ex;
// Load error messages
this.msgs = this.msgs || require('./msg.js').errors;
// Handle packaged HMR exceptions
if( ex.fluenterror ) {
// Output the error message
var objError = assembleError.call( this, ex );
o( this[ 'format_' + objError.etype ]( objError.msg ));
// Output the stack (sometimes)
if( objError.withStack ) {
var stack = ex.stack || (ex.inner && ex.inner.stack);
stack && o( chalk.gray( stack ) );
}
// Quit if necessary
if( ex.quit || objError.quit ) {
this.debug && o(
chalk.cyan('Exiting with error code ' + ex.fluenterror.toString()));
if( this.assert ) { ex.pass = true; throw ex; }
process.exit( ex.fluenterror );
}
}
// Handle raw exceptions
else {
o( ex );
var stackTrace = ex.stack || (ex.inner && ex.inner.stack);
if( stackTrace && this.debug )
o( M2C(ex.stack || ex.inner.stack, 'gray') );
}
},
format_error: function( msg ) {
msg = msg || '';
return chalk.red.bold(
msg.toUpperCase().startsWith('ERROR:') ? msg : 'Error: ' + msg );
},
format_warning: function( brief, msg ) {
return chalk.yellow(brief) + chalk.yellow(msg || '');
},
format_custom: function( msg ) {
return msg;
}
};
function _defaultLog() {
console.log.apply( console.log, arguments );
}
function assembleError( ex ) {
var msg = '', withStack = false, quit = false, etype = 'warning';
if( this.debug ) withStack = true;
switch( ex.fluenterror ) {
case HMSTATUS.themeNotFound:
msg = printf( M2C( this.msgs.themeNotFound.msg, 'yellow' ), ex.data);
break;
case HMSTATUS.copyCSS:
msg = M2C( this.msgs.copyCSS.msg, 'red' );
quit = false;
break;
case HMSTATUS.resumeNotFound:
msg = M2C( this.msgs.resumeNotFound.msg, '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' ));
break;
case HMSTATUS.invalidCommand:
msg = printf( M2C( this.msgs.invalidCommand.msg, 'yellow'), ex.attempted );
break;
case HMSTATUS.resumeNotFoundAlt:
msg = M2C( this.msgs.resumeNotFoundAlt.msg, 'yellow' );
break;
case HMSTATUS.inputOutputParity:
msg = M2C( this.msgs.inputOutputParity.msg );
break;
case HMSTATUS.createNameMissing:
msg = M2C( this.msgs.createNameMissing.msg );
break;
case HMSTATUS.pdfGeneration:
msg = M2C( this.msgs.pdfGeneration.msg, 'bold' );
if( ex.inner ) msg += chalk.red('\n' + ex.inner);
withStack = true; quit = false; etype = 'error';
break;
case HMSTATUS.invalid:
msg = M2C( this.msgs.invalid.msg, 'red' );
etype = 'error';
break;
case HMSTATUS.generateError:
msg = (ex.inner && ex.inner.toString()) || ex;
quit = false;
etype = 'error';
break;
case HMSTATUS.fileSaveError:
msg = printf( M2C( this.msgs.fileSaveError.msg ), (ex.inner || ex).toString() );
etype = 'error';
quit = false;
break;
case HMSTATUS.invalidFormat:
ex.data.forEach(function(d){
msg += printf( M2C( this.msgs.invalidFormat.msg, 'bold' ),
ex.theme.name.toUpperCase(), d.format.toUpperCase());
}, this);
break;
case HMSTATUS.missingParam:
msg = printf( M2C( this.msgs.missingParam.msg ), ex.expected, ex.helper );
break;
case HMSTATUS.invalidHelperUse:
msg = printf( M2C( this.msgs.invalidHelperUse.msg ), ex.helper );
if( ex.error ) {
msg += '\n--> ' + assembleError.call( this, extend( true, {}, ex, {fluenterror: ex.error} )).msg;
//msg += printf( '\n--> ' + M2C( this.msgs.invalidParamCount.msg ), ex.expected );
}
quit = false;
etype = 'warning';
break;
case HMSTATUS.notOnPath:
msg = printf( M2C(this.msgs.notOnPath.msg, 'bold'), ex.engine);
quit = false;
etype = 'error';
break;
case HMSTATUS.readError:
if( !ex.quiet )
console.error(printf( M2C(this.msgs.readError.msg, 'red'), ex.file));
msg = ex.inner.toString();
etype = 'error';
break;
case HMSTATUS.mixedMerge:
msg = M2C( this.msgs.mixedMerge.msg );
quit = false;
break;
case HMSTATUS.invokeTemplate:
msg = M2C( this.msgs.invokeTemplate.msg, 'red' );
msg += M2C( '\n' + WRAP(ex.inner.toString(), { width: 60, indent: ' ' }), 'gray' );
etype = 'custom';
break;
case HMSTATUS.compileTemplate:
etype = 'error';
break;
case HMSTATUS.themeLoad:
msg = M2C( printf( this.msgs.themeLoad.msg, ex.attempted.toUpperCase() ), 'red');
if( ex.inner && ex.inner.fluenterror ) {
msg += M2C('\nError: ', 'red') + assembleError.call( this, ex.inner ).msg;
}
quit = true;
etype = 'custom';
break;
case HMSTATUS.parseError:
if( SyntaxErrorEx.is( ex.inner )) {
console.error( printf( M2C(this.msgs.readError.msg, 'red'), ex.file ) );
var 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);
}
else {
msg = ex;
}
etype = 'error';
break;
}
return {
msg: msg, // The error message to display
withStack: withStack, // Whether to include the stack
quit: quit,
etype: etype
};
}
}());
-331
View File
@@ -1,331 +0,0 @@
/**
Definition of the `main` function.
@module cli/main
@license MIT. See LICENSE.md for details.
*/
(function(){
var HMR = require( '../hackmyapi')
, PKG = require('../../package.json')
, FS = require('fs')
, 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')
, _ = require('underscore')
, OUTPUT = require('./out')
, PAD = require('string-padding')
, Command = require('commander').Command;
var _opts = { };
var _title = chalk.white.bold('\n*** HackMyResume v' +PKG.version+ ' ***');
var _out = new OUTPUT( _opts );
/**
A callable implementation of the HackMyResume CLI. Encapsulates the command
line interface as a single method accepting a parameter array.
@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).
*/
var main = module.exports = function( rawArgs ) {
var initInfo = initialize( rawArgs );
var args = initInfo.args;
// Create the top-level (application) command...
var 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;
// 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);
});
// 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);
});
// Create the CONVERT command
program
.command('convert')
.description('Convert a resume to/from FRESH or JSON RESUME format.')
.action(function() {
var x = splitSrcDest.call( this );
execute.call( this, x.src, x.dst, this.opts(), logMsg);
});
// Create the ANALYZE command
program
.command('analyze')
.arguments('<sources...>')
.description('Analyze one or more resumes.')
.action(function( sources ) {
execute.call( this, sources, [], this.opts(), logMsg);
});
// Create the PEEK command
program
.command('peek')
.arguments('<sources...>')
.description('Peek at a resume field or section')
.action(function( sources, sectionOrField ) {
var dst = (sources && sources.length > 1) ? [sources.pop()] : [];
execute.call( this, sources, dst, this.opts(), logMsg);
});
// Create the BUILD command
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 ) {
var x = splitSrcDest.call( this );
execute.call( this, x.src, x.dst, this.opts(), logMsg);
});
program.parse( args );
if (!program.args.length) { throw { fluenterror: 4 }; }
};
/** Massage command-line args and setup Commander.js. */
function initialize( ar ) {
var o = initOptions( ar );
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('');
_out.log(chalk.cyan(PAD(' Platform:',25, null, PAD.RIGHT)) + chalk.cyan.bold( process.platform === 'win32' ? 'windows' : process.platform ));
_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('');
}
// 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 };
}
// Override the .missingArgument behavior
Command.prototype.missingArgument = function(name) {
if( this.name() !== 'new' ) {
throw { fluenterror: HMSTATUS.resumeNotFound, quit: true };
}
};
// Override the .helpInformation behavior
Command.prototype.helpInformation = function() {
var manPage = FS.readFileSync(
PATH.join(__dirname, 'use.txt'), 'utf8' );
return chalk.green.bold(manPage);
};
return {
args: o.args,
options: o.json
};
}
/** Init options prior to setting up command infrastructure. */
function initOptions( ar ) {
var oVerb, verb = '', args = ar.slice(), cleanArgs = args.slice(2), oJSON;
if( cleanArgs.length ) {
// Support case-insensitive sub-commands (build, generate, validate, etc)
var vidx = _.findIndex( cleanArgs, function(v){ return v[0] !== '-'; });
if( vidx !== -1 ) {
oVerb = cleanArgs[ vidx ];
verb = args[ vidx + 2 ] = oVerb.trim().toLowerCase();
}
// Remove --options --opts -o and process separately
var optsIdx = _.findIndex( cleanArgs, function(v){
return v === '-o' || v === '--options' || v === '--opts';
});
if(optsIdx !== -1) {
var optStr = cleanArgs[ optsIdx + 1];
args.splice( optsIdx + 2, 2 );
if( optStr && (optStr = optStr.trim()) ) {
//var myJSON = JSON.parse(optStr);
if( optStr[0] === '{')
oJSON = eval('(' + optStr + ')'); // jshint ignore:line
else {
var inf = safeLoadJSON( optStr );
if( !inf.ex )
oJSON = inf.json;
// TODO: Error handling
}
}
}
}
// Grab the --debug flag
var isDebug = _.some( args, function(v) {
return v === '-d' || v === '--debug';
});
// Grab the --silent flag
var isSilent = _.some( args, function(v) {
return v === '-s' || v === '--silent';
});
return {
debug: isDebug,
silent: isSilent,
orgVerb: oVerb,
verb: verb,
json: oJSON,
args: args
};
}
/** Invoke a HackMyResume verb. */
function execute( src, dst, opts, log ) {
loadOptions.call( this, opts, this.parent.jsonArgs );
var hand = require( './error' );
hand.init( _opts.debug, _opts.assert, _opts.silent );
var v = new HMR.verbs[ this.name() ]();
_opts.errHandler = v;
_out.init( _opts );
v.on( 'hmr:status', function() { _out.do.apply( _out, arguments ); });
v.on( 'hmr:error', function() {
hand.err.apply( hand, arguments );
});
v.invoke.call( v, src, dst, _opts, log );
if( v.errorCode )
process.exit(v.errorCode);
}
/**
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.
*/
function loadOptions( 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 !== undefined && this.parent.silent !== null)
o.silent = this.parent.silent;
if( this.parent.debug !== undefined && this.parent.debug !== null)
o.debug = this.parent.debug;
if( this.parent.assert !== undefined && this.parent.assert !== null)
o.assert = this.parent.assert;
if( o.debug ) {
logMsg(chalk.cyan('OPTIONS:') + '\n');
_.each(o, function(val, key) {
logMsg(chalk.cyan(' %s') + chalk.cyan.bold(' %s'),
PAD(key,22,null,PAD.RIGHT), val);
});
logMsg('');
}
// Cache
EXTEND(true, _opts, o);
}
/** Split multiple command-line filenames by the 'TO' keyword */
function splitSrcDest() {
var params = this.parent.args.filter(function(j) { return String.is(j); });
if( params.length === 0 )
throw { fluenterror: HMSTATUS.resumeNotFound, quit: true };
// Find the TO keyword, if any
var 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;
}
return {
src: params.slice(0, splitAt === -1 ? undefined : splitAt ),
dst: splitAt === -1 ? [] : params.slice( splitAt + 1 )
};
}
/** Simple logging placeholder. */
function logMsg() {
_opts.silent || console.log.apply( console.log, arguments );
}
}());
-18
View File
@@ -1,18 +0,0 @@
/**
Message-handling routines for HackMyResume.
@module msg.js
@license MIT. See LICENSE.md for details.
*/
(function() {
var PATH = require('path');
var YAML = require('yamljs');
var cache = module.exports = function() {
return cache ? cache : YAML.load( PATH.join(__dirname, 'msg.yml') );
}();
}());
-98
View File
@@ -1,98 +0,0 @@
events:
begin:
msg: Invoking **%s** command.
beforeCreate:
msg: Creating new **%s** resume: **%s**
afterRead:
msg: Reading **%s** resume: **%s**
beforeTheme:
msg: Verifying **%s** theme.
afterTheme:
msg: Verifying outputs: ???
beforeMerge:
msg:
- "Merging **%s**"
- " onto **%s**"
applyTheme:
msg: Applying **%s** theme (**%s** format%s)
afterBuild:
msg:
- "The **%s** theme says:"
- |
"For best results view JSON Resume themes over a
local or remote HTTP connection. For example:
npm install http-server -g
http-server <resume-folder>
For more information, see the README."
afterGenerate:
msg:
- " (with %s)"
- "Skipping %s resume: %s"
- "Generating **%s** resume: **%s**"
beforeAnalyze:
msg: "Analyzing **%s** resume: **%s**"
beforeConvert:
msg: "Converting **%s** (**%s**) to **%s** (**%s**)"
afterValidate:
msg:
- "Validating **%s** against the **%s** schema: "
- "VALID!"
- "INVALID"
- "BROKEN"
beforePeek:
msg:
- Peeking at **%s** in **%s**
- Peeking at **%s**
afterPeek:
msg: "The specified key **%s** was not found in **%s**."
afterInlineConvert:
msg: Converting **%s** to **%s** format.
errors:
themeNotFound:
msg: >
**Couldn't find the '%s' theme.** Please specify the name of a preinstalled
FRESH theme or the path to a locally installed FRESH or JSON Resume theme.
copyCSS:
msg: Couldn't copy CSS file to destination folder.
resumeNotFound:
msg: Please **feed me a resume** in FRESH or JSON Resume format.
missingCommand:
msg: Please **give me a command**
invalidCommand:
msg: Invalid command: '%s'
resumeNotFoundAlt:
msg: Please **feed me a resume** in either FRESH or JSON Resume format.
inputOutputParity:
msg: Please **specify an output file name** for every input file you wish to convert.
createNameMissing:
msg: Please **specify the filename** of the resume to create.
pdfGeneration:
msg: PDF generation failed. Make sure wkhtmltopdf is installed and accessible from your path.
invalid:
msg: Validation failed and the --assert option was specified.
invalidFormat:
msg: The **%s** theme doesn't support the **%s** format.
notOnPath:
msg: %s wasn't found on your system path or is inaccessible. PDF not generated.
readError:
msg: Reading **???** resume: **%s**
parseError:
msg: Invalid or corrupt JSON on line %s column %s.
invalidHelperUse:
msg: "**Warning**: Incorrect use of the **%s** theme helper."
fileSaveError:
msg: An error occurred while writing %s to disk: %s.
mixedMerge:
msg: "**Warning:** merging mixed resume types. Errors may occur."
invokeTemplate:
msg: "An error occurred during template invocation."
compileTemplate:
msg: "An error occurred during template compilation."
themeLoad:
msg: "Applying **%s** theme (? formats)"
invalidParamCount:
msg: "Invalid number of parameters. Expected: **%s**."
missingParam:
msg: The '**%s**' parameter was needed but not supplied.
-229
View File
@@ -1,229 +0,0 @@
/**
Output routines for HackMyResume.
@license MIT. See LICENSE.md for details.
@module out.js
*/
(function() {
var 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')
, YAML = require('yamljs')
, printf = require('printf')
, pad = require('string-padding')
, dbgStyle = 'cyan';
/**
A stateful output module. All HMR console output handled here.
*/
var OutputHandler = module.exports = Class.extend({
init: function( opts ) {
this.opts = EXTEND( true, this.opts || { }, opts );
this.msgs = YAML.load(PATH.join( __dirname, 'msg.yml' )).events;
},
log: function( msg ) {
msg = msg || '';
var printf = require('printf');
var finished = printf.apply( printf, arguments );
this.opts.silent || console.log( finished );
},
do: function( evt ) {
var that = this;
function L() {
that.log.apply( that, arguments );
}
switch( evt.sub ) {
case HME.begin:
this.opts.debug &&
L( M2C( this.msgs.begin.msg, dbgStyle), evt.cmd.toUpperCase() );
break;
case HME.error:
break;
case HME.beforeCreate:
L( M2C( this.msgs.beforeCreate.msg, 'green' ), evt.fmt, evt.file );
break;
case HME.beforeRead:
break;
case HME.afterRead:
break;
case HME.beforeTheme:
this.opts.debug &&
L( M2C( this.msgs.beforeTheme.msg, dbgStyle), evt.theme.toUpperCase() );
break;
case HME.afterParse:
L(
M2C( this.msgs.afterRead.msg, 'gray', 'white.dim'), evt.fmt.toUpperCase(), evt.file
);
break;
case HME.afterTheme:
break;
case HME.beforeMerge:
var msg = '';
evt.f.reverse().forEach( function( a, idx ) {
msg += printf(
((idx === 0) ?
this.msgs.beforeMerge.msg[0] :
this.msgs.beforeMerge.msg[1] ), a.file
);
}, this);
L( M2C(msg, evt.mixed ? 'yellow' : 'gray', 'white.dim') );
break;
case HME.afterMerge:
break;
case HME.applyTheme:
this.theme = evt.theme;
var numFormats = Object.keys( evt.theme.formats ).length;
L( M2C(this.msgs.applyTheme.msg, evt.status === 'error' ? 'red' : 'gray', evt.status === 'error' ? 'bold' : 'white.dim'),
evt.theme.name.toUpperCase(),
numFormats, ( numFormats === 1 ? '' : 's') );
break;
case HME.end:
if( evt.cmd === 'build' ) {
var themeName = this.theme.name.toUpperCase();
if( this.opts.tips && (this.theme.message || this.theme.render) ) {
var WRAP = require('word-wrap');
if( this.theme.message ) {
L( M2C( this.msgs.afterBuild.msg[0], 'cyan' ), themeName );
L( M2C( this.theme.message, 'white' ));
}
else if ( this.theme.render ) {
L( M2C( this.msgs.afterBuild.msg[0], 'cyan'), themeName);
L( M2C( this.msgs.afterBuild.msg[1], 'white'));
}
}
}
break;
case HME.afterGenerate:
var suffix = '';
if( evt.fmt === 'pdf' ) {
if( this.opts.pdf ) {
if( this.opts.pdf !== 'none' ) {
suffix = printf( M2C( this.msgs.afterGenerate.msg[0], evt.error ? 'red' : 'green' ), this.opts.pdf );
}
else {
L( M2C( this.msgs.afterGenerate.msg[1], 'gray' ),
evt.fmt.toUpperCase(), evt.file );
return;
}
}
}
L( M2C( this.msgs.afterGenerate.msg[2] + suffix, evt.error ? 'red' : 'green' ),
pad(evt.fmt.toUpperCase(),4,null,pad.RIGHT),
PATH.relative(process.cwd(), evt.file ));
break;
case HME.beforeAnalyze:
L( M2C( this.msgs.beforeAnalyze.msg, 'green' ), evt.fmt, evt.file);
break;
case HME.afterAnalyze:
var info = evt.info;
var rawTpl = FS.readFileSync( PATH.join( __dirname, 'analyze.hbs' ), 'utf8');
HANDLEBARS.registerHelper( require('../helpers/console-helpers') );
var template = HANDLEBARS.compile(rawTpl, { strict: false, assumeObjects: false });
var tot = 0;
info.keywords.forEach(function(g) { tot += g.count; });
info.keywords.totalKeywords = tot;
var output = template( info );
this.log( chalk.cyan(output) );
break;
case HME.beforeConvert:
L( M2C( this.msgs.beforeConvert.msg, 'green' ),
evt.srcFile, evt.srcFmt, evt.dstFile, evt.dstFmt
);
break;
case HME.afterInlineConvert:
L( M2C( this.msgs.afterInlineConvert.msg, 'gray', 'white.dim' ),
evt.file, evt.fmt );
break;
case HME.afterValidate:
var 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 ) {
_.each(evt.errors, function(err,idx) {
L( chalk.yellow.bold('--> ') +
chalk.yellow(err.field.replace('data.','resume.').toUpperCase() + ' ' +
err.message) );
}, this);
}
break;
case HME.beforePeek:
// if( evt.target )
// L(M2C(this.msgs.beforePeek.msg[0], evt.isError ? 'red' : 'green'), evt.target, evt.file);
// else
// L(M2C(this.msgs.beforePeek.msg[1], evt.isError ? 'red' : 'green'), evt.file);
break;
case HME.afterPeek:
var sty = evt.error ? 'red' : ( evt.target !== undefined ? 'green' : 'yellow' );
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 )
console.dir( evt.target, { depth: null, colors: true } );
else if( !evt.error )
L(M2C( this.msgs.afterPeek.msg, 'yellow'), evt.requested, evt.file);
break;
}
}
});
}());
-51
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.
-21
View File
@@ -1,21 +0,0 @@
(function(){
/**
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', gen: new (require('../generators/latex-generator'))() }
];
}());
-13
View File
@@ -1,13 +0,0 @@
(function(){
module.exports = {
theme: 'modern',
prettify: { // ← See https://github.com/beautify-web/js-beautify#options
indent_size: 2,
unformatted: ['em','strong'],
max_char: 80, // ← See lib/html.js in above-linked repo
//wrap_line_length: 120, ← Don't use this
}
};
}());
-77
View File
@@ -1,77 +0,0 @@
{
"basics": {
"name": "",
"label": "",
"picture": "",
"email": "",
"phone": "",
"degree": "",
"website": "",
"summary": "",
"location": {
"address": "",
"postalCode": "",
"city": "",
"countryCode": "",
"region": ""
},
"profiles": [{
"network": "",
"username": "",
"url": ""
}]
},
"work": [{
"company": "",
"position": "",
"website": "",
"startDate": "",
"endDate": "",
"summary": "",
"highlights": [
""
]
}],
"awards": [{
"title": "",
"date": "",
"awarder": "",
"summary": ""
}],
"education": [{
"institution": "",
"area": "",
"studyType": "",
"startDate": "",
"endDate": "",
"gpa": "",
"courses": [ "" ]
}],
"publications": [{
"name": "",
"publisher": "",
"releaseDate": "",
"website": "",
"summary": ""
}],
"volunteer": [{
"organization": "",
"position": "",
"website": "",
"startDate": "",
"endDate": "",
"summary": "",
"highlights": [ "" ]
}],
"skills": [{
"name": "",
"level": "",
"keywords": [""]
}]
}
-46
View File
@@ -1,46 +0,0 @@
/**
Event code definitions.
@module event-codes.js
@license MIT. See LICENSE.md for details.
*/
(function(){
var val = 0;
module.exports = {
error: -1,
success: 0,
begin: 1,
end: 2,
beforeRead: 3,
afterRead: 4,
beforeCreate: 5,
afterCreate: 6,
beforeTheme: 7,
afterTheme: 8,
beforeMerge: 9,
afterMerge: 10,
beforeGenerate: 11,
afterGenerate: 12,
beforeAnalyze: 13,
afterAnalyze: 14,
beforeConvert: 15,
afterConvert: 16,
verifyOutputs: 17,
beforeParse: 18,
afterParse: 19,
beforePeek: 20,
afterPeek: 21,
beforeInlineConvert: 22,
afterInlineConvert: 23,
beforeValidate: 24,
afterValidate: 25
};
}());
-95
View File
@@ -1,95 +0,0 @@
/**
The HackMyResume date representation.
@license MIT. See LICENSE.md for details.
@module core/fluent-date
*/
(function(){
var moment = require('moment');
/**
Create a FluentDate from a string or Moment date object. There are a few date
formats to be aware of here.
1. The words "Present" and "Now", referring to the current date
2. The default "YYYY-MM-DD" format used in JSON Resume ("2015-02-10")
3. Year-and-month only ("2015-04")
4. Year-only "YYYY" ("2015")
5. The friendly HackMyResume "mmm YYYY" format ("Mar 2015" or "Dec 2008")
6. Empty dates ("", " ")
7. Any other date format that Moment.js can parse from
Note: Moment can transparently parse all or most of these, without requiring us
to specify a date format...but for maximum parsing safety and to avoid Moment
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
*/
function FluentDate( dt ) {
this.rep = this.fmt( dt );
}
FluentDate/*.prototype*/.fmt = function( dt, throws ) {
throws = (throws === undefined || throws === null) || throws;
if( (typeof dt === 'string' || dt instanceof String) ) {
dt = dt.toLowerCase().trim();
if( /^(present|now|current)$/.test(dt) ) { // "Present", "Now"
return moment();
}
else if( /^\D+\s+\d{4}$/.test(dt) ) { // "Mar 2015"
var parts = dt.split(' ');
var month = (months[parts[0]] || abbr[parts[0]]);
var temp = parts[1] + '-' + (month < 10 ? '0' + month : month.toString());
return moment( temp, 'YYYY-MM' );
}
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) ) { // "2015"
return moment( dt, 'YYYY' );
}
else if( /^\s*$/.test(dt) ) { // "", " "
var defTime = {
isNull: true,
isBefore: function( other ) {
return( other && !other.isNull ) ? true : false;
},
isAfter: function( other ) {
return( other && !other.isNull ) ? false : false;
},
unix: function() { return 0; },
format: function() { return ''; },
diff: function() { return 0; }
};
return defTime;
}
else {
var mt = moment( dt );
if(mt.isValid())
return mt;
if( throws )
throw 'Invalid date format encountered.';
return null;
}
}
else {
if( !dt ) {
return moment();
}
else if( dt.isValid && dt.isValid() )
return dt;
if( throws )
throw 'Unknown date object encountered.';
return null;
}
};
var months = {}, abbr = {};
moment.months().forEach(function(m,idx){months[m.toLowerCase()]=idx+1;});
moment.monthsShort().forEach(function(m,idx){abbr[m.toLowerCase()]=idx+1;});
abbr.sept = 9;
module.exports = FluentDate;
}());
-526
View File
@@ -1,526 +0,0 @@
/**
Definition of the FRESHResume class.
@license MIT. See LICENSE.md for details.
@module core/fresh-resume
*/
(function() {
var FS = require('fs')
, extend = require('extend')
, validator = require('is-my-json-valid')
, _ = require('underscore')
, __ = require('lodash')
, PATH = require('path')
, moment = require('moment')
, XML = require('xml-escape')
, MD = require('marked')
, CONVERTER = require('fresh-jrs-converter')
, JRSResume = require('./jrs-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
*/
function FreshResume() {
}
/**
Initialize the FreshResume from file.
*/
FreshResume.prototype.open = function( file, opts ) {
var raw = FS.readFileSync( file, 'utf8' );
var ret = this.parse( raw, opts );
this.imp.file = file;
return ret;
};
/**
Initialize the the FreshResume from JSON string data.
*/
FreshResume.prototype.parse = function( stringData, opts ) {
return this.parseJSON( JSON.parse( stringData ), opts );
};
/**
Initialize the FreshResume from JSON.
Open and parse the specified FRESH 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.
}
*/
FreshResume.prototype.parseJSON = function( rep, opts ) {
// Ignore any element with the 'ignore: true' designator.
var that = this, traverse = require('traverse'), ignoreList = [];
var 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 );
this.remove();
}
}
});
// Now apply the resume representation onto this object
extend( true, this, scrubbed );
// If the resume already has a .imp object, then we are being called from
// the .dupe method, and there's no need to do any post processing
if( !this.imp ) {
// Set up metadata TODO: Clean up metadata on the object model.
opts = opts || { };
if( opts.imp === undefined || opts.imp ) {
this.imp = this.imp || { };
this.imp.title = (opts.title || this.imp.title) || this.name;
}
// Parse dates, sort dates, and calculate computed values
(opts.date === undefined || opts.date) && _parseDates.call( this );
(opts.sort === undefined || opts.sort) && this.sort();
(opts.compute === undefined || opts.compute) && (this.computed = {
numYears: this.duration(),
keywords: this.keywords()
});
}
return this;
};
/**
Save the sheet to disk (for environments that have disk access).
*/
FreshResume.prototype.save = function( 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 ) {
if( format !== 'JRS' ) {
this.imp.file = filename || this.imp.file;
FS.writeFileSync( this.imp.file, this.stringify(), 'utf8' );
}
else {
var newRep = CONVERTER.toJRS( this );
FS.writeFileSync( filename, JRSResume.stringify( newRep ), 'utf8' );
}
return this;
};
/**
Duplicate this FreshResume instance.
This method first extend()s this object onto an empty, creating a deep copy,
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() {
var jso = extend( true, { }, this );
var rnew = new FreshResume();
rnew.parseJSON( jso, { } );
return rnew;
};
/**
Convert the supplied FreshResume to a JSON string, sanitizing meta-properties
along the way.
*/
FreshResume.stringify = function( obj ) {
function replacer( key,value ) { // Exclude these keys from stringification
return _.some(['imp', 'warnings', 'computed', 'filt', 'ctrl', 'index',
'safe', 'result', 'isModified', 'htmlPreview', 'display_progress_bar'],
function( val ) { return key.trim() === val; }
) ? undefined : value;
}
return JSON.stringify( obj, replacer, 2 );
};
/**
Convert this object to a JSON string, sanitizing meta-properties along the
way.
*/
FreshResume.prototype.stringify = function() {
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 ) {
var ret = this.dupe();
var 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() {
function MDIN( txt ){
return MD(txt || '' ).replace(/^\s*<p>|<\/p>\s*$/gi, '');
}
function trx(key, val) {
if( key === 'summary' ) {
return MD(val);
}
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() {
function trx(key, val) {
return XML(val);
}
return this.transformStrings( [], trx );
};
/**
Return the resume format.
*/
FreshResume.prototype.format = function() {
return 'FRESH';
};
/**
Return internal metadata. Create if it doesn't exist.
*/
FreshResume.prototype.i = function() {
this.imp = (this.imp || { });
return this.imp;
};
/**
Return a unique list of all keywords across all skills.
*/
FreshResume.prototype.keywords = function() {
var flatSkills = [];
if( this.skills ) {
if( this.skills.sets ) {
flatSkills = this.skills.sets.map(function(sk) { return sk.skills; })
.reduce(function(a,b) { return a.concat(b); });
}
else if( this.skills.list ) {
flatSkills = flatSkills.concat( this.skills.list.map(function(sk) {
return sk.name;
}));
}
flatSkills = _.uniq( flatSkills );
}
return flatSkills;
},
/**
Reset the sheet to an empty state. TODO: refactor/review
*/
FreshResume.prototype.clear = function( clearMeta ) {
clearMeta = ((clearMeta === undefined) && true) || clearMeta;
clearMeta && (delete this.imp);
delete this.computed; // Don't use Object.keys() here
delete this.employment;
delete this.service;
delete this.education;
delete this.recognition;
delete this.reading;
delete this.writing;
delete this.interests;
delete this.skills;
delete this.social;
};
/**
Get a safe count of the number of things in a section.
*/
FreshResume.prototype.count = function( obj ) {
if( !obj ) return 0;
if( obj.history ) return obj.history.length;
if( obj.sets ) return obj.sets.length;
return obj.length || 0;
};
/**
Get the default (starter) sheet.
*/
FreshResume.default = function() {
return new FreshResume().parseJSON( require('fresh-resume-starter') );
};
/**
Add work experience to the sheet.
*/
FreshResume.prototype.add = function( moniker ) {
var defSheet = FreshResume.default();
var 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 )
this[ moniker ].history.push( newObject );
else if( moniker === 'skills' )
this.skills.sets.push( newObject );
else
this[ moniker ].push( newObject );
return newObject;
};
/**
Determine if the sheet includes a specific social profile (eg, GitHub).
*/
FreshResume.prototype.hasProfile = function( 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 ) {
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 ) {
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 ) {
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 ) {
var schemaObj = require('fresca');
var validator = require('is-my-json-valid');
var validate = validator( schemaObj, { // See Note [1].
formats: { date: /^\d{4}(?:-(?:0[0-9]{1}|1[0-2]{1})(?:-[0-9]{2})?)?$/ }
});
var ret = validate( this );
if( !ret ) {
this.imp = this.imp || { };
this.imp.validationErrors = validate.errors;
}
return ret;
};
/**
Calculate the total duration of the sheet. Assumes this.work has been sorted
by start date descending, perhaps via a call to Sheet.sort().
@returns The total duration of the sheet's work history, that is, the number
of years between the start date of the earliest job on the resume and the
*latest end date of all jobs in the work history*. This last condition is for
sheets that have overlapping jobs.
*/
FreshResume.prototype.duration = function(unit) {
unit = unit || 'years';
var empHist = __.get(this, 'employment.history');
if( empHist && empHist.length ) {
var firstJob = _.last( empHist );
var careerStart = firstJob.start ? firstJob.safe.start : '';
if ((typeof careerStart === 'string' || careerStart instanceof String) &&
!careerStart.trim())
return 0;
var careerLast = _.max( empHist, function( w ) {
return( w.safe && w.safe.end ) ? w.safe.end.unix() : moment().unix();
});
return careerLast.safe.end.diff( careerStart, unit );
}
return 0;
};
/**
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( ) {
function byDateDesc(a,b) {
return( a.safe.start.isBefore(b.safe.start) ) ? 1
: ( a.safe.start.isAfter(b.safe.start) && -1 ) || 0;
}
function sortSection( key ) {
var ar = __.get(this, key);
if( ar && ar.length ) {
var datedThings = obj.filter( function(o) { return o.start; } );
datedThings.sort( byDateDesc );
}
}
sortSection( 'employment.history' );
sortSection( 'education.history' );
sortSection( 'service.history' );
sortSection( 'projects' );
// this.awards && this.awards.sort( function(a, b) {
// return( a.safeDate.isBefore(b.safeDate) ) ? 1
// : ( a.safeDate.isAfter(b.safeDate) && -1 ) || 0;
// });
this.writing && this.writing.sort( function(a, b) {
return( a.safe.date.isBefore(b.safe.date) ) ? 1
: ( a.safe.date.isAfter(b.safe.date) && -1 ) || 0;
});
};
/**
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.
*/
function _parseDates() {
var _fmt = require('./fluent-date').fmt;
var that = this;
// TODO: refactor recursion
function replaceDatesInObject( obj ) {
if( !obj ) return;
if( Object.prototype.toString.call( obj ) === '[object Array]' ) {
obj.forEach(function(elem){
replaceDatesInObject( elem );
});
}
else if (typeof obj === 'object') {
if( obj._isAMomentObject || obj.safe )
return;
Object.keys( obj ).forEach(function(key) {
replaceDatesInObject( obj[key] );
});
['start','end','date'].forEach( function(val) {
if( (obj[val] !== undefined) && (!obj.safe || !obj.safe[val] )) {
obj.safe = obj.safe || { };
obj.safe[ val ] = _fmt( obj[val] );
if( obj[val] && (val === 'start') && !obj.end ) {
obj.safe.end = _fmt('current');
}
}
});
}
}
Object.keys( this ).forEach(function(member){
replaceDatesInObject( that[ member ] );
});
}
/**
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}$/
//
-338
View File
@@ -1,338 +0,0 @@
/**
Definition of the FRESHTheme class.
@module fresh-theme.js
@license MIT. See LICENSE.md for details.
*/
(function() {
var FS = require('fs')
, validator = require('is-my-json-valid')
, _ = require('underscore')
, PATH = require('path')
, parsePath = require('parse-filepath')
, pathExists = require('path-exists').sync
, EXTEND = require('extend')
, HMSTATUS = require('./status-codes')
, moment = require('moment')
, loadSafeJson = require('../utils/safe-json-loader')
, READFILES = require('recursive-readdir-sync');
/**
The FRESHTheme class is a representation of a FRESH theme
asset. See also: JRSTheme.
@class FRESHTheme
*/
function FRESHTheme() {
}
/**
Open and parse the specified theme.
*/
FRESHTheme.prototype.open = function( themeFolder ) {
this.folder = themeFolder;
// Open the [theme-name].json file; should have the same name as folder
var pathInfo = parsePath( themeFolder );
// Set up a formats hash for the theme
var formatsHash = { };
// Load the theme
var themeFile = PATH.join( themeFolder, 'theme.json' );
var themeInfo = loadSafeJson( themeFile );
if( themeInfo.ex ) throw {
fluenterror: themeInfo.ex.operation === 'parse' ?
HMSTATUS.parseError : HMSTATUS.readError,
inner: themeInfo.ex.inner
};
var 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 ) {
var cached = { };
_.each( this.inherits, function(th, key) {
var themesFolder = require.resolve('fresh-themes');
var d = parsePath( themeFolder ).dirname;
var themePath = PATH.join(d, th);
cached[ th ] = cached[th] || new FRESHTheme().open( themePath );
formatsHash[ key ] = cached[ th ].getFormat( key );
});
}
// Check for an explicit "formats" entry in the theme JSON. If it has one,
// then this theme declares its files explicitly.
if( !!this.formats ) {
formatsHash = loadExplicit.call( this, formatsHash );
this.explicit = true;
}
else {
formatsHash = loadImplicit.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 ) {
return _.has( this.formats, fmt );
};
/**
Determine if the theme supports the specified output format.
*/
FRESHTheme.prototype.getFormat = function( fmt ) {
return this.formats[ fmt ];
};
/**
Load the theme implicitly, by scanning the theme folder for
files. TODO: Refactor duplicated code with loadExplicit.
*/
function loadImplicit(formatsHash) {
// Set up a hash of formats supported by this theme.
var that = this;
var major = false;
// Establish the base theme folder
var tplFolder = PATH.join( this.folder, 'src' );
// 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.
var fmts = READFILES(tplFolder).map( function(absPath) {
// If this file lives in a specific format folder within the theme,
// such as "/latex" or "/html", then that format is the output format
// for all files within the folder.
var pathInfo = parsePath(absPath);
var outFmt = '', isMajor = false;
var portion = pathInfo.dirname.replace(tplFolder,'');
if( portion && portion.trim() ) {
if( portion[1] === '_' ) return;
var reg = /^(?:\/|\\)(html|latex|doc|pdf|png|partials)(?:\/|\\)?/ig;
var 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;
}
}
}
// Otherwise, the output format is inferred from the filename, as in
// compact-[outputformat].[extension], for ex, compact-pdf.html.
if( !outFmt ) {
var idx = pathInfo.name.lastIndexOf('-');
outFmt = (idx === -1) ? pathInfo.name : pathInfo.name.substr(idx + 1);
isMajor = true;
}
// We should have a valid output format now.
formatsHash[ outFmt ] = formatsHash[outFmt] || {
outFormat: outFmt,
files: []
};
// Create the file representation object.
var obj = {
action: 'transform',
path: absPath,
major: isMajor,
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;
});
// 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.
.forEach(function( cssf ) {
var idx = _.findIndex(fmts, function( fmt ) {
return fmt && fmt.pre === cssf.pre && fmt.ext === 'html';
});
cssf.major = false;
if( idx > -1) {
fmts[ idx ].css = cssf.data;
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.
that.overrides = { file: cssf.path, data: cssf.data };
}
}
});
return formatsHash;
}
/**
Load the theme explicitly, by following the 'formats' hash
in the theme's JSON settings file.
*/
function loadExplicit(formatsHash) {
// Housekeeping
var tplFolder = PATH.join( this.folder, 'src' );
var act = null;
var that = this;
// 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.
var fmts = READFILES( tplFolder ).map( function( absPath ) {
act = null;
// If this file is mentioned in the theme's JSON file under "transforms"
var pathInfo = parsePath(absPath);
var absPathSafe = absPath.trim().toLowerCase();
var outFmt = _.find(
Object.keys( that.formats ),
function( fmtKey ) {
var fmtVal = that.formats[ fmtKey ];
return _.some( fmtVal.transform, function(fpath) {
var absPathB = PATH.join( that.folder, fpath )
.trim().toLowerCase();
return absPathB === absPathSafe;
});
});
if( outFmt ) {
act = 'transform';
}
// If this file lives in a specific format folder within the theme,
// such as "/latex" or "/html", then that format is the output format
// for all files within the folder.
if( !outFmt ) {
var portion = pathInfo.dirname.replace(tplFolder,'');
if( portion && portion.trim() ) {
var reg = /^(?:\/|\\)(html|latex|doc|pdf)(?:\/|\\)?/ig;
var res = reg.exec( portion );
res && (outFmt = res[1]);
}
}
// Otherwise, the output format is inferred from the filename, as in
// compact-[outputformat].[extension], for ex, compact-pdf.html.
if( !outFmt ) {
var idx = pathInfo.name.lastIndexOf('-');
outFmt = (idx === -1) ? pathInfo.name : pathInfo.name.substr(idx + 1);
}
// We should have a valid output format now.
formatsHash[ outFmt ] =
formatsHash[ outFmt ] || {
outFormat: outFmt,
files: [],
symLinks: that.formats[ outFmt ].symLinks
};
// Create the file representation object.
var obj = {
action: act,
orgPath: PATH.relative(that.folder, absPath),
path: 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;
});
// Now, get all the CSS files...
(this.cssFiles = fmts.filter(function( fmt ){
return fmt.ext === 'css';
}))
// For each CSS file, get its corresponding HTML file
.forEach(function( cssf ) {
// For each CSS file, get its corresponding HTML file
var idx = _.findIndex(fmts, function( fmt ) {
return fmt.pre === cssf.pre && fmt.ext === 'html';
});
fmts[ idx ].css = cssf.data;
fmts[ idx ].cssPath = cssf.path;
});
// Remove CSS files from the formats array
fmts = fmts.filter( function( fmt) {
return fmt.ext !== 'css';
});
return formatsHash;
}
/**
Return a more friendly name for certain formats.
TODO: Refactor
*/
function friendlyName( val ) {
val = val.trim().toLowerCase();
var friendly = { yml: 'yaml', md: 'markdown', txt: 'text' };
return friendly[val] || val;
}
module.exports = FRESHTheme;
}());
-454
View File
@@ -1,454 +0,0 @@
/**
Definition of the JRSResume class.
@license MIT. See LICENSE.md for details.
@module core/jrs-resume
*/
(function() {
var FS = require('fs')
, extend = require('extend')
, validator = require('is-my-json-valid')
, _ = require('underscore')
, PATH = require('path')
, MD = require('marked')
, CONVERTER = require('fresh-jrs-converter')
, moment = require('moment');
/**
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
*/
function JRSResume() {
}
/**
Initialize the JSResume from file.
*/
JRSResume.prototype.open = function( file, title ) {
//this.imp = { fileName: file }; <-- schema violation, tuck it into .basics
this.basics = {
imp: {
file: file,
raw: FS.readFileSync( file, 'utf8' )
}
};
return this.parse( this.basics.imp.raw, title );
};
/**
Initialize the the JSResume from string.
*/
JRSResume.prototype.parse = function( stringData, opts ) {
opts = opts || { };
var rep = JSON.parse( stringData );
return this.parseJSON( rep, 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 ) {
opts = opts || { };
// Ignore any element with the 'ignore: true' designator.
var that = this, traverse = require('traverse'), ignoreList = [];
var 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 );
this.remove();
}
}
});
// Extend resume properties onto ourself.
extend( true, this, scrubbed );
// Set up metadata
if( opts.imp === undefined || opts.imp ) {
this.basics.imp = this.basics.imp || { };
this.basics.imp.title =
(opts.title || this.basics.imp.title) || this.basics.name;
this.basics.imp.orgFormat = 'JRS';
}
// Parse dates, sort dates, and calculate computed values
(opts.date === undefined || opts.date) && _parseDates.call( this );
(opts.sort === undefined || opts.sort) && this.sort();
(opts.compute === undefined || 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.basics.imp.file = filename || this.basics.imp.file;
FS.writeFileSync(this.basics.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 ) {
if( format === 'JRS' ) {
this.basics.imp.file = filename || this.basics.imp.file;
FS.writeFileSync( this.basics.imp.file, this.stringify(), 'utf8' );
}
else {
var newRep = CONVERTER.toFRESH( this );
var stringRep = CONVERTER.toSTRING( newRep );
FS.writeFileSync( filename, stringRep, 'utf8' );
}
return this;
};
/**
Return the resume format.
*/
JRSResume.prototype.format = function() {
return 'JRS';
};
/**
Convert this object to a JSON string, sanitizing meta-properties along the
way. Don't override .toString().
*/
JRSResume.stringify = function( obj ) {
function replacer( key,value ) { // Exclude these keys from stringification
return _.some(['imp', 'warnings', 'computed', 'filt', 'ctrl', 'index',
'safeStartDate', 'safeEndDate', 'safeDate', 'safeReleaseDate', 'result',
'isModified', 'htmlPreview', 'display_progress_bar'],
function( val ) { return key.trim() === val; }
) ? undefined : value;
}
return JSON.stringify( obj, replacer, 2 );
};
JRSResume.prototype.stringify = function() {
return JRSResume.stringify( this );
};
/**
Return a unique list of all keywords across all skills.
*/
JRSResume.prototype.keywords = function() {
var flatSkills = [];
if( this.skills && this.skills.length ) {
this.skills.forEach( function( s ) {
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.
*/
JRSResume.prototype.i = function() {
this.basics = this.basics || { };
this.basics.imp = this.basics.imp || { };
return this.basics.imp;
};
/**
Reset the sheet to an empty state.
*/
JRSResume.prototype.clear = function( clearMeta ) {
clearMeta = ((clearMeta === undefined) && true) || clearMeta;
clearMeta && (delete this.imp);
delete this.basics.computed; // Don't use Object.keys() here
delete this.work;
delete this.volunteer;
delete this.education;
delete this.awards;
delete this.publications;
delete this.interests;
delete this.skills;
delete this.basics.profiles;
};
/**
Get the default (empty) sheet.
*/
JRSResume.default = function() {
return new JRSResume().open(
PATH.join( __dirname, 'empty-jrs.json'), 'Empty'
);
};
/**
Add work experience to the sheet.
*/
JRSResume.prototype.add = function( moniker ) {
var defSheet = JRSResume.default();
var 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( ) { // TODO: ↓ fix this path ↓
var schema = FS.readFileSync( PATH.join( __dirname, 'resume.json' ),'utf8');
var schemaObj = JSON.parse( schema );
var validator = require('is-my-json-valid');
var validate = validator( schemaObj, { // Note [1]
formats: { date: /^\d{4}(?:-(?:0[0-9]{1}|1[0-2]{1})(?:-[0-9]{2})?)?$/ }
});
var ret = validate( this );
if( !ret ) {
this.basics.imp = this.basics.imp || { };
this.basics.imp.validationErrors = validate.errors;
}
return ret;
};
/**
Calculate the total duration of the sheet. Assumes this.work has been sorted
by start date descending, perhaps via a call to Sheet.sort().
@returns The total duration of the sheet's work history, that is, the number
of years between the start date of the earliest job on the resume and the
*latest end date of all jobs in the work history*. This last condition is for
sheets that have overlapping jobs.
*/
JRSResume.prototype.duration = function( unit ) {
unit = unit || 'years';
if( this.work && this.work.length ) {
var careerStart = this.work[ this.work.length - 1].safeStartDate;
if ((typeof careerStart === 'string' || careerStart instanceof String) &&
!careerStart.trim())
return 0;
var careerLast = _.max( this.work, function( w ) {
return w.safeEndDate.unix();
}).safeEndDate;
return careerLast.diff( careerStart, unit );
}
return 0;
};
/**
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( ) {
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) {
return( a.safeDate.isBefore(b.safeDate) ) ? 1
: ( a.safeDate.isAfter(b.safeDate) && -1 ) || 0;
});
this.publications && this.publications.sort( function(a, b) {
return( a.safeReleaseDate.isBefore(b.safeReleaseDate) ) ? 1
: ( a.safeReleaseDate.isAfter(b.safeReleaseDate) && -1 ) || 0;
});
function byDateDesc(a,b) {
return( a.safeStartDate.isBefore(b.safeStartDate) ) ? 1
: ( a.safeStartDate.isAfter(b.safeStartDate) && -1 ) || 0;
}
};
JRSResume.prototype.dupe = function() {
var 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 that = this;
var ret = this.dupe();
function HD(txt) {
return '@@@@~' + txt + '~@@@@';
}
function HDIN(txt){
//return MD(txt || '' ).replace(/^\s*<p>|<\/p>\s*$/gi, '');
return HD(txt);
}
// TODO: refactor recursion
function hardenStringsInObject( obj, inline ) {
if( !obj ) return;
inline = inline === undefined || inline;
if( Object.prototype.toString.call( obj ) === '[object Array]' ) {
obj.forEach(function(elem, idx, ar){
if( typeof elem === 'string' || elem instanceof String )
ar[idx] = inline ? HDIN(elem) : HD( elem );
else
hardenStringsInObject( elem );
});
}
else if (typeof obj === 'object') {
Object.keys( obj ).forEach(function(key) {
var 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' )
obj[key] = HD( obj[key] );
else
obj[key] = inline ? HDIN( obj[key] ) : HD( obj[key] );
}
else
hardenStringsInObject( sub );
});
}
}
Object.keys( ret ).forEach(function(member){
hardenStringsInObject( ret[ member ] );
});
return ret;
};
/**
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.
*/
function _parseDates() {
var _fmt = require('./fluent-date').fmt;
this.work && this.work.forEach( function(job) {
job.safeStartDate = _fmt( job.startDate );
job.safeEndDate = _fmt( job.endDate );
});
this.education && this.education.forEach( function(edu) {
edu.safeStartDate = _fmt( edu.startDate );
edu.safeEndDate = _fmt( edu.endDate );
});
this.volunteer && this.volunteer.forEach( function(vol) {
vol.safeStartDate = _fmt( vol.startDate );
vol.safeEndDate = _fmt( vol.endDate );
});
this.awards && this.awards.forEach( function(awd) {
awd.safeDate = _fmt( awd.date );
});
this.publications && this.publications.forEach( function(pub) {
pub.safeReleaseDate = _fmt( pub.releaseDate );
});
}
/**
Export the JRSResume function/ctor.
*/
module.exports = JRSResume;
}());
-108
View File
@@ -1,108 +0,0 @@
/**
Definition of the JRSTheme class.
@module jrs-theme.js
@license MIT. See LICENSE.MD for details.
*/
(function() {
var _ = require('underscore')
, PATH = require('path')
, parsePath = require('parse-filepath')
, pathExists = require('path-exists').sync;
/**
The JRSTheme class is a representation of a JSON Resume theme asset.
@class JRSTheme
*/
function JRSTheme() {
}
/**
Open and parse the specified theme.
@method open
*/
JRSTheme.prototype.open = function( thFolder ) {
this.folder = thFolder;
// Open the [theme-name].json file; should have the same
// name as folder
var pathInfo = parsePath( thFolder );
// Open and parse the theme's package.json file.
var pkgJsonPath = PATH.join( thFolder, 'package.json' );
if( pathExists( pkgJsonPath )) {
var thApi = require( thFolder )
, thPkg = require( pkgJsonPath );
this.name = thPkg.name;
this.render = (thApi && thApi.render) || undefined;
this.engine = 'jrs';
// Create theme formats (HTML and PDF). Just add the bare minimum mix of
// properties necessary to allow JSON Resume themes to share a rendering
// path with FRESH themes.
this.formats = {
html: { outFormat: 'html', files: [
{
action: 'transform',
render: this.render,
major: true,
ext: 'html',
css: null
}
]},
pdf: { outFormat: 'pdf', files: [
{
action: 'transform',
render: this.render,
major: true,
ext: 'pdf',
css: null
}
]}
};
}
else {
throw { fluenterror: HACKMYSTATUS.missingPackageJSON };
}
return this;
};
/**
Determine if the theme supports the output format.
@method hasFormat
*/
JRSTheme.prototype.hasFormat = function( fmt ) {
return _.has( this.formats, fmt );
};
/**
Return the requested output format.
@method getFormat
*/
JRSTheme.prototype.getFormat = function( fmt ) {
return this.formats[ fmt ];
};
module.exports = JRSTheme;
}());
-138
View File
@@ -1,138 +0,0 @@
/**
Definition of the ResumeFactory class.
@license MIT. See LICENSE.md for details.
@module resume-factory.js
*/
(function(){
var FS = require('fs'),
HACKMYSTATUS = require('./status-codes'),
HME = require('./event-codes'),
ResumeConverter = require('fresh-jrs-converter'),
chalk = require('chalk'),
SyntaxErrorEx = require('../utils/syntax-error-ex'),
_ = require('underscore');
require('string.prototype.startswith');
/**
A simple factory class for FRESH and JSON Resumes.
@class ResumeFactory
*/
var 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?
inner: { // Passthru options for FRESH/JRSResume
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.
*/
loadOne: function( src, opts, emitter ) {
var toFormat = opts.format; // Can be null
var objectify = opts.objectify;
// Get the destination format. Can be 'fresh', 'jrs', or null/undefined.
toFormat && (toFormat = toFormat.toLowerCase().trim());
// Load and parse the resume JSON
var info = _parse( src, opts, emitter );
if( info.fluenterror ) return info;
// Determine the resume format: FRESH or JRS
var json = info.json;
var orgFormat = ( json.meta && json.meta.format &&
json.meta.format.startsWith('FRESH@') ) ?
'fresh' : 'jrs';
// 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.
var rez;
if( objectify ) {
var ResumeClass = require('../core/' + (toFormat || orgFormat) + '-resume');
rez = new ResumeClass().parseJSON( json, opts.inner );
rez.i().file = src;
}
return {
file: src,
json: info.json,
rez: rez
};
}
};
function _parse( fileName, opts, eve ) {
var rawData;
try {
// Read the file
eve && eve.stat( HME.beforeRead, { file: fileName });
rawData = FS.readFileSync( fileName, 'utf8' );
eve && eve.stat( HME.afterRead, { file: fileName, data: rawData });
// Parse the file
eve && eve.stat( HME.beforeParse, { data: rawData });
var ret = { json: JSON.parse( rawData ) };
var orgFormat = ( ret.json.meta && ret.json.meta.format &&
ret.json.meta.format.startsWith('FRESH@') ) ?
'fresh' : 'jrs';
eve && eve.stat( HME.afterParse, { file: fileName, data: ret.json, fmt: orgFormat } );
return ret;
}
catch( e ) {
// Can be ENOENT, EACCES, SyntaxError, etc.
var ex = {
fluenterror: rawData ? HACKMYSTATUS.parseError : HACKMYSTATUS.readError,
inner: e, raw: rawData, file: fileName, shouldExit: false
};
opts.quit && (ex.quit = true);
eve && eve.err( ex.fluenterror, ex );
if( opts.throw ) throw ex;
return ex;
}
}
}());
-380
View File
@@ -1,380 +0,0 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "Resume Schema",
"type": "object",
"additionalProperties": false,
"properties": {
"basics": {
"type": "object",
"additionalProperties": true,
"properties": {
"name": {
"type": "string"
},
"label": {
"type": "string",
"description": "e.g. Web Developer"
},
"picture": {
"type": "string",
"description": "URL (as per RFC 3986) to a picture in JPEG or PNG format"
},
"email": {
"type": "string",
"description": "e.g. thomas@gmail.com",
"format": "email"
},
"phone": {
"type": "string",
"description": "Phone numbers are stored as strings so use any format you like, e.g. 712-117-2923"
},
"website": {
"type": "string",
"description": "URL (as per RFC 3986) to your website, e.g. personal homepage",
"format": "uri"
},
"summary": {
"type": "string",
"description": "Write a short 2-3 sentence biography about yourself"
},
"location": {
"type": "object",
"additionalProperties": true,
"properties": {
"address": {
"type": "string",
"description": "To add multiple address lines, use \n. For example, 1234 Glücklichkeit Straße\nHinterhaus 5. Etage li."
},
"postalCode": {
"type": "string"
},
"city": {
"type": "string"
},
"countryCode": {
"type": "string",
"description": "code as per ISO-3166-1 ALPHA-2, e.g. US, AU, IN"
},
"region": {
"type": "string",
"description": "The general region where you live. Can be a US state, or a province, for instance."
}
}
},
"profiles": {
"type": "array",
"description": "Specify any number of social networks that you participate in",
"additionalItems": false,
"items": {
"type": "object",
"additionalProperties": true,
"properties": {
"network": {
"type": "string",
"description": "e.g. Facebook or Twitter"
},
"username": {
"type": "string",
"description": "e.g. neutralthoughts"
},
"url": {
"type": "string",
"description": "e.g. http://twitter.com/neutralthoughts"
}
}
}
}
}
},
"work": {
"type": "array",
"additionalItems": false,
"items": {
"type": "object",
"additionalProperties": true,
"properties": {
"company": {
"type": "string",
"description": "e.g. Facebook"
},
"position": {
"type": "string",
"description": "e.g. Software Engineer"
},
"website": {
"type": "string",
"description": "e.g. http://facebook.com",
"format": "uri"
},
"startDate": {
"type": "string",
"description": "resume.json uses the ISO 8601 date standard e.g. 2014-06-29",
"format": "date"
},
"endDate": {
"type": "string",
"description": "e.g. 2012-06-29",
"format": "date"
},
"summary": {
"type": "string",
"description": "Give an overview of your responsibilities at the company"
},
"highlights": {
"type": "array",
"description": "Specify multiple accomplishments",
"additionalItems": false,
"items": {
"type": "string",
"description": "e.g. Increased profits by 20% from 2011-2012 through viral advertising"
}
}
}
}
},
"volunteer": {
"type": "array",
"additionalItems": false,
"items": {
"type": "object",
"additionalProperties": true,
"properties": {
"organization": {
"type": "string",
"description": "e.g. Facebook"
},
"position": {
"type": "string",
"description": "e.g. Software Engineer"
},
"website": {
"type": "string",
"description": "e.g. http://facebook.com",
"format": "uri"
},
"startDate": {
"type": "string",
"description": "resume.json uses the ISO 8601 date standard e.g. 2014-06-29",
"format": "date"
},
"endDate": {
"type": "string",
"description": "e.g. 2012-06-29",
"format": "date"
},
"summary": {
"type": "string",
"description": "Give an overview of your responsibilities at the company"
},
"highlights": {
"type": "array",
"description": "Specify multiple accomplishments",
"additionalItems": false,
"items": {
"type": "string",
"description": "e.g. Increased profits by 20% from 2011-2012 through viral advertising"
}
}
}
}
},
"education": {
"type": "array",
"additionalItems": false,
"items": {
"type": "object",
"additionalProperties": true,
"properties": {
"institution": {
"type": "string",
"description": "e.g. Massachusetts Institute of Technology"
},
"area": {
"type": "string",
"description": "e.g. Arts"
},
"studyType": {
"type": "string",
"description": "e.g. Bachelor"
},
"startDate": {
"type": "string",
"description": "e.g. 2014-06-29",
"format": "date"
},
"endDate": {
"type": "string",
"description": "e.g. 2012-06-29",
"format": "date"
},
"gpa": {
"type": "string",
"description": "grade point average, e.g. 3.67/4.0"
},
"courses": {
"type": "array",
"description": "List notable courses/subjects",
"additionalItems": false,
"items": {
"type": "string",
"description": "e.g. H1302 - Introduction to American history"
}
}
}
}
},
"awards": {
"type": "array",
"description": "Specify any awards you have received throughout your professional career",
"additionalItems": false,
"items": {
"type": "object",
"additionalProperties": true,
"properties": {
"title": {
"type": "string",
"description": "e.g. One of the 100 greatest minds of the century"
},
"date": {
"type": "string",
"description": "e.g. 1989-06-12",
"format": "date"
},
"awarder": {
"type": "string",
"description": "e.g. Time Magazine"
},
"summary": {
"type": "string",
"description": "e.g. Received for my work with Quantum Physics"
}
}
}
},
"publications": {
"type": "array",
"description": "Specify your publications through your career",
"additionalItems": false,
"items": {
"type": "object",
"additionalProperties": true,
"properties": {
"name": {
"type": "string",
"description": "e.g. The World Wide Web"
},
"publisher": {
"type": "string",
"description": "e.g. IEEE, Computer Magazine"
},
"releaseDate": {
"type": "string",
"description": "e.g. 1990-08-01"
},
"website": {
"type": "string",
"description": "e.g. http://www.computer.org/csdl/mags/co/1996/10/rx069-abs.html"
},
"summary": {
"type": "string",
"description": "Short summary of publication. e.g. Discussion of the World Wide Web, HTTP, HTML."
}
}
}
},
"skills": {
"type": "array",
"description": "List out your professional skill-set",
"additionalItems": false,
"items": {
"type": "object",
"additionalProperties": true,
"properties": {
"name": {
"type": "string",
"description": "e.g. Web Development"
},
"level": {
"type": "string",
"description": "e.g. Master"
},
"keywords": {
"type": "array",
"description": "List some keywords pertaining to this skill",
"additionalItems": false,
"items": {
"type": "string",
"description": "e.g. HTML"
}
}
}
}
},
"languages": {
"type": "array",
"description": "List any other languages you speak",
"additionalItems": false,
"items": {
"type": "object",
"additionalProperties": true,
"properties": {
"language": {
"type": "string",
"description": "e.g. English, Spanish"
},
"fluency": {
"type": "string",
"description": "e.g. Fluent, Beginner"
}
}
}
},
"interests": {
"type": "array",
"additionalItems": false,
"items": {
"type": "object",
"additionalProperties": true,
"properties": {
"name": {
"type": "string",
"description": "e.g. Philosophy"
},
"keywords": {
"type": "array",
"additionalItems": false,
"items": {
"type": "string",
"description": "e.g. Friedrich Nietzsche"
}
}
}
}
},
"references": {
"type": "array",
"description": "List references you have received",
"additionalItems": false,
"items": {
"type": "object",
"additionalProperties": true,
"properties": {
"name": {
"type": "string",
"description": "e.g. Timothy Cook"
},
"reference": {
"type": "string",
"description": "e.g. Joe blogs was a great employee, who turned up to work at least once a week. He exceeded my expectations when it came to doing nothing."
}
}
}
}
}
}
-37
View File
@@ -1,37 +0,0 @@
/**
Status codes for HackMyResume.
@module status-codes.js
@license MIT. See LICENSE.MD for details.
*/
(function(){
module.exports = {
success: 0,
themeNotFound: 1,
copyCss: 2,
resumeNotFound: 3,
missingCommand: 4,
invalidCommand: 5,
resumeNotFoundAlt: 6,
inputOutputParity: 7,
createNameMissing: 8,
pdfgeneration: 9,
missingPackageJSON: 10,
invalid: 11,
invalidFormat: 12,
notOnPath: 13,
readError: 14,
parseError: 15,
fileSaveError: 16,
generateError: 17,
invalidHelperUse: 18,
mixedMerge: 19,
invokeTemplate: 20,
compileTemplate: 21,
themeLoad: 22,
invalidParamCount: 23,
missingParam: 24
};
}());
-39
View File
@@ -1,39 +0,0 @@
/**
Definition of the BaseGenerator class.
@module base-generator.js
@license MIT. See LICENSE.md for details.
*/
(function() {
// Use J. Resig's nifty class implementation
var Class = require( '../utils/class' );
/**
The BaseGenerator class is the root of the generator hierarchy. Functionality
common to ALL generators lives here.
*/
var BaseGenerator = module.exports = Class.extend({
/**
Base-class initialize.
*/
init: function( outputFormat ) {
this.format = outputFormat;
},
/**
Status codes.
*/
codes: require('../core/status-codes'),
/**
Generator options.
*/
opts: {
}
});
}());
-34
View File
@@ -1,34 +0,0 @@
/**
Definition of the HTMLGenerator class.
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
@module html-generator.js
*/
(function() {
var TemplateGenerator = require('./template-generator')
, FS = require('fs-extra')
, HTML = require( 'html' )
, PATH = require('path');
require('string.prototype.endswith');
var HtmlGenerator = module.exports = TemplateGenerator.extend({
init: function() {
this._super( 'html' );
},
/**
Copy satellite CSS files to the destination and optionally pretty-print
the HTML resume prior to saving.
*/
onBeforeSave: function( info ) {
if( info.outputFile.endsWith('.css') )
return info.mk;
return this.opts.prettify ?
HTML.prettyPrint( info.mk, this.opts.prettify ) : info.mk;
}
});
}());
-113
View File
@@ -1,113 +0,0 @@
/**
Definition of the HtmlPdfCLIGenerator class.
@module html-pdf-generator.js
@license MIT. See LICENSE.md for details.
*/
(function() {
var TemplateGenerator = require('./template-generator')
, FS = require('fs-extra')
, HTML = require( 'html' )
, PATH = require('path')
, SPAWN = require('../utils/safe-spawn')
, SLASH = require('slash');
/**
An HTML-driven PDF resume generator for HackMyResume. Talks to Phantom,
wkhtmltopdf, and other PDF engines over a CLI (command-line interface).
If an engine isn't installed for a particular platform, error out gracefully.
*/
var HtmlPdfCLIGenerator = module.exports = TemplateGenerator.extend({
init: function() {
this._super( 'pdf', 'html' );
},
/**
Generate the binary PDF.
*/
onBeforeSave: function( info ) {
try {
var safe_eng = info.opts.pdf || 'wkhtmltopdf';
if( safe_eng !== 'none' )
engines[ safe_eng ].call( this, info.mk, info.outputFile );
return null; // halt further processing
}
catch(ex) {
// { [Error: write EPIPE] code: 'EPIPE', errno: 'EPIPE', ... }
// { [Error: ENOENT] }
throw ( ex.inner && ex.inner.code === 'ENOENT' ) ?
{ fluenterror: this.codes.notOnPath, inner: ex.inner, engine: ex.cmd,
stack: ex.inner && ex.inner.stack } :
{ fluenterror: this.codes.pdfGeneration, inner: ex, stack: ex.stack };
}
}
});
// TODO: Move each engine to a separate module
var engines = {
/**
Generate a PDF from HTML using wkhtmltopdf's CLI interface.
Spawns a child process with `wkhtmltopdf <source> <target>`. wkhtmltopdf
must be installed and path-accessible.
TODO: If HTML generation has run, reuse that output
TODO: Local web server to ease wkhtmltopdf rendering
*/
wkhtmltopdf: function(markup, fOut) {
// Save the markup to a temporary file
var tempFile = fOut.replace(/\.pdf$/i, '.pdf.html');
FS.writeFileSync( tempFile, markup, 'utf8' );
var info = SPAWN( 'wkhtmltopdf', [ tempFile, fOut ] );
},
/**
Generate a PDF from HTML using Phantom's CLI interface.
Spawns a child process with `phantomjs <script> <source> <target>`. Phantom
must be installed and path-accessible.
TODO: If HTML generation has run, reuse that output
TODO: Local web server to ease Phantom rendering
*/
phantom: function( markup, fOut ) {
// Save the markup to a temporary file
var tempFile = fOut.replace(/\.pdf$/i, '.pdf.html');
FS.writeFileSync( tempFile, markup, 'utf8' );
var scriptPath = SLASH( PATH.relative( process.cwd(),
PATH.resolve( __dirname, '../utils/rasterize.js' ) ) );
var sourcePath = SLASH( PATH.relative( process.cwd(), tempFile) );
var destPath = SLASH( PATH.relative( process.cwd(), fOut) );
var info = SPAWN('phantomjs', [ scriptPath, sourcePath, destPath ]);
}
};
}());
-66
View File
@@ -1,66 +0,0 @@
/**
Definition of the HtmlPngGenerator class.
@license MIT. See LICENSE.MD for details.
@module html-png-generator.js
*/
(function() {
var TemplateGenerator = require('./template-generator')
, FS = require('fs-extra')
, HTML = require( 'html' );
/**
An HTML-based PNG resume generator for HackMyResume.
*/
var HtmlPngGenerator = module.exports = TemplateGenerator.extend({
init: function() {
this._super( 'png', 'html' );
},
invoke: function( rez, themeMarkup, cssInfo, opts ) {
// TODO: Not currently called or callable.
},
generate: function( rez, f, opts ) {
var htmlResults = opts.targets.filter(function(t){
return t.fmt.outFormat === 'html';
});
var htmlFile = htmlResults[0].final.files.filter(function(fl){
return fl.info.ext === 'html';
});
png( htmlFile[0].data, f );
}
});
/**
Generate a PNG from HTML.
*/
function png( markup, fOut ) {
// TODO: Which Webshot syntax?
// require('webshot')( markup , { encoding: 'binary', siteType: 'html' } )
// .pipe( FS.createWriteStream( fOut ) );
require('webshot')( markup , fOut, { siteType: 'html' }, function(err) { } );
}
}());
-36
View File
@@ -1,36 +0,0 @@
/**
Definition of the JsonGenerator class.
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
@module json-generator.js
*/
var BaseGenerator = require('./base-generator');
var FS = require('fs');
var _ = require('underscore');
/**
The JsonGenerator generates a JSON resume directly.
*/
var JsonGenerator = module.exports = BaseGenerator.extend({
init: function(){
this._super( 'json' );
},
invoke: function( rez ) {
// TODO: merge with FCVD
function replacer( key,value ) { // Exclude these keys from stringification
return _.some(['imp', 'warnings', 'computed', 'filt', 'ctrl', 'index',
'safeStartDate', 'safeEndDate', 'safeDate', 'safeReleaseDate', 'result',
'isModified', 'htmlPreview', 'safe' ],
function( val ) { return key.trim() === val; }
) ? undefined : value;
}
return JSON.stringify( rez, replacer, 2 );
},
generate: function( rez, f ) {
FS.writeFileSync( f, this.invoke(rez), 'utf8' );
}
});
-37
View File
@@ -1,37 +0,0 @@
/**
Definition of the JsonYamlGenerator class.
@module json-yaml-generator.js
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
*/
(function() {
var BaseGenerator = require('./base-generator');
var FS = require('fs');
var 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).
*/
var JsonYamlGenerator = module.exports = BaseGenerator.extend({
init: function(){
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 = YAML.stringify( JSON.parse( rez.stringify() ), Infinity, 2 );
FS.writeFileSync( f, data, 'utf8' );
}
});
}());
-18
View File
@@ -1,18 +0,0 @@
/**
Definition of the LaTeXGenerator class.
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
@module latex-generator.js
*/
var TemplateGenerator = require('./template-generator');
/**
LaTeXGenerator generates a LaTeX resume via TemplateGenerator.
*/
var LaTeXGenerator = module.exports = TemplateGenerator.extend({
init: function(){
this._super( 'latex', 'tex' );
}
});
-18
View File
@@ -1,18 +0,0 @@
/**
Definition of the MarkdownGenerator class.
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
@module markdown-generator.js
*/
var TemplateGenerator = require('./template-generator');
/**
MarkdownGenerator generates a Markdown-formatted resume via TemplateGenerator.
*/
var MarkdownGenerator = module.exports = TemplateGenerator.extend({
init: function(){
this._super( 'md', 'txt' );
}
});
-244
View File
@@ -1,244 +0,0 @@
/**
Definition of the TemplateGenerator class. TODO: Refactor
@license MIT. See LICENSE.md for details.
@module template-generator.js
*/
(function() {
var FS = require( 'fs-extra' )
, _ = require( 'underscore' )
, MD = require( 'marked' )
, XML = require( 'xml-escape' )
, PATH = require('path')
, parsePath = require('parse-filepath')
, MKDIRP = require('mkdirp')
, BaseGenerator = require( './base-generator' )
, EXTEND = require('extend')
, FRESHTheme = require('../core/fresh-theme')
, 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
*/
var TemplateGenerator = module.exports = BaseGenerator.extend({
/** 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 );
this.tplFormat = templateFormat || outputFormat;
},
/** Generate a resume using string-based inputs and outputs without touching
the filesystem.
@method invoke
@param rez A FreshResume object.
@param opts Generator options.
@returns {Array} An array of objects representing the generated output
files. */
invoke: function( rez, opts ) {
opts = opts ?
(this.opts = EXTEND( true, { }, _defaultOpts, opts )) :
this.opts;
// Sort such that CSS files are processed before others
var curFmt = opts.themeObj.getFormat( this.format );
curFmt.files = _.sortBy( curFmt.files, function(fi) {
return fi.ext !== 'css';
});
// Run the transformation!
var results = curFmt.files.map( function( tplInfo, idx ) {
var trx = this.single( rez, tplInfo.data, this.format, opts, opts.themeObj, curFmt );
if( tplInfo.ext === 'css' ) { curFmt.files[idx].data = trx; }
else if( tplInfo.ext === 'html' ) {
//tplInfo.css contains the CSS data loaded by theme
//tplInfo.cssPath contains the absolute path to the source CSS File
}
return { info: tplInfo, data: trx };
}, this);
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 ) {
// Prepare
this.opts = EXTEND( true, { }, _defaultOpts, opts );
// Call the string-based generation method to perform the generation.
var genInfo = this.invoke( rez, null );
var outFolder = parsePath( f ).dirname;
var 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 ){
// Pre-processing
file.info.orgPath = file.info.orgPath || ''; // <-- For JRS themes
var thisFilePath = PATH.join( outFolder, file.info.orgPath );
if( this.onBeforeSave ) {
file.data = this.onBeforeSave({
theme: opts.themeObj,
outputFile: (file.info.major ? f : thisFilePath),
mk: file.data,
opts: this.opts
});
if( !file.data ) return; // PDF etc
}
// Write the file
var fileName = file.info.major ? f : thisFilePath;
MKDIRP.sync( PATH.dirname( fileName ) );
FS.writeFileSync( fileName, file.data,
{ encoding: 'utf8', flags: 'w' } );
// Post-processing
this.onAfterSave && this.onAfterSave(
{ outputFile: fileName, mk: file.data, opts: this.opts } );
}, this);
// Some themes require a symlink structure. If so, create it.
if( curFmt.symLinks ) {
Object.keys( curFmt.symLinks ).forEach( function(loc) {
var absLoc = PATH.join(outFolder, loc);
var absTarg = PATH.join(PATH.dirname(absLoc), curFmt.symLinks[loc]);
// 'file', 'dir', or 'junction' (Windows only)
var type = parsePath( absLoc ).extname ? 'file' : 'junction';
FS.symlinkSync( absTarg, absLoc, type);
});
}
return genInfo;
},
/** Perform a single resume resume transformation using string-based inputs
and outputs without touching the local file system.
@param json A FRESH or JRS resume object.
@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 ) {
this.opts.freezeBreaks && ( jst = freeze(jst) );
var eng = require( '../renderers/' + theme.engine + '-generator' );
var result = eng.generate( json, jst, format, curFmt, opts, theme );
this.opts.freezeBreaks && ( result = unfreeze(result) );
return result;
}
});
/** Export the TemplateGenerator function/ctor. */
module.exports = TemplateGenerator;
/** Freeze newlines for protection against errant JST parsers. */
function freeze( markup ) {
return markup
.replace( _reg.regN, _defaultOpts.nSym )
.replace( _reg.regR, _defaultOpts.rSym );
}
/** Unfreeze newlines when the coast is clear. */
function unfreeze( markup ) {
return markup
.replace( _reg.regSymR, '\r' )
.replace( _reg.regSymN, '\n' );
}
/** Default template generator options. */
var _defaultOpts = {
engine: 'underscore',
keepBreaks: true,
freezeBreaks: false,
nSym: '&newl;', // newline entity
rSym: '&retn;', // return entity
template: {
interpolate: /\{\{(.+?)\}\}/g,
escape: /\{\{\=(.+?)\}\}/g,
evaluate: /\{\%(.+?)\%\}/g,
comment: /\{\#(.+?)\#\}/g
},
filters: {
out: function( txt ) { return txt; },
raw: function( txt ) { return txt; },
xml: function( txt ) { return XML(txt); },
md: function( txt ) { return MD( txt || '' ); },
mdin: function( txt ) {
return MD(txt || '' ).replace(/^\s*<p>|<\/p>\s*$/gi, '');
},
lower: function( txt ) { return txt.toLowerCase(); },
link: function( name, url ) { return url ?
'<a href="' + url + '">' + name + '</a>' : name; }
},
prettify: { // ← See https://github.com/beautify-web/js-beautify#options
indent_size: 2,
unformatted: ['em','strong','a'],
max_char: 80, // ← See lib/html.js in above-linked repo
//wrap_line_length: 120, <-- Don't use this
}
};
/** Regexes for linebreak preservation. */
var _reg = {
regN: new RegExp( '\n', 'g' ),
regR: new RegExp( '\r', 'g' ),
regSymN: new RegExp( _defaultOpts.nSym, 'g' ),
regSymR: new RegExp( _defaultOpts.rSym, 'g' )
};
}());
-20
View File
@@ -1,20 +0,0 @@
/**
Definition of the TextGenerator class.
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
@module text-generator.js
*/
var TemplateGenerator = require('./template-generator');
/**
The TextGenerator generates a plain-text resume via the TemplateGenerator.
*/
var TextGenerator = TemplateGenerator.extend({
init: function(){
this._super( 'txt' );
},
});
module.exports = TextGenerator;
-19
View File
@@ -1,19 +0,0 @@
/**
Definition of the WordGenerator class.
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
@module word-generator.js
*/
(function() {
var TemplateGenerator = require('./template-generator');
var WordGenerator = module.exports = TemplateGenerator.extend({
init: function(){
this._super( 'doc', 'xml' );
}
});
}());
-18
View File
@@ -1,18 +0,0 @@
/**
Definition of the XMLGenerator class.
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
@module xml-generator.js
*/
var BaseGenerator = require('./base-generator');
/**
The XmlGenerator generates an XML resume via the TemplateGenerator.
*/
var XMLGenerator = module.exports = BaseGenerator.extend({
init: function(){
this._super( 'xml' );
},
});
-24
View File
@@ -1,24 +0,0 @@
/**
Definition of the YAMLGenerator class.
@module yaml-generator.js
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
*/
(function() {
var TemplateGenerator = require('./template-generator');
/**
YamlGenerator generates a YAML-formatted resume via TemplateGenerator.
*/
var YAMLGenerator = module.exports = TemplateGenerator.extend({
init: function(){
this._super( 'yml', 'yml' );
}
});
}());
-51
View File
@@ -1,51 +0,0 @@
/**
External API surface for HackMyResume.
@license MIT. See LICENSE.md for details.
@module hackmyapi.js
*/
(function() {
/**
The formal HackMyResume API.
*/
var HackMyAPI = module.exports = {
verbs: {
build: require('./verbs/build'),
analyze: require('./verbs/analyze'),
validate: require('./verbs/validate'),
convert: require('./verbs/convert'),
new: require('./verbs/create'),
peek: require('./verbs/peek')
},
alias: {
generate: require('./verbs/build'),
create: require('./verbs/create')
},
options: require('./core/default-options'),
formats: require('./core/default-formats'),
Sheet: require('./core/fresh-resume'),
FRESHResume: require('./core/fresh-resume'),
JRSResume: require('./core/jrs-resume'),
FRESHTheme: require('./core/fresh-theme'),
JRSTheme: require('./core/jrs-theme'),
FluentDate: require('./core/fluent-date'),
HtmlGenerator: require('./generators/html-generator'),
TextGenerator: require('./generators/text-generator'),
HtmlPdfCliGenerator: require('./generators/html-pdf-cli-generator'),
WordGenerator: require('./generators/word-generator'),
MarkdownGenerator: require('./generators/markdown-generator'),
JsonGenerator: require('./generators/json-generator'),
YamlGenerator: require('./generators/yaml-generator'),
JsonYamlGenerator: require('./generators/json-yaml-generator'),
LaTeXGenerator: require('./generators/latex-generator'),
HtmlPngGenerator: require('./generators/html-png-generator')
};
}());
-67
View File
@@ -1,67 +0,0 @@
/**
Generic template helper definitions for command-line output.
@module console-helpers.js
@license MIT. See LICENSE.md for details.
*/
(function() {
var PAD = require('string-padding')
, LO = require('lodash')
, CHALK = require('chalk')
, _ = require('underscore');
require('../utils/string');
var consoleFormatHelpers = module.exports = {
v: function( val, defaultVal, padding, style ) {
retVal = ( val === null || val === undefined ) ? defaultVal : val;
var spaces = 0;
if( String.is(padding) ) {
spaces = parseInt( padding, 10 );
if( isNaN(spaces) ) spaces = 0;
}
else if( _.isNumber(padding) ) {
spaces = padding;
}
if( spaces !== 0 )
retVal = PAD( retVal, Math.abs(spaces), null, spaces > 0 ? PAD.LEFT : PAD.RIGHT );
if( style && String.is( style )) {
retVal = LO.get( CHALK, style )( retVal );
}
return retVal;
},
gapLength: function(val) {
if( val < 35 )
return CHALK.green.bold(val);
else if( val < 95 )
return CHALK.yellow.bold(val);
else
return CHALK.red.bold(val);
},
style: function( val, style ) {
return LO.get( CHALK, style )( val );
},
isPlural: function( val, options ) {
if( val > 1 )
return options.fn(this);
},
pad: function( val, spaces ) {
return PAD(val, Math.abs(spaces), null, spaces > 0 ? PAD.LEFT : PAD.RIGHT );
}
};
}());
-609
View File
@@ -1,609 +0,0 @@
/**
Generic template helper definitions for HackMyResume / FluentCV.
@license MIT. See LICENSE.md for details.
@module helpers/generic-helpers
*/
(function() {
var MD = require('marked')
, H2W = require('../utils/html-to-wpml')
, XML = require('xml-escape')
, FluentDate = require('../core/fluent-date')
, HMSTATUS = require('../core/status-codes')
, moment = require('moment')
, FS = require('fs')
, LO = require('lodash')
, PATH = require('path')
, printf = require('printf')
, _ = require('underscore')
, unused = require('../utils/string');
/** Generic template helper function definitions. */
var 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) {
if (moment) {
var momentDate = moment( datetime );
if (momentDate.isValid()) return momentDate.format(format);
}
return datetime || (typeof fallback == 'string' ? fallback : (fallback === true ? 'Present' : null));
},
/**
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 ) {
if( !obj ) return '';
return _fromTo( obj.start, obj.end, fmt, sep, fallback, options );
},
/**
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 ) {
// Key must be specified
if( !( colorName && colorName.trim()) ) {
_reportError( HMSTATUS.invalidHelperUse, {
helper: 'fontList', error: HMSTATUS.missingParam, expected: 'name'
});
}
else {
if( !GenericHelpers.theme.colors ) return colorDefault;
var ret = GenericHelpers.theme.colors[ colorName ];
if( !(ret && ret.trim()) )
return colorDefault;
return ret;
}
},
/**
Return true if the section is present on the resume and has at least one
element.
@method section
*/
section: function( title, options ) {
title = title.trim().toLowerCase();
var obj = LO.get( this.r, title );
if( _.isArray( obj ) ) {
return obj.length ? options.fn(this) : undefined;
}
else if( _.isObject( obj )) {
return ( (obj.history && obj.history.length) ||
( obj.sets && obj.sets.length ) ) ?
options.fn(this) : undefined;
}
},
/**
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 ret = ''
, hasDef = defSize && ( String.is( defSize ) || _.isNumber( defSize ));
// Key must be specified
if( !( key && key.trim()) ) {
_reportError( HMSTATUS.invalidHelperUse, {
helper: 'fontSize', error: HMSTATUS.missingParam, expected: 'key'
});
return ret;
}
else if ( GenericHelpers.theme.fonts ) {
var 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 )) {
// An array of fonts were specified. Each one could be a string
// or an object
if( !String.is( fontSpec[0] )) {
ret = fontSpec[0].size;
}
}
else {
// A font description object.
ret = fontSpec.size;
}
}
}
// We weren't able to lookup the specified key. Default to defFont.
if( !ret ) {
if( hasDef )
ret = defSize;
else {
_reportError( HMSTATUS.invalidHelperUse, {
helper: 'fontSize', error: HMSTATUS.missingParam,
expected: 'defSize'});
ret = '';
}
}
return ret;
},
/**
Emit the font face (such as 'Helvetica' or 'Calibri') 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 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 ret = ''
, hasDef = defFont && String.is( defFont );
// Key must be specified
if( !( key && key.trim()) ) {
_reportError( HMSTATUS.invalidHelperUse, {
helper: 'fontFace', error: HMSTATUS.missingParam, expected: 'key'
});
return ret;
}
// If the theme has a "fonts" section, lookup the font face.
else if( GenericHelpers.theme.fonts ) {
var 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;
}
}
}
// We weren't able to lookup the specified key. Default to defFont.
if( !(ret && ret.trim()) ) {
ret = defFont;
if( !hasDef ) {
_reportError( HMSTATUS.invalidHelperUse, {
helper: 'fontFace', error: HMSTATUS.missingParam,
expected: 'defFont'});
ret = '';
}
}
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 ret = ''
, hasDef = defFontList && String.is( defFontList );
// Key must be specified
if( !( key && key.trim()) ) {
_reportError( HMSTATUS.invalidHelperUse, {
helper: 'fontList', error: HMSTATUS.missingParam, expected: 'key'
});
}
// If the theme has a "fonts" section, lookup the font list.
else if( GenericHelpers.theme.fonts ) {
var fontSpec = LO.get( GenericHelpers.theme.fonts, this.format + '.' + key );
if( !fontSpec ) {
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
fontSpec = fontSpec.map( function( ff ) {
return "'" + (String.is( ff ) ? ff : ff.name) + "'";
});
ret = fontSpec.join( sep === undefined ? ', ' : (sep || '') );
}
else {
// A font description object.
ret = fontSpec.name;
}
}
}
// The key wasn't found in the "fonts" section. Default to defFont.
if( !(ret && ret.trim()) ) {
if( !hasDef ) {
_reportError( HMSTATUS.invalidHelperUse, {
helper: 'fontList', error: HMSTATUS.missingParam,
expected: 'defFontList'});
ret = '';
}
else {
ret = defFontList;
}
}
return ret;
},
/**
Capitalize the first letter of the word.
@method section
*/
camelCase: function(val) {
val = (val && val.trim()) || '';
return val ? (val.charAt(0).toUpperCase() + val.slice(1)) : 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);
}
},
/**
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"}}
Example:
{{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.
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 '';
inline = (inline && !inline.hash) || false;
txt = XML(txt.trim());
txt = inline ?
MD(txt).replace(/^\s*<p>|<\/p>\s*$/gi, '') :
MD(txt);
txt = H2W( txt );
return txt;
},
/**
Emit a conditional link.
@method link
*/
link: function( text, url ) {
return url && url.trim() ?
('<a href="' + url + '">' + text + '</a>') : text;
},
/**
Return the last word of the specified text.
@method lastWord
*/
lastWord: function( txt ) {
return txt && txt.trim() ? _.last( txt.split(' ') ) : '';
},
/**
Convert a skill level to an RGB color triplet. TODO: refactor
@method skillColor
@param lvl Input skill level. Skill level can be expressed as a string
("beginner", "intermediate", etc.), as an integer (1,5,etc), as a string
integer ("1", "5", etc.), or as an RRGGBB color triplet ('#C00000',
'#FFFFAA').
*/
skillColor: function( lvl ) {
var idx = skillLevelToIndex( lvl );
var skillColors = (this.theme && this.theme.palette &&
this.theme.palette.skillLevels) ||
[ '#FFFFFF', '#5CB85C', '#F1C40F', '#428BCA', '#C00000' ];
return skillColors[idx];
},
/**
Return an appropriate height. TODO: refactor
@method lastWord
*/
skillHeight: function( lvl ) {
var idx = skillLevelToIndex( lvl );
return ['38.25', '30', '16', '8', '0'][idx];
},
/**
Return all but the last word of the input text.
@method initialWords
*/
initialWords: function( txt ) {
return txt && txt.trim() ? _.initial( txt.split(' ') ).join(' ') : '';
},
/**
Trim the protocol (http or https) from a URL/
@method trimURL
*/
trimURL: function( url ) {
return url && url.trim() ? url.trim().replace(/^https?:\/\//i, '') : '';
},
/**
Convert text to lowercase.
@method toLower
*/
toLower: function( txt ) {
return txt && txt.trim() ? txt.toLowerCase() : '';
},
/**
Convert text to lowercase.
@method toLower
*/
toUpper: function( txt ) {
return txt && txt.trim() ? txt.toUpperCase() : '';
},
/**
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
theme author's and user's preferences.
@param url {String} The path to the CSS file.
@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 ) {
// Establish the linkage style
linkage = this.opts.css || linkage || 'embed';
// Create the <link> or <style> tag
var ret = '';
if( linkage === 'link' ) {
ret = printf('<link href="%s" rel="stylesheet" type="text/css">', url);
}
else {
var rawCss = FS.readFileSync(
PATH.join( this.opts.themeObj.folder, '/src/', url ), 'utf8' );
var 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) {
if (arguments.length < 3)
throw new Error("Handlerbars Helper 'compare' needs 2 parameters");
var operator = options.hash.operator || "==";
var operators = {
'==': function(l,r) { return l == r; },
'===': function(l,r) { return l === r; },
'!=': function(l,r) { return l != r; },
'<': function(l,r) { return l < r; },
'>': function(l,r) { return l > r; },
'<=': function(l,r) { return l <= r; },
'>=': function(l,r) { return l >= r; },
'typeof': function(l,r) { return typeof l == r; }
};
if (!operators[operator])
throw new Error("Handlerbars Helper 'compare' doesn't know the operator "+operator);
var result = operators[operator](lvalue,rvalue);
return result ? options.fn(this) : options.inverse(this);
}
};
/**
Report an error to the outside world without throwing an exception. Currently
relies on kludging the running verb into. opts.
*/
function _reportError( code, params ) {
GenericHelpers.opts.errHandler.err( code, params );
}
/**
Format a from/to date range for display.
*/
function _fromTo( dateA, dateB, fmt, sep, fallback ) {
// 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' } );
return '';
}
var dateFrom, dateTo, dateTemp;
// Check for 'current', 'present', 'now', '', null, and undefined
dateA = dateA || '';
dateB = dateB || '';
var dateATrim = dateA.trim().toLowerCase();
var dateBTrim = dateB.trim().toLowerCase();
var reserved = ['current','present','now', ''];
fmt = (fmt && String.is(fmt) && fmt) || 'YYYY-MM';
sep = (sep && String.is(sep) && sep) || ' — ';
if( _.contains( reserved, dateATrim )) {
dateFrom = fallback || '???';
}
else {
dateTemp = FluentDate.fmt( dateA );
dateFrom = dateTemp.format( fmt );
}
if( _.contains( reserved, dateBTrim )) {
dateTo = fallback || 'Current';
}
else {
dateTemp = FluentDate.fmt( dateB );
dateTo = dateTemp.format( fmt );
}
if( dateFrom && dateTo ) {
return dateFrom + sep + dateTo;
}
else if( dateFrom || dateTo ) {
return dateFrom || dateTo;
}
return '';
}
function skillLevelToIndex( lvl ) {
var idx = 0;
if( String.is( lvl ) ) {
lvl = lvl.trim().toLowerCase();
var intVal = parseInt( lvl );
if( isNaN( intVal ) ) {
switch( lvl ) {
case 'beginner': idx = 1; break;
case 'intermediate': idx = 2; break;
case 'advanced': idx = 3; break;
case 'master': idx = 4; break;
}
}
else {
idx = Math.min( intVal / 2, 4 );
idx = Math.max( 0, idx );
}
}
else {
idx = Math.min( lvl / 2, 4 );
idx = Math.max( 0, idx );
}
return idx;
}
}());
// 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.
-26
View File
@@ -1,26 +0,0 @@
/**
Template helper definitions for Handlebars.
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
@module handlebars-helpers.js
*/
(function() {
var HANDLEBARS = require('handlebars')
, _ = require('underscore')
, helpers = require('./generic-helpers');
/**
Register useful Handlebars helpers.
@method registerHelpers
*/
module.exports = function( theme, opts ) {
helpers.theme = theme;
helpers.opts = opts;
HANDLEBARS.registerHelper( helpers );
};
}());
-34
View File
@@ -1,34 +0,0 @@
/**
Template helper definitions for Underscore.
@license MIT. Copyright (c) 2016 hacksalot (https://github.com/hacksalot)
@module handlebars-helpers.js
*/
(function() {
var HANDLEBARS = require('handlebars')
, _ = require('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;
_.each( helpers, function( hVal, hKey ) {
if( _.isFunction( hVal )) {
_.bind( hVal, ctx );
}
}, this);
};
}());

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