1
0
mirror of https://github.com/JuanCanham/HackMyResume.git synced 2026-04-22 05:44:11 +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
105 changed files with 1623 additions and 5164 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 -3
View File
@@ -1,3 +1,3 @@
node_modules/
tests/sandbox/
docs/
_site
.sass-cache
.jekyll-metadata
-59
View File
@@ -1,59 +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: ['tests/*.js'] }
},
clean: ['tests/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-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.registerTask('test', 'Test the HackMyResume library.',
function( config ) { grunt.task.run( ['clean','simplemocha:all'] ); });
grunt.registerTask('document', 'Generate HackMyResume library documentation.',
function( config ) { grunt.task.run( ['yuidoc'] ); });
grunt.registerTask('default', [ 'jshint', 'test', 'yuidoc' ]);
};
-22
View File
@@ -1,22 +0,0 @@
The MIT License
===============
Copyright (c) 2015 James M. Devlin (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.
-304
View File
@@ -1,304 +0,0 @@
HackMyResume
============
*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/resume-bouqet.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. **Convert** resumes between [FRESH][fresca] and [JSON Resume][6] formats.
3. **Validate** resumes against either format.
HackMyResume is built with Node.js and runs on recent versions of OS X, Linux,
or Windows.
## Features
- OS X, Linux, and Windows.
- 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.
- Use from your command line or [desktop][7].
- Free and open-source through the MIT license.
## Install
Install HackMyResume with NPM:
```bash
[sudo] npm install hackmyresume -g
```
Note: for PDF generation you'll need to install a copy of [wkhtmltopdf][3] for
your platform. For LaTeX generation you'll need a valid LaTeX environment with
access to `xelatex` and similar.
## 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 four 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
```
- **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
```
## 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).
MS Word | .doc | A Microsoft Word office document.
Adobe Acrobat (PDF) | .pdf | A binary PDF document driven by an HTML theme.
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.
## Install
HackMyResume requires a recent version of [Node.js][4] and [NPM][5]. Then:
1. Install the latest official [wkhtmltopdf][3] binary for your platform.
2. Optionally install an updated LaTeX environment (LaTeX resumes only).
2. Install **HackMyResume** with `[sudo] npm install hackmyresume -g`.
3. You're ready to go.
## Use
Assuming you've got a JSON-formatted resume handy, generating resumes in
different formats and combinations 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 -o 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 v0.9.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
You can specify a predefined or custom theme via the optional `-t` parameter.
For a predefined theme, include the theme name. For a custom theme, include the
path to the custom theme's folder.
```bash
hackmyresume build resume.json -t modern
hackmyresume build resume.json -t ~/foo/bar/my-custom-theme/
```
As of v1.0.0, available predefined themes are `positive`, `modern`, `compact`,
`minimist`, and `hello-world`.
### 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 -o resume.all
```
This can be useful for overriding a base (generic) resume with information from
a specific (targeted) resume. For example, you might override your generic
catch-all "software developer" resume with specific details from your targeted
"game developer" resume, or combine two partial resumes into a "complete"
resume. Merging follows conventional [extend()][9]-style behavior and there's
no arbitrary limit to how many resumes you can merge:
```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 -o out1.doc -o out1.pdf -o foo.txt
```
You can also omit the output file(s) and/or theme completely:
```bash
# Equivalent to "hackmyresume resume.json resume.all -t modern"
hackmyresume build resume.json
```
### 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 -o 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`.
### 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 v0.9.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).
### Prettifying
HackMyResume applies [js-beautify][10]-style HTML prettification by default to
HTML-formatted resumes. To disable prettification, the `--nopretty` or `-n` flag
can be used:
```bash
hackmyresume generate resume.json out.all --nopretty
```
### Silent Mode
Use `-s` or `--silent` to run in silent mode:
```bash
hackmyresume generate resume.json -o someFile.all -s
hackmyresume generate resume.json -o someFile.all --silent
```
## 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
+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: 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

Before

Width:  |  Height:  |  Size: 27 KiB

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

-72
View File
@@ -1,72 +0,0 @@
{
"name": "hackmyresume",
"version": "1.2.2",
"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"
},
"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)",
"license": "MIT",
"preferGlobal": "true",
"bugs": {
"url": "https://github.com/hacksalot/HackMyResume/issues"
},
"main": "src/hackmycmd.js",
"bin": {
"hackmyresume": "src/index.js"
},
"homepage": "https://github.com/hacksalot/HackMyResume",
"dependencies": {
"colors": "^1.1.2",
"fluent-themes": "~0.7.1-beta",
"fresca": "~0.2.2",
"fs-extra": "^0.24.0",
"handlebars": "^4.0.5",
"html": "0.0.10",
"is-my-json-valid": "^2.12.2",
"jst": "0.0.13",
"lodash": "^3.10.1",
"marked": "^0.3.5",
"minimist": "^1.2.0",
"mkdirp": "^0.5.1",
"moment": "^2.10.6",
"parse-filepath": "^0.6.3",
"path-exists": "^2.1.0",
"recursive-readdir-sync": "^1.0.6",
"simple-html-tokenizer": "^0.2.0",
"underscore": "^1.8.3",
"wkhtmltopdf": "^0.1.5",
"xml-escape": "^1.0.0",
"yamljs": "^0.2.4"
},
"devDependencies": {
"chai": "*",
"grunt": "*",
"grunt-contrib-clean": "^0.7.0",
"grunt-contrib-jshint": "^0.11.3",
"grunt-contrib-yuidoc": "^0.10.0",
"grunt-simple-mocha": "*",
"jane-q-fullstacker": "fluentdesk/jane-q-fullstacker",
"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

-372
View File
@@ -1,372 +0,0 @@
/**
FRESH to JSON Resume conversion routiens.
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
@module convert.js
*/
(function(){
/**
Convert between FRESH and JRS resume/CV formats.
@class FRESHConverter
*/
var FRESHConverter = module.exports = {
/**
Convert from JSON Resume format to FRESH.
@method toFresh
@todo Refactor
*/
toFRESH: function( src, foreign ) {
foreign = (foreign === undefined || foreign === null) ? true : foreign;
return {
name: src.basics.name,
info: {
label: src.basics.label,
class: src.basics.class, // <--> round-trip
image: src.basics.picture,
brief: src.basics.summary
},
contact: {
email: src.basics.email,
phone: src.basics.phone,
website: src.basics.website,
other: src.basics.other // <--> round-trip
},
meta: meta( true, src.meta ),
location: {
city: src.basics.location.city,
region: src.basics.location.region,
country: src.basics.location.countryCode,
code: src.basics.location.postalCode,
address: src.basics.location.address
},
employment: employment( src.work, true ),
education: education( src.education, true),
service: service( src.volunteer, true),
skills: skillsToFRESH( src.skills ),
writing: writing( src.publications, true),
recognition: recognition( src.awards, true, foreign ),
social: social( src.basics.profiles, true ),
interests: src.interests,
testimonials: references( src.references, true ),
languages: src.languages,
disposition: src.disposition // <--> round-trip
};
},
/**
Convert from FRESH format to JSON Resume.
@param foreign True if non-JSON-Resume properties should be included in
the result, false if those properties should be excluded.
@todo Refactor
*/
toJRS: function( src, foreign ) {
foreign = (foreign === undefined || foreign === null) ? false : foreign;
return {
basics: {
name: src.name,
label: src.info.label,
class: foreign ? src.info.class : undefined,
summary: src.info.brief,
website: src.contact.website,
phone: src.contact.phone,
email: src.contact.email,
picture: src.info.image,
location: {
address: src.location.address,
postalCode: src.location.code,
city: src.location.city,
countryCode: src.location.country,
region: src.location.region
},
profiles: social( src.social, false )
},
work: employment( src.employment, false ),
education: education( src.education, false ),
skills: skillsToJRS( src.skills, false ),
volunteer: service( src.service, false ),
awards: recognition( src.recognition, false, foreign ),
publications: writing( src.writing, false ),
interests: src.interests,
references: references( src.testimonials, false ),
samples: foreign ? src.samples : undefined,
disposition: foreign ? src.disposition : undefined,
languages: src.languages
};
}
};
function meta( direction, obj ) {
if( !obj ) return obj; // preserve null and undefined
if( direction ) {
obj = obj || { };
obj.format = obj.format || "FRESH@0.1.0";
obj.version = obj.version || "0.1.0";
}
return obj;
}
function employment( obj, direction ) {
if( !obj ) return obj;
if( !direction ) {
return obj && obj.history ?
obj.history.map(function(emp){
return {
company: emp.employer,
website: emp.url,
position: emp.position,
startDate: emp.start,
endDate: emp.end,
summary: emp.summary,
highlights: emp.highlights
};
}) : undefined;
}
else {
return {
history: obj && obj.length ?
obj.map( function( job ) {
return {
position: job.position,
employer: job.company,
summary: job.summary,
current: (!job.endDate || !job.endDate.trim() || job.endDate.trim().toLowerCase() === 'current') || undefined,
start: job.startDate,
end: job.endDate,
url: job.website,
keywords: "",
highlights: job.highlights
};
}) : undefined
};
}
}
function education( obj, direction ) {
if( !obj ) return obj;
if( direction ) {
return obj && obj.length ? {
history: obj.map(function(edu){
return {
institution: edu.institution,
start: edu.startDate,
end: edu.endDate,
grade: edu.gpa,
curriculum: edu.courses,
url: edu.website || edu.url || null,
summary: null,
area: edu.area,
studyType: edu.studyType
};
})
} : undefined;
}
else {
return obj && obj.history ?
obj.history.map(function(edu){
return {
institution: edu.institution,
gpa: edu.grade,
courses: edu.curriculum,
startDate: edu.start,
endDate: edu.end,
area: edu.area,
studyType: edu.studyType
};
}) : undefined;
}
}
function service( obj, direction, foreign ) {
if( !obj ) return obj;
if( direction ) {
return {
history: obj && obj.length ? obj.map(function(vol) {
return {
type: 'volunteer',
position: vol.position,
organization: vol.organization,
start: vol.startDate,
end: vol.endDate,
url: vol.website,
summary: vol.summary,
highlights: vol.highlights
};
}) : undefined
};
}
else {
return obj && obj.history ?
obj.history.map(function(srv){
return {
flavor: foreign ? srv.flavor : undefined,
organization: srv.organization,
position: srv.position,
startDate: srv.start,
endDate: srv.end,
website: srv.url,
summary: srv.summary,
highlights: srv.highlights
};
}) : undefined;
}
}
function social( obj, direction ) {
if( !obj ) return obj;
if( direction ) {
return obj.map(function(pro){
return {
label: pro.network,
network: pro.network,
url: pro.url,
user: pro.username
};
});
}
else {
return obj.map( function( soc ) {
return {
network: soc.network,
username: soc.user,
url: soc.url
};
});
}
}
function recognition( obj, direction, foreign ) {
if( !obj ) return obj;
if( direction ) {
return obj && obj.length ? obj.map(
function(awd){
return {
flavor: foreign ? awd.flavor : undefined,
url: foreign ? awd.url: undefined,
title: awd.title,
date: awd.date,
from: awd.awarder,
summary: awd.summary
};
}) : undefined;
}
else {
return obj && obj.length ? obj.map(function(awd){
return {
flavor: foreign ? awd.flavor : undefined,
url: foreign ? awd.url: undefined,
title: awd.title,
date: awd.date,
awarder: awd.from,
summary: awd.summary
};
}) : undefined;
}
}
function references( obj, direction ) {
if( !obj ) return obj;
if( direction ) {
return obj && obj.length && obj.map(function(ref){
return {
name: ref.name,
flavor: 'professional',
quote: ref.reference,
private: false
};
});
}
else {
return obj && obj.length && obj.map(function(ref){
return {
name: ref.name,
reference: ref.quote
};
});
}
}
function writing( obj, direction ) {
if( !obj ) return obj;
if( direction ) {
return obj.map(function( pub ) {
return {
title: pub.name,
flavor: undefined,
publisher: pub.publisher,
url: pub.website,
date: pub.releaseDate,
summary: pub.summary
};
});
}
else {
return obj && obj.length ? obj.map(function(pub){
return {
name: pub.title,
publisher: pub.publisher && pub.publisher.name ? pub.publisher.name : pub.publisher,
releaseDate: pub.date,
website: pub.url,
summary: pub.summary
};
}) : undefined;
}
}
function skillsToFRESH( skills ) {
return {
sets: skills.map(function(set) {
return {
name: set.name,
level: set.level,
skills: set.keywords
};
})
};
}
function skillsToJRS( skills ) {
var ret = [];
if( skills.sets && skills.sets.length ) {
ret = skills.sets.map(function(set){
return {
name: set.name,
level: set.level,
keywords: set.skills
};
});
}
else if( skills.list ) {
ret = skills.list.map(function(sk){
return {
name: sk.name,
level: sk.level,
keywords: sk.keywords
};
});
}
return ret;
}
}());
-19
View File
@@ -1,19 +0,0 @@
(function(){
var FLUENT = require('../hackmyapi');
/**
Supported resume formats.
*/
module.exports = [
{ name: 'html', ext: 'html', gen: new FLUENT.HtmlGenerator() },
{ name: 'txt', ext: 'txt', gen: new FLUENT.TextGenerator() },
{ name: 'doc', ext: 'doc', fmt: 'xml', gen: new FLUENT.WordGenerator() },
{ name: 'pdf', ext: 'pdf', fmt: 'html', is: false, gen: new FLUENT.HtmlPdfGenerator() },
{ name: 'md', ext: 'md', fmt: 'txt', gen: new FLUENT.MarkdownGenerator() },
{ name: 'json', ext: 'json', gen: new FLUENT.JsonGenerator() },
{ name: 'yml', ext: 'yml', fmt: 'yml', gen: new FLUENT.JsonYamlGenerator() },
{ name: 'latex', ext: 'tex', fmt: 'latex', gen: new FLUENT.LaTeXGenerator() }
];
}());
-13
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
}
};
}());
-184
View File
@@ -1,184 +0,0 @@
{
"name": "",
"meta": {
"format": "FRESH@0.1.0",
"version": "0.1.0"
},
"info": {
"label": "",
"characterClass": "",
"brief": "",
"image": ""
},
"contact": {
"website": "",
"phone": "",
"email": "",
"other": []
},
"location": {
"address": "",
"city": "",
"region": "",
"code": "",
"country": ""
},
"social": [
{
"label": "",
"network": "",
"user": "",
"url": ""
}
],
"employment": {
"summary": "",
"history": [
{
"employer": "",
"url": "",
"position": "",
"summary": "",
"start": "",
"end": "",
"keywords": [],
"highlights": []
}
]
},
"education": {
"summary": "",
"level": "",
"degree": "",
"history": [
{
"institution": "",
"url": "",
"start": "",
"end": "",
"grade": "",
"summary": "",
"curriculum": []
}
]
},
"service": {
"summary": "",
"history": [
{
"flavor": "",
"position": "",
"organization": "",
"url": "",
"start": "",
"end": "",
"summary": "",
"highlights": []
}
]
},
"skills": {
"sets": [
{
"name": "",
"level": "",
"skills": []
}
],
"list": [ ]
},
"samples": [
{
"title": "",
"summary": "",
"url": "",
"date": ""
}
],
"writing": [
{
"title": "",
"flavor": "",
"date": "",
"publisher": {
"name": "",
"url": ""
},
"url": ""
}
],
"reading": [
{
"title": "",
"flavor": "",
"url": "",
"author": ""
}
],
"recognition": [
{
"flavor": "",
"from": "",
"title": "",
"event": "",
"date": "",
"summary": ""
}
],
"references": [
{
"name": "",
"flavor": "",
"private": true,
"contact": [
{
"label": "",
"flavor": "",
"value": ""
}
]
}
],
"testimonials": [
{
"name": "",
"flavor": "",
"quote": ""
}
],
"languages": [
{
"language": "",
"level": "",
"years": 0
}
],
"interests": [
{
"name": "",
"summary": "",
"keywords": []
}
]
}
-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": [""]
}]
}
-84
View File
@@ -1,84 +0,0 @@
/**
The HackMyResume date representation.
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
@module fluent-date.js
*/
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 ) {
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;
throw 'Invalid date format encountered.';
}
}
else {
if( !dt ) {
return moment();
}
else if( dt.isValid && dt.isValid() )
return dt;
throw 'Unknown date object encountered.';
}
};
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;
-413
View File
@@ -1,413 +0,0 @@
/**
Definition of the FRESHResume class.
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
@module fresh-resume.js
*/
(function() {
var FS = require('fs')
, extend = require('../utils/extend')
, validator = require('is-my-json-valid')
, _ = require('underscore')
, __ = require('lodash')
, PATH = require('path')
, moment = require('moment')
, MD = require('marked')
, CONVERTER = require('./convert')
, JRSResume = require('./jrs-resume');
/**
A FRESH-style resume in JSON or YAML.
@class FreshResume
*/
function FreshResume() {
}
/**
Open and parse the specified FRESH resume sheet. 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.
*/
FreshResume.prototype.open = function( file, title ) {
this.imp = { fileName: file };
this.imp.raw = FS.readFileSync( file, 'utf8' );
return this.parse( this.imp.raw, title );
};
/**
Save the sheet to disk (for environments that have disk access).
*/
FreshResume.prototype.save = function( filename ) {
this.imp.fileName = filename || this.imp.fileName;
FS.writeFileSync( this.imp.fileName, 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.fileName = filename || this.imp.fileName;
FS.writeFileSync( this.imp.fileName, this.stringify(), 'utf8' );
}
else {
var newRep = CONVERTER.toJRS( this );
FS.writeFileSync( filename, JRSResume.stringify( newRep ), 'utf8' );
}
return this;
};
FreshResume.prototype.dupe = function() {
var rnew = new FreshResume();
rnew.parse( this.stringify(), { } );
return rnew;
};
/**
Convert the supplied object 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 );
};
/**
Create a copy of this resume in which all fields have been interpreted as
Markdown.
*/
FreshResume.prototype.markdownify = function() {
var that = this;
var ret = this.dupe();
function MDIN(txt){
return MD(txt || '' ).replace(/^\s*<p>|<\/p>\s*$/gi, '');
}
// TODO: refactor recursion
function markdownifyStringsInObject( 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 ? MDIN(elem) : MD( elem );
else
markdownifyStringsInObject( 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','start','end','date'], key) )
return;
if( key === 'summary' )
obj[key] = MD( obj[key] );
else
obj[key] = inline ? MDIN( obj[key] ) : MD( obj[key] );
}
else
markdownifyStringsInObject( sub );
});
}
}
Object.keys( ret ).forEach(function(member){
markdownifyStringsInObject( ret[ member ] );
});
return ret;
};
/**
Convert this object to a JSON string, sanitizing meta-properties along the
way. Don't override .toString().
*/
FreshResume.prototype.stringify = function() {
return FreshResume.stringify( this );
};
/**
Open and parse the specified JSON resume sheet. 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.
*/
FreshResume.prototype.parse = function( stringData, opts ) {
// Parse the incoming JSON representation
var rep = JSON.parse( stringData );
// Convert JSON Resume to FRESH if necessary
if( rep.basics ) {
rep = CONVERTER.toFRESH( rep );
rep.imp = rep.imp || { };
rep.imp.orgFormat = 'JRS';
}
// Now apply the resume representation onto this object
extend( true, this, rep );
// Set up metadata
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;
};
/**
Return a unique list of all keywords across all skills.
*/
FreshResume.prototype.keywords = function() {
var flatSkills = [];
this.skills && this.skills.length &&
(flatSkills = this.skills.map(function(sk) { return sk.name; }));
return flatSkills;
},
/**
Reset the sheet to an empty state.
*/
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 (empty) sheet.
*/
FreshResume.default = function() {
return new FreshResume().open(
PATH.join( __dirname, 'empty-fresh.json'), 'Empty' );
};
/**
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, { // 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() {
var empHist = __.get(this, 'employment.history');
if( empHist && empHist.length ) {
var firstJob = _.last( this.employment.history );
var careerStart = firstJob.start ? firstJob.safe.start : '';
if ((typeof careerStart === 'string' || careerStart instanceof String) &&
!careerStart.trim())
return 0;
var careerLast = _.max( this.employment.history, function( w ) {
return( w.safe && w.safe.end ) ? w.safe.end.unix() : moment().unix();
});
return careerLast.safe.end.diff( careerStart, 'years' );
}
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( ) {
__.get(this, 'employment.history') && this.employment.history.sort( byDateDesc );
__.get(this, 'education.history') && this.education.history.sort( byDateDesc );
__.get(this, 'service.history') && this.service.history.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.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;
});
function byDateDesc(a,b) {
return( a.safe.start.isBefore(b.safe.start) ) ? 1
: ( a.safe.start.isAfter(b.safe.start) && -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}$/
//
-269
View File
@@ -1,269 +0,0 @@
/**
Definition of the JRSResume class.
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
@module jrs-resume.js
*/
(function() {
var FS = require('fs')
, extend = require('../utils/extend')
, validator = require('is-my-json-valid')
, _ = require('underscore')
, PATH = require('path')
, moment = require('moment');
/**
The JRSResume class represent a specific JSON character sheet. When Sheet.open
is called, we merge the loaded JSON sheet properties onto the Sheet instance
via extend(), so a full-grown sheet object will have all of the methods here,
plus a complement of JSON properties from the backing JSON file. That allows
us to treat Sheet objects interchangeably with the loaded JSON model.
@class JRSResume
*/
function JRSResume() {
}
/**
Open and parse the specified JSON resume sheet. 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.
*/
JRSResume.prototype.open = function( file, title ) {
//this.imp = { fileName: file }; <-- schema violation, tuck it into .basics instead
this.basics = {
imp: {
fileName: file,
raw: FS.readFileSync( file, 'utf8' )
}
};
return this.parse( this.basics.imp.raw, title );
};
/**
Save the sheet to disk (for environments that have disk access).
*/
JRSResume.prototype.save = function( filename ) {
this.basics.imp.fileName = filename || this.basics.imp.fileName;
FS.writeFileSync( this.basics.imp.fileName, this.stringify( this ), 'utf8' );
return this;
};
/**
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 );
};
/**
Open and parse the specified JSON resume sheet. 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.
*/
JRSResume.prototype.parse = function( stringData, opts ) {
opts = opts || { };
var rep = JSON.parse( stringData );
extend( true, this, rep );
// 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;
}
// 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;
};
/**
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;
};
/**
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() {
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, 'years' );
}
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;
}
};
/**
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;
}());
-13
View File
@@ -1,13 +0,0 @@
(function(){
var FRESHResume = require('../core/fresh-resume');
module.exports = function loadSourceResumes( src, log, fn ) {
return src.map( function( res ) {
log( 'Reading '.info + 'SOURCE'.infoBold + ' resume: '.info +
res.cyan.bold );
return (fn && fn(res)) || (new FRESHResume()).open( res );
});
};
}());
-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."
}
}
}
}
}
}
-275
View File
@@ -1,275 +0,0 @@
/**
Definition of the Theme class.
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
@module theme.js
*/
(function() {
var FS = require('fs')
, extend = require('../utils/extend')
, validator = require('is-my-json-valid')
, _ = require('underscore')
, PATH = require('path')
, parsePath = require('parse-filepath')
, EXTEND = require('../utils/extend')
, moment = require('moment')
, RECURSIVE_READ_DIR = require('recursive-readdir-sync');
/**
The Theme class is a representation of a HackMyResume theme asset.
@class Theme
*/
function Theme() {
}
/**
Open and parse the specified theme.
*/
Theme.prototype.open = function( themeFolder ) {
// Open the [theme-name].json file; should have the same name as folder
this.folder = themeFolder;
var pathInfo = parsePath( themeFolder );
var themeFile = PATH.join( themeFolder, pathInfo.basename + '.json' );
var themeInfo = JSON.parse( FS.readFileSync( themeFile, 'utf8' ) );
var that = this;
// Move properties from the theme JSON file to the theme object
EXTEND( true, this, themeInfo );
// Set up a formats has for the theme
var formatsHash = { };
// 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 );
this.explicit = true;
}
else {
formatsHash = loadImplicit.call( this );
}
// Add freebie formats every theme gets
formatsHash.json = { title: 'json', outFormat: 'json', pre: 'json', ext: 'json', path: null, data: null };
formatsHash.yml = { title: 'yaml', outFormat: 'yml', pre: 'yml', ext: 'yml', path: null, data: null };
// 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.
*/
Theme.prototype.hasFormat = function( fmt ) {
return _.has( this.formats, fmt );
};
/**
Determine if the theme supports the specified output format.
*/
Theme.prototype.getFormat = function( fmt ) {
return this.formats[ fmt ];
};
function loadImplicit() {
// Set up a hash of formats supported by this theme.
var formatsHash = { };
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 = RECURSIVE_READ_DIR( 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|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'); }))
.forEach(function( cssf ) {
// For each CSS file, get its corresponding HTML file
var idx = _.findIndex(fmts, function( fmt ) {
return fmt && fmt.pre === cssf.pre && fmt.ext === 'html';
});
cssf.action = null;
fmts[ idx ].css = cssf.data;
fmts[ idx ].cssPath = cssf.path;
});
// Remove CSS files from the formats array
fmts = fmts.filter( function( fmt) {
return fmt && (fmt.ext !== 'css');
});
return formatsHash;
}
function loadExplicit() {
var that = this;
// Set up a hash of formats supported by this theme.
var formatsHash = { };
// Establish the base theme folder
var tplFolder = PATH.join( this.folder, 'src' );
var act = null;
// 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 = RECURSIVE_READ_DIR( 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'; }))
.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;
}
function friendlyName( val ) {
val = val.trim().toLowerCase();
var friendly = { yml: 'yaml', md: 'markdown', txt: 'text' };
return friendly[val] || val;
}
module.exports = Theme;
}());
-169
View File
@@ -1,169 +0,0 @@
/**
Generic template helper definitions for FluentCV.
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
@module generic-helpers.js
*/
(function() {
var MD = require('marked')
, H2W = require('../utils/html-to-wpml')
, moment = require('moment')
, _ = require('underscore');
/**
Generic template helper function definitions.
@class GenericHelpers
*/
var GenericHelpers = module.exports = {
/**
Convert the input date to a specified format through Moment.js.
@method formatDate
*/
formatDate: function(datetime, format) {
return moment ? moment( datetime ).format( format ) : datetime;
},
/**
Convert inline Markdown to inline WordProcessingML.
@method wpml
*/
wpml: function( txt, inline ) {
if(!txt) return '';
inline = (inline && !inline.hash) || false;
txt = inline ?
MD(txt.trim()).replace(/^\s*<p>|<\/p>\s*$/gi, '') :
MD(txt.trim());
txt = H2W( txt.trim() );
return txt;
},
/**
Emit a conditional link.
@method link
*/
link: function( text, url ) {
return url && url.trim() ?
('<a href="' + url + '">' + text + '</a>') : text;
},
/**
Return the last word of the specified text.
@method lastWord
*/
lastWord: function( txt ) {
return txt && txt.trim() ? _.last( txt.split(' ') ) : '';
},
/**
Convert a skill level to an RGB color triplet.
@method skillColor
@param lvl Input skill level. Skill level can be expressed as a string
("beginner", "intermediate", etc.), as an integer (1,5,etc), as a string
integer ("1", "5", etc.), or as an RRGGBB color triplet ('#C00000',
'#FFFFAA').
*/
skillColor: function( lvl ) {
var idx = skillLevelToIndex( lvl );
var skillColors = (this.theme && this.theme.palette &&
this.theme.palette.skillLevels) ||
[ '#FFFFFF', '#5CB85C', '#F1C40F', '#428BCA', '#C00000' ];
return skillColors[idx];
},
/**
Return an appropriate height.
@method lastWord
*/
skillHeight: function( lvl ) {
var idx = skillLevelToIndex( lvl );
return ['38.25', '30', '16', '8', '0'][idx];
},
/**
Return all but the last word of the input text.
@method initialWords
*/
initialWords: function( txt ) {
return txt && txt.trim() ? _.initial( txt.split(' ') ).join(' ') : '';
},
/**
Trim the protocol (http or https) from a URL/
@method trimURL
*/
trimURL: function( url ) {
return url && url.trim() ? url.trim().replace(/^https?:\/\//i, '') : '';
},
/**
Convert text to lowercase.
@method toLower
*/
toLower: function( txt ) {
return txt && txt.trim() ? txt.toLowerCase() : '';
},
/**
Return true if either value is truthy.
@method either
*/
either: function( lhs, rhs, options ) {
if (lhs || rhs) return options.fn(this);
},
/**
Perform a generic comparison.
See: http://doginthehat.com.au/2012/02/comparison-block-helper-for-handlebars-templates
@method compare
*/
compare: function(lvalue, rvalue, options) {
if (arguments.length < 3)
throw new Error("Handlerbars Helper 'compare' needs 2 parameters");
var operator = options.hash.operator || "==";
var operators = {
'==': function(l,r) { return l == r; },
'===': function(l,r) { return l === r; },
'!=': function(l,r) { return l != r; },
'<': function(l,r) { return l < r; },
'>': function(l,r) { return l > r; },
'<=': function(l,r) { return l <= r; },
'>=': function(l,r) { return l >= r; },
'typeof': function(l,r) { return typeof l == r; }
};
if (!operators[operator])
throw new Error("Handlerbars Helper 'compare' doesn't know the operator "+operator);
var result = operators[operator](lvalue,rvalue);
return result ? options.fn(this) : options.inverse(this);
}
};
function skillLevelToIndex( lvl ) {
var idx = 0;
if( String.is( lvl ) ) {
lvl = lvl.trim().toLowerCase();
var intVal = parseInt( lvl );
if( isNaN( intVal ) ) {
switch( lvl ) {
case 'beginner': idx = 1; break;
case 'intermediate': idx = 2; break;
case 'advanced': idx = 3; break;
case 'master': idx = 4; break;
}
}
else {
idx = Math.min( intVal / 2, 4 );
idx = Math.max( 0, idx );
}
}
else {
idx = Math.min( lvl / 2, 4 );
idx = Math.max( 0, idx );
}
return idx;
}
}());
-50
View File
@@ -1,50 +0,0 @@
/**
Definition of the HandlebarsGenerator class.
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
@module handlebars-generator.js
*/
(function() {
var _ = require('underscore')
, HANDLEBARS = require('handlebars')
, FS = require('fs')
, registerHelpers = require('./handlebars-helpers');
/**
Perform template-based resume generation using Handlebars.js.
@class HandlebarsGenerator
*/
var HandlebarsGenerator = module.exports = {
generate: function( json, jst, format, cssInfo, opts, theme ) {
// Pre-compile any partials present in the theme.
_.each( theme.partials, function( el ) {
var tplData = FS.readFileSync( el.path, 'utf8' );
var compiledTemplate = HANDLEBARS.compile( tplData );
HANDLEBARS.registerPartial( el.name, compiledTemplate );
});
// Register necessary helpers.
registerHelpers( theme );
// Compile and run the Handlebars template.
var template = HANDLEBARS.compile(jst);
return template({
r: format === 'html' || format === 'pdf' ? json.markdownify() : json,
RAW: json,
filt: opts.filters,
cssInfo: cssInfo,
headFragment: opts.headFragment || ''
});
}
};
}());
-25
View File
@@ -1,25 +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 ) {
helpers.theme = theme;
HANDLEBARS.registerHelper( helpers );
};
}());
-52
View File
@@ -1,52 +0,0 @@
/**
Definition of the UnderscoreGenerator class.
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
@module underscore-generator.js
*/
(function() {
var _ = require('underscore');
/**
Perform template-based resume generation using Underscore.js.
@class UnderscoreGenerator
*/
var UnderscoreGenerator = module.exports = {
generate: function( json, jst, format, cssInfo, opts, theme ) {
// Tweak underscore's default template delimeters
var delims = (opts.themeObj && opts.themeObj.delimeters) || opts.template;
if( opts.themeObj && opts.themeObj.delimeters ) {
delims = _.mapObject( delims, function(val,key) {
return new RegExp( val, "ig");
});
}
_.templateSettings = delims;
// Strip {# comments #}
jst = jst.replace( delims.comment, '');
// Compile and run the template. TODO: avoid unnecessary recompiles.
var compiled = _.template(jst);
var ret = compiled({
r: format === 'html' || format === 'pdf' ? json.markdownify() : json,
filt: opts.filters,
XML: require('xml-escape'),
RAW: json,
cssInfo: cssInfo,
headFragment: opts.headFragment || ''
});
return ret;
}
};
}());
-46
View File
@@ -1,46 +0,0 @@
/**
Definition of the BaseGenerator class.
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
@module base-generator.js
*/
(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: {
success: 0,
themeNotFound: 1,
copyCss: 2,
resumeNotFound: 3,
missingCommand: 4,
invalidCommand: 5
},
/**
Generator options.
*/
opts: {
}
});
}());
-31
View File
@@ -1,31 +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');
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 ) {
return this.opts.prettify ?
HTML.prettyPrint( info.mk, this.opts.prettify ) : info.mk;
}
});
}());
-72
View File
@@ -1,72 +0,0 @@
/**
Definition of the HtmlPdfGenerator class.
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
@module html-pdf-generator.js
*/
(function() {
var TemplateGenerator = require('./template-generator')
, FS = require('fs-extra')
, HTML = require( 'html' );
/**
An HTML-based PDF resume generator for HackMyResume.
*/
var HtmlPdfGenerator = module.exports = TemplateGenerator.extend({
init: function() {
this._super( 'pdf', 'html' );
},
/**
Generate the binary PDF.
*/
onBeforeSave: function( info ) {
pdf( info.mk, info.outputFile );
return null; // halt further processing
}
});
/**
Generate a PDF from HTML.
*/
function pdf( markup, fOut ) {
var pdfCount = 0;
if( false ) { //( _opts.pdf === 'phantom' || _opts.pdf == 'all' ) {
pdfCount++;
require('phantom').create( function( ph ) {
ph.createPage( function( page ) {
page.setContent( markup );
page.set('paperSize', {
format: 'A4',
orientation: 'portrait',
margin: '1cm'
});
page.set("viewportSize", {
width: 1024, // TODO: option-ify
height: 768 // TODO: Use "A" sizes
});
page.set('onLoadFinished', function(success) {
page.render( fOut );
pdfCount++;
ph.exit();
});
},
{ dnodeOpts: { weak: false } } );
});
}
if( true ) { // _opts.pdf === 'wkhtmltopdf' || _opts.pdf == 'all' ) {
var fOut2 = fOut;
if( pdfCount == 1 ) {
fOut2 = fOut2.replace(/\.pdf$/g, '.b.pdf');
}
require('wkhtmltopdf')( markup, { pageSize: 'letter' } )
.pipe( FS.createWriteStream( fOut2 ) );
pdfCount++;
}
}
}());
-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' );
}
});
-298
View File
@@ -1,298 +0,0 @@
/**
Definition of the TemplateGenerator class.
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
@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('../utils/extend')
, Theme = require('../core/theme');
// Default 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
}
};
/**
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({
init: function( outputFormat, templateFormat, cssFile ){
this._super( outputFormat );
this.tplFormat = templateFormat || outputFormat;
},
/**
String-based template generation method.
@method invoke
@param rez A FreshResume object.
@param opts Generator options.
@returns An array of strings representing generated output files.
*/
invoke: function( rez, opts ) {
// Carry over options
this.opts = EXTEND( true, { }, _defaultOpts, opts );
// Load the theme
var themeInfo = themeFromMoniker.call( this );
var theme = themeInfo.theme;
var tFolder = themeInfo.folder;
var tplFolder = PATH.join( tFolder, 'src' );
var curFmt = theme.getFormat( this.format );
var that = this;
// "Generate": process individual files within the theme
return {
files: curFmt.files.map( function( tplInfo ) {
return {
info: tplInfo,
data: tplInfo.action === 'transform' ?
transform.call( that, rez, tplInfo, theme ) : undefined
};
}).filter(function(item){ return item !== null; }),
themeInfo: themeInfo
};
},
/**
File-based template generation method.
@method generate
@param rez A FreshResume object.
@param f Full path to the output resume file to generate.
@param opts Generator options.
*/
generate: function( rez, f, opts ) {
// Call the generation method
var genInfo = this.invoke( rez, opts );
// Carry over options
this.opts = EXTEND( true, { }, _defaultOpts, opts );
// Load the theme
var themeInfo = genInfo.themeInfo;
var theme = themeInfo.theme;
var tFolder = themeInfo.folder;
var tplFolder = PATH.join( tFolder, 'src' );
var outFolder = parsePath(f).dirname;
var curFmt = theme.getFormat( this.format );
var that = this;
// "Generate": process individual files within the theme
genInfo.files.forEach(function( file ){
var thisFilePath;
if( file.info.action === 'transform' ) {
thisFilePath = PATH.join( outFolder, file.info.orgPath );
try {
if( that.onBeforeSave ) {
file.data = that.onBeforeSave({
theme: theme,
outputFile: (file.info.major ? f : thisFilePath),
mk: file.data
});
if( !file.data ) return; // PDF etc
}
var fileName = file.info.major ? f : thisFilePath;
MKDIRP.sync( PATH.dirname( fileName ) );
FS.writeFileSync( fileName, file.data,
{ encoding: 'utf8', flags: 'w' } );
that.onAfterSave && that.onAfterSave(
{ outputFile: fileName, mk: file.data } );
}
catch(ex) {
console.log(ex);
}
}
else if( file.info.action === null/* && theme.explicit*/ ) {
thisFilePath = PATH.join( outFolder, file.info.orgPath );
try {
MKDIRP.sync( PATH.dirname(thisFilePath) );
FS.copySync( file.info.path, thisFilePath );
}
catch(ex) {
console.log(ex);
}
}
});
// 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);
});
}
},
/**
Perform a single resume JSON-to-DEST resume transformation.
@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, cssInfo, opts, theme ) {
this.opts.freezeBreaks && ( jst = freeze(jst) );
var eng = require( '../eng/' + theme.engine + '-generator' );
var result = eng.generate( json, jst, format, cssInfo, opts, theme );
this.opts.freezeBreaks && ( result = unfreeze(result) );
return result;
}
});
/**
Export the TemplateGenerator function/ctor.
*/
module.exports = TemplateGenerator;
/**
Given a theme title, load the corresponding theme.
*/
function themeFromMoniker() {
// Verify the specified theme name/path
var tFolder = PATH.join(
parsePath( require.resolve('fluent-themes') ).dirname,
this.opts.theme
);
var exists = require('path-exists').sync;
if( !exists( tFolder ) ) {
tFolder = PATH.resolve( this.opts.theme );
if( !exists( tFolder ) ) {
throw { fluenterror: this.codes.themeNotFound, data: this.opts.theme};
}
}
var t = this.opts.themeObj || new Theme().open( tFolder );
// Load the theme and format
return {
theme: t,
folder: tFolder
};
}
function transform( rez, tplInfo, theme ) {
try {
var cssInfo = {
file: tplInfo.css ? tplInfo.cssPath : null,
data: tplInfo.css || null
};
return this.single( rez, tplInfo.data, this.format, cssInfo, this.opts,
theme );
}
catch(ex) {
console.log(ex);
}
}
/**
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' );
}
/**
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' );
}
});
}());
-22
View File
@@ -1,22 +0,0 @@
/**
External API surface for HackMyResume.
@license MIT. Copyright (c) 2015 James M. Devlin / FluentDesk.
@module hackmyapi.js
*/
module.exports = {
Sheet: require('./core/fresh-resume'),
FRESHResume: require('./core/fresh-resume'),
JRSResume: require('./core/jrs-resume'),
Theme: require('./core/theme'),
FluentDate: require('./core/fluent-date'),
HtmlGenerator: require('./gen/html-generator'),
TextGenerator: require('./gen/text-generator'),
HtmlPdfGenerator: require('./gen/html-pdf-generator'),
WordGenerator: require('./gen/word-generator'),
MarkdownGenerator: require('./gen/markdown-generator'),
JsonGenerator: require('./gen/json-generator'),
YamlGenerator: require('./gen/yaml-generator'),
JsonYamlGenerator: require('./gen/json-yaml-generator'),
LaTeXGenerator: require('./gen/latex-generator')
};
-45
View File
@@ -1,45 +0,0 @@
/**
Internal resume generation logic for HackMyResume.
@license MIT. Copyright (c) 2015 James M. Devlin / FluentDesk.
@module hackmycmd.js
*/
(function() {
module.exports = function () {
var unused = require('./utils/string')
, PATH = require('path');
/**
Display help documentation.
*/
function help() {
console.log( FS.readFileSync( PATH.join(__dirname, 'use.txt'), 'utf8' )
.useful.bold );
}
/**
Internal module interface. Used by FCV Desktop and HMR.
*/
return {
verbs: {
generate: require('./verbs/generate'),
build: require('./verbs/generate'),
validate: require('./verbs/validate'),
convert: require('./verbs/convert'),
create: require('./verbs/create'),
new: require('./verbs/create'),
help: help
},
lib: require('./hackmyapi'),
options: require('./core/default-options'),
formats: require('./core/default-formats')
};
}();
}());
// [1]: JSON.parse throws SyntaxError on invalid JSON. See:
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse
-136
View File
@@ -1,136 +0,0 @@
#! /usr/bin/env node
/**
Command-line interface (CLI) for HackMyResume.
@license MIT. Copyright (c) 2015 James M. Devlin / FluentDesk.
@module index.js
*/
var ARGS = require( 'minimist' )
, FCMD = require( './hackmycmd')
, PKG = require('../package.json')
, COLORS = require('colors')
, FS = require('fs')
, PATH = require('path')
, opts = { }
, title = ('\n*** HackMyResume v' + PKG.version + ' ***').bold.white
, _ = require('underscore');
try {
main();
}
catch( ex ) {
handleError( ex );
}
function main() {
// Colorize
COLORS.setTheme({
title: ['white','bold'],
info: process.platform === 'win32' ? 'gray' : ['white','dim'],
infoBold: ['white','dim'],
warn: 'yellow',
error: 'red',
guide: 'yellow',
status: 'gray',//['white','dim'],
useful: 'green',
});
// Setup
if( process.argv.length <= 2 ) { throw { fluenterror: 4 }; }
var a = ARGS( process.argv.slice(2) );
opts = getOpts( a );
logMsg( title );
// Get the action to be performed
var params = a._.map( function(p){ return p.toLowerCase().trim(); });
var verb = params[0];
if( !FCMD.verbs[ verb ] ) {
logMsg('Invalid command: "'.warn + verb.warn.bold + '"'.warn);
return;
}
// Find the TO keyword, if any
var splitAt = _.indexOf( params, 'to' );
if( splitAt === a._.length - 1 ) {
// 'TO' cannot be the last argument
logMsg('Please '.warn + 'specify an output file'.warn.bold +
' for this operation or '.warn + 'omit the TO keyword'.warn.bold +
'.'.warn );
return;
}
// Massage inputs and outputs
var src = a._.slice(1, splitAt === -1 ? undefined : splitAt );
var dst = splitAt === -1 ? [] : a._.slice( splitAt + 1 );
( splitAt === -1 ) && src.length > 1 && dst.push( src.pop() ); // Allow omitting TO keyword
var parms = [ src, dst, opts, logMsg ];
// Invoke the action
FCMD.verbs[ verb ].apply( null, parms );
}
function logMsg( msg ) {
opts.silent || console.log( msg );
}
function getOpts( args ) {
var noPretty = args.nopretty || args.n;
noPretty = noPretty && (noPretty === true || noPretty === 'true');
return {
theme: args.t || 'modern',
format: args.f || 'FRESH',
prettify: !noPretty,
silent: args.s || args.silent
};
}
// TODO: refactor
function handleError( ex ) {
var msg = '', exitCode;
if( ex.fluenterror ){
switch( ex.fluenterror ) { // TODO: Remove magic numbers
case 1: msg = "The specified theme couldn't be found: " + ex.data; break;
case 2: msg = "Couldn't copy CSS file to destination folder"; break;
case 3: msg = 'Please '.guide + 'specify a valid input resume'.guide.bold + ' in FRESH or JSON Resume format.'.guide; break;
case 4: msg = title + "\nPlease ".guide + "specify a command".guide.bold + " (".guide +
Object.keys( FCMD.verbs ).map( function(v, idx, ar) {
return (idx === ar.length - 1 ? 'or '.guide : '') +
v.toUpperCase().guide;
}).join(', '.guide) + ").\n\n".guide + FS.readFileSync( PATH.join(__dirname, 'use.txt'), 'utf8' ).info.bold;
break;
//case 4: msg = title + '\n' + ; break;
case 5: msg = 'Please '.guide + 'specify the output resume file'.guide.bold + ' that should be created.'.guide; break;
case 6: msg = 'Please '.guide + 'specify a valid input resume'.guide.bold + ' in either FRESH or JSON Resume format.'.guide; break;
case 7: msg = 'Please '.guide + 'specify an output file name'.guide.bold + ' for every input file you wish to convert.'.guide; break;
case 8: msg = 'Please '.guide + 'specify the filename of the resume'.guide.bold + ' to create.'.guide; break;
}
exitCode = ex.fluenterror;
}
else {
msg = ex.toString();
exitCode = 4;
}
var idx = msg.indexOf('Error: ');
var trimmed = idx === -1 ? msg : msg.substring( idx + 7 );
if( !ex.fluenterror || ex.fluenterror < 3 ) { // TODO: magic #s
console.log( ('ERROR: ' + trimmed.toString()).red.bold );
console.log( ex.stack.gray);
}
else
console.log( trimmed.toString() );
process.exit( exitCode );
}
-25
View File
@@ -1,25 +0,0 @@
Usage:
hackmyresume <COMMAND> <SOURCES> [TO <TARGETS>] [-t <THEME>] [-f <FORMAT>]
<COMMAND> should be BUILD, NEW, CONVERT, VALIDATE, or HELP. <SOURCES> should
be the path to one or more FRESH or JSON Resume format resumes. <TARGETS>
should be the name of the destination resume to be created, if any. The
<THEME> parameter should be the name of a predefined theme (for example:
COMPACT, MINIMIST, MODERN, or HELLO-WORLD) or the relative path to a custom
theme. <FORMAT> should be either FRESH (for a FRESH-format resume) or JRS
(for a JSON Resume-format resume).
hackmyresume BUILD resume.json TO out/resume.all
hackmyresume NEW resume.json
hackmyresume CONVERT resume.json TO resume-jrs.json
hackmyresume VALIDATE resume.json
Both SOURCES and TARGETS can accept multiple files:
hackmyresume BUILD r1.json r2.json TO out/resume.all out2/resume.html
hackmyresume NEW r1.json r2.json r3.json
hackmyresume VALIDATE resume.json resume2.json resume3.json
See https://github.com/hacksalot/hackmyresume/blob/master/README.md for more
information.
-72
View File
@@ -1,72 +0,0 @@
/**
Definition of John Resig's `Class` class.
@module class.js
*/
/* Simple JavaScript Inheritance
* By John Resig http://ejohn.org/
* MIT Licensed.
* http://ejohn.org/blog/simple-javascript-inheritance/
*/
// Inspired by base2 and Prototype
(function(){
var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
// The base Class implementation (does nothing)
this.Class = function(){};
module.exports = Class;
// Create a new Class that inherits from this class
Class.extend = function(prop) {
var _super = this.prototype;
// Instantiate a base class (but only create the instance,
// don't run the init constructor)
initializing = true;
var prototype = new this();
initializing = false;
// Copy the properties over onto the new prototype
for (var name in prop) {
// Check if we're overwriting an existing function
prototype[name] = typeof prop[name] == "function" &&
typeof _super[name] == "function" && fnTest.test(prop[name]) ?
(function(name, fn){
return function() {
var tmp = this._super;
// Add a new ._super() method that is the same method
// but on the super-class
this._super = _super[name];
// The method only need to be bound temporarily, so we
// remove it when we're done executing
var ret = fn.apply(this, arguments);
this._super = tmp;
return ret;
};
})(name, prop[name]) : // jshint ignore:line
prop[name];
}
// The dummy class constructor
function Class() {
// All construction is actually done in the init method
if ( !initializing && this.init )
this.init.apply(this, arguments);
}
// Populate our constructed prototype object
Class.prototype = prototype;
// Enforce the constructor to be what we expect
Class.prototype.constructor = Class;
// And make this class extendable
Class.extend = arguments.callee;
return Class;
};
})();
-79
View File
@@ -1,79 +0,0 @@
/**
Definition of the `extend` method.
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
@module extend.js
*/
function _extend() {
function isPlainObject( obj ) {
if ((typeof obj !== "object") || obj.nodeType ||
(obj !== null && obj === obj.window)) {
return false;
}
if (obj.constructor &&
!hasOwnProperty.call( obj.constructor.prototype, "isPrototypeOf" )) {
return false;
}
return true;
}
var options
, name
, src
, copy
, copyIsArray
, clone
, target = arguments[0] || {}
, i = 1
, length = arguments.length
, deep = false;
// Handle a deep copy situation
if (typeof target === "boolean") {
deep = target;
// Skip the boolean and the target
target = arguments[i] || {};
i++;
}
// Handle case when target is a string or something (possible in deep copy)
//if (typeof target !== "object" && !jQuery.isFunction(target))
if (typeof target !== "object" && typeof target !== "function")
target = {};
for (; i < length; i++) {
// Only deal with non-null/undefined values
if ((options = arguments[i]) !== null) {
// Extend the base object
for (name in options) {
src = target[name];
copy = options[name];
// Prevent never-ending loop
if (target === copy) continue;
// Recurse if we're merging plain objects or arrays
if (deep && copy && (isPlainObject(copy) ||
(copyIsArray = (copy.constructor === Array)))) {
if (copyIsArray) {
copyIsArray = false;
clone = src && (src.constructor === Array) ? src : [];
} else {
clone = src && isPlainObject(src) ? src : {};
}
// Never move original objects, clone them
target[name] = _extend(deep, clone, copy);
// Don't bring in undefined values
} else if (copy !== undefined) {
target[name] = copy;
}
}
}
}
// Return the modified object
return target;
}
module.exports = _extend;
-64
View File
@@ -1,64 +0,0 @@
/**
Definition of the Markdown to WordProcessingML conversion routine.
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
@module html-to-wpml.js
*/
(function(){
var _ = require('underscore');
var HTML5Tokenizer = require('simple-html-tokenizer');
module.exports = function( html ) {
// Tokenize the HTML stream.
var tokens = HTML5Tokenizer.tokenize( html );
var final = '', is_bold, is_italic, is_link, link_url;
// Process <em>, <strong>, and <a> elements in the HTML stream, producing
// equivalent WordProcessingML that can be dumped into a <w:p> or other
// text container element.
_.each( tokens, function( tok ) {
switch( tok.type ) {
case 'StartTag':
switch( tok.tagName ) {
case 'p': final += '<w:p>'; break;
case 'strong': is_bold = true; break;
case 'em': is_italic = true; break;
case 'a':
is_link = true;
link_url = tok.attributes.filter(function(attr){
return attr[0] === 'href'; }
)[0][1];
break;
}
break;
case 'EndTag':
switch( tok.tagName ) {
case 'p': final += '</w:p>'; break;
case 'strong': is_bold = false; break;
case 'em': is_italic = false; break;
case 'a': is_link = false; break;
}
break;
case 'Chars':
var style = is_bold ? '<w:b/>' : '';
style += is_italic ? '<w:i/>': '';
style += is_link ? '<w:rStyle w:val="Hyperlink"/>' : '';
final +=
(is_link ? ('<w:hlink w:dest="' + link_url + '">') : '') +
'<w:r><w:rPr>' + style + '</w:rPr><w:t>' + tok.chars +
'</w:t></w:r>' + (is_link ? '</w:hlink>' : '');
break;
}
});
return final;
};
}());
-23
View File
@@ -1,23 +0,0 @@
/**
Definitions of string utility functions.
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
@module string.js
*/
/**
Determine if the string is null, empty, or whitespace.
See: http://stackoverflow.com/a/32800728/4942583
@method isNullOrWhitespace
*/
String.isNullOrWhitespace = function( input ) {
return !input || !input.trim();
};
String.prototype.endsWith = function(suffix) {
return this.indexOf(suffix, this.length - suffix.length) !== -1;
};
String.is = function( val ) {
return typeof val === 'string' || val instanceof String;
};
-30
View File
@@ -1,30 +0,0 @@
(function(){
var loadSourceResumes = require('../core/load-source-resumes');
/**
Convert between FRESH and JRS formats.
*/
module.exports = function convert( src, dst, opts, logger ) {
var _log = logger || console.log;
if( !src || !src.length ) { throw { fluenterror: 6 }; }
if( !dst || !dst.length ) {
if( src.length === 1 ) { throw { fluenterror: 5 }; }
else if( src.length === 2 ) { dst = [ src[1] ]; src = [ src[0] ]; }
else { throw { fluenterror: 5 }; }
}
if( src && dst && src.length && dst.length && src.length !== dst.length ) {
throw { fluenterror: 7 };
}
var sheets = loadSourceResumes( src, _log );
sheets.forEach(function(sheet, idx){
var sourceFormat = sheet.imp.orgFormat === 'JRS' ? 'JRS' : 'FRESH';
var targetFormat = sourceFormat === 'JRS' ? 'FRESH' : 'JRS';
_log( 'Converting '.useful + sheet.imp.fileName.useful.bold + (' (' +
sourceFormat + ') to ').useful + dst[0].useful.bold +
(' (' + targetFormat + ').').useful );
sheet.saveAs( dst[idx], targetFormat );
});
};
}());
-22
View File
@@ -1,22 +0,0 @@
(function(){
var FLUENT = require('../hackmyapi')
, MKDIRP = require('mkdirp')
, PATH = require('path');
/**
Create a new empty resume in either FRESH or JRS format.
*/
module.exports = function create( src, dst, opts, logger ) {
var _log = logger || console.log;
if( !src || !src.length ) throw { fluenterror: 8 };
src.forEach( function( t ) {
var safeFormat = opts.format.toUpperCase();
_log('Creating new '.useful +safeFormat.useful.bold +
' resume: '.useful + t.useful.bold);
MKDIRP.sync( PATH.dirname( t ) ); // Ensure dest folder exists;
FLUENT[ safeFormat + 'Resume' ].default().save( t );
});
};
}());
-150
View File
@@ -1,150 +0,0 @@
(function() {
var PATH = require('path')
, parsePath = require('parse-filepath')
, MKDIRP = require('mkdirp')
, _opts = require('../core/default-options')
, FluentTheme = require('../core/theme')
, loadSourceResumes = require('../core/load-source-resumes')
, _ = require('underscore')
, _fmts = require('../core/default-formats')
, _err, _log, rez;
/**
Handle an exception.
*/
function error( ex ) {
throw ex;
}
module.exports =
/**
Given a source JSON resume, a destination resume path, and a theme file,
generate 0..N resumes in the desired formats.
@param src Path to the source JSON resume file: "rez/resume.json".
@param dst An array of paths to the target resume file(s).
@param theme Friendly name of the resume theme. Defaults to "modern".
@param logger Optional logging override.
*/
function generate( src, dst, opts, logger, errHandler ) {
_log = logger || console.log;
_err = errHandler || error;
//_opts = extend( true, _opts, opts );
_opts.theme = (opts.theme && opts.theme.toLowerCase().trim())|| 'modern';
_opts.prettify = opts.prettify === true ? _opts.prettify : false;
// Load input resumes...
if( !src || !src.length ) { throw { fluenterror: 3 }; }
var sheets = loadSourceResumes( src, _log );
// Merge input resumes...
var msg = '';
rez = _.reduceRight( sheets, function( a, b, idx ) {
msg += ((idx == sheets.length - 2) ?
'Merging '.gray+ a.imp.fileName : '') + ' onto '.gray + b.imp.fileName;
return extend( true, b, a );
});
msg && _log(msg);
// Verify the specified theme name/path
var relativeThemeFolder = '../../node_modules/fluent-themes/themes';
var tFolder = PATH.resolve( __dirname, relativeThemeFolder, _opts.theme);
var exists = require('path-exists').sync;
if (!exists( tFolder )) {
tFolder = PATH.resolve( _opts.theme );
if (!exists( tFolder )) {
throw { fluenterror: 1, data: _opts.theme };
}
}
// Load the theme
var theTheme = (new FluentTheme()).open( tFolder );
_opts.themeObj = theTheme;
_log( 'Applying '.info + theTheme.name.toUpperCase().infoBold +
(' theme (' + Object.keys(theTheme.formats).length + ' formats)').info);
// Expand output resumes... (can't use map() here)
var targets = [], that = this;
( (dst && dst.length && dst) || ['resume.all'] ).forEach( function(t) {
var to = PATH.resolve(t),
pa = parsePath(to),
fmat = pa.extname || '.all';
targets.push.apply(targets, fmat === '.all' ?
Object.keys( theTheme.formats ).map(function(k){
var z = theTheme.formats[k];
return { file: to.replace(/all$/g,z.outFormat), fmt: z };
}) : [{ file: to, fmt: theTheme.getFormat( fmat.slice(1) ) }]);
});
// Run the transformation!
var finished = targets.map( function(t) { return single(t, theTheme); });
// Don't send the client back empty-handed
return { sheet: rez, targets: targets, processed: finished };
};
/**
Generate a single resume of a specific format.
@param f Full path to the destination resume to generate, for example,
"/foo/bar/resume.pdf" or "c:\foo\bar\resume.txt".
*/
function single( targInfo, theme ) {
try {
var f = targInfo.file
, fType = targInfo.fmt.outFormat
, fName = PATH.basename(f, '.' + fType)
, theFormat;
// If targInfo.fmt.files exists, this theme has an explicit "files"
// section in its theme.json file.
if( targInfo.fmt.files && targInfo.fmt.files.length ) {
_log( 'Generating '.useful +
targInfo.fmt.outFormat.toUpperCase().useful.bold +
' resume: '.useful + PATH.relative(process.cwd(), f ).replace(/\\/g,'/').useful.bold);
theFormat = _fmts.filter(
function(fmt) { return fmt.name === targInfo.fmt.outFormat; })[0];
MKDIRP.sync( PATH.dirname( f ) ); // Ensure dest folder exists;
theFormat.gen.generate( rez, f, _opts );
// targInfo.fmt.files.forEach( function( form ) {
//
// if( form.action === 'transform' ) {
// var theFormat = _fmts.filter( function( fmt ) {
// return fmt.name === targInfo.fmt.outFormat;
// })[0];
// MKDIRP.sync( PATH.dirname( f ) ); // Ensure dest folder exists;
// theFormat.gen.generate( rez, f, _opts );
// }
// else if( form.action === null ) {
// // Copy the file
// }
//
// });
}
// Otherwise the theme has no files section
else {
_log( 'Generating '.useful +
targInfo.fmt.outFormat.toUpperCase().useful.bold +
' resume: '.useful + PATH.relative(process.cwd(), f ).replace(/\\/g,'/').useful.bold);
theFormat = _fmts.filter(
function(fmt) { return fmt.name === targInfo.fmt.outFormat; })[0];
MKDIRP.sync( PATH.dirname( f ) ); // Ensure dest folder exists;
theFormat.gen.generate( rez, f, _opts );
}
}
catch( ex ) {
_err( ex );
}
}
}());
-97
View File
@@ -1,97 +0,0 @@
(function() {
var FS = require('fs');
var loadSourceResumes = require('../core/load-source-resumes');
module.exports =
/**
Validate 1 to N resumes in either FRESH or JSON Resume format.
*/
function validate( src, unused, opts, logger ) {
var _log = logger || console.log;
if( !src || !src.length ) { throw { fluenterror: 6 }; }
var isValid = true;
var validator = require('is-my-json-valid');
var schemas = {
fresh: require('fresca'),
jars: require('../core/resume.json')
};
// Load input resumes...
var sheets = loadSourceResumes(src, _log, function( res ) {
try {
return {
file: res,
raw: FS.readFileSync( res, 'utf8' )
};
}
catch( ex ) {
throw ex;
}
});
sheets.forEach( function( rep ) {
var rez;
try {
rez = JSON.parse( rep.raw );
}
catch( ex ) { // Note [1]
_log('Validating '.info + rep.file.infoBold +
' against FRESH/JRS schema: '.info + 'ERROR!'.error.bold);
if (ex instanceof SyntaxError) {
// Invalid JSON
_log( '--> '.bold.red + rep.file.toUpperCase().red +
' contains invalid JSON. Unable to validate.'.red );
_log( (' INTERNAL: ' + ex).red );
}
else {
_log(('ERROR: ' + ex.toString()).red.bold);
}
return;
}
var isValid = false;
var style = 'useful';
var errors = [];
var fmt = rez.meta &&
(rez.meta.format === 'FRESH@0.1.0') ? 'fresh':'jars';
try {
var validate = validator( schemas[ fmt ], { // Note [1]
formats: {
date: /^\d{4}(?:-(?:0[0-9]{1}|1[0-2]{1})(?:-[0-9]{2})?)?$/
}
});
isValid = validate( rez );
if( !isValid ) {
style = 'warn';
errors = validate.errors;
}
}
catch(ex) {
return;
}
_log( 'Validating '.info + rep.file.infoBold + ' against '.info +
fmt.replace('jars','JSON Resume').toUpperCase().infoBold +
' schema: '.info + (isValid ? 'VALID!' : 'INVALID')[style].bold );
errors.forEach(function(err,idx) {
_log( '--> '.bold.yellow +
(err.field.replace('data.','resume.').toUpperCase() + ' ' +
err.message).yellow );
});
});
};
}());
-91
View File
@@ -1,91 +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": [
""
]
}
]
}
@@ -1,104 +0,0 @@
{
"basics": {
"name": "Jane Q. Fullstacker"
},
"education": [
{
"gpa": "3.5",
"courses": [
"Course 1",
"Course 2",
"Course 2"
],
"startDate": "2005-09",
"endDate": "2008-05"
},
{
"institution": "Medfield College",
"gpa": "3.2",
"courses": [
"Course 1",
"Course 2",
"Course 2"
],
"startDate": "2003-09",
"endDate": "2005-06"
}
],
"skills": [
{
"name": "Web Dev",
"keywords": [
"JavaScript",
"HTML 5",
"CSS",
"LAMP",
"MVC",
"REST"
]
},
{
"name": "JavaScript"
}
],
"volunteer": [],
"publications": [
{
"name": "Building User Interfaces with Electron and Atom",
"releaseDate": "2011",
"website": "http://codeproject.com/build-ui-electron-atom.aspx"
},
{
"name": "Jane Fullstacker's Blog",
"publisher": "self",
"releaseDate": "2011",
"website": "http://janef.me"
},
{
"name": "Teach Yourself GORFF in 21 Days",
"publisher": "Amazon"
}
],
"interests": [
{
"name": "reading",
"summary": "Jane is a fan of mystery novels and courtroom dramas including Agatha Christie and John Grisham.",
"keywords": [
"mystery",
"Agatha Christie",
"John Grisham"
]
},
{
"name": "hiking",
"summary": "Jane enjoys hiking, light mountain climbing, and has four summits under her belt!"
},
{
"name": "yoga"
}
],
"references": [
{
"name": "John Davidson",
"reference": "Jane is awesome! I'd hire her again in a heartbeat."
},
{
"name": "Elias Fullstacker",
"reference": "I worked with Jane on Jabberwocky and can vouch for her awesome technical capabilities and attention to detail. Insta-hire."
},
{
"name": "Dana Nevins",
"reference": "I've known Jane personally and professionally for almost ten years. She is one in a million."
}
],
"languages": [
{
"language": "English",
"level": "Native"
},
{
"language": "Spanish",
"level": "Moderate"
}
]
}
@@ -1,263 +0,0 @@
{
"basics": {
"name": "Jane Q. Fullstacker",
"label": "Senior Developer",
"summary": "**Full-stack software developer with 6+ years industry experience** specializing in scalable cloud architectures for this, that, and the other. A native of southern CA, Jane enjoys hiking, mystery novels, and the company of Rufus, her two-year-old beagle.",
"website": "http://janef.me/blog",
"phone": "1-650-999-7777",
"email": "jdoe@onecoolstartup.io",
"picture": "jane_doe.png",
"location": {
"address": "Jane Fullstacker\n123 Somewhere Rd.\nMountain View, CA 94035",
"postalCode": "94035",
"city": "Mountain View",
"countryCode": "US",
"region": "CA"
},
"profiles": [
{
"network": "GitHub",
"username": "janef-was-here",
"url": "https://github.com/janef-was-here"
},
{
"network": "Twitter",
"username": "janef-was-here",
"url": "https://twitter.com/janef-was-here"
}
]
},
"work": [
{
"company": "One Cool Startup",
"website": "https://onecool.io/does-not-exist",
"position": "Head Code Ninja",
"startDate": "2013-09",
"summary": "Development team manager for OneCoolApp and OneCoolWebsite, a free social network tiddlywink generator and lifestyle portal with over 200,000 users.",
"highlights": [
"Managed a 5-person development team",
"Accomplishment 2",
"Etc."
]
},
{
"company": "Veridian Dynamics",
"website": "https://en.wikipedia.org/wiki/Better_Off_Ted#Plot",
"position": "Principal Developer",
"startDate": "2011-07",
"endDate": "2013-08",
"summary": "Developer on numerous projects culminating in technical lead role for the [Jabberwocky project](http://betteroffted.wikia.com/wiki/Jabberwocky) and promotion to principal developer.",
"highlights": [
"Managed a 5-person development team",
"Accomplishment 2",
"Etc."
]
},
{
"company": "Stark Industries",
"position": "IT Administrator",
"startDate": "2008-10",
"endDate": "2011-06",
"summary": "Junior programmer with heavy code responsibilities. Promoted to intermediate role after 6 months.",
"highlights": [
"Promoted to intermediate developer after 6 months",
"Accomplishment 2",
"Etc."
]
},
{
"company": "Dunder Mifflin",
"position": "Intern",
"startDate": "2008-06",
"endDate": "2008-09",
"summary": "Performed IT administration and deployments for Dunder Mifflin.",
"highlights": [
"Supervised roll-out of Dunder Mifflin Infinity website.",
"Performed mission-critical system backups and ",
"Etc."
]
}
],
"education": [
{
"institution": "Cornell University",
"gpa": "3.5",
"courses": [
"Course 1",
"Course 2",
"Course 2"
],
"startDate": "2005-09",
"endDate": "2008-05"
},
{
"institution": "Medfield College",
"gpa": "3.2",
"courses": [
"Course 1",
"Course 2",
"Course 2"
],
"startDate": "2003-09",
"endDate": "2005-06"
}
],
"skills": [
{
"name": "Web Dev",
"keywords": [
"JavaScript",
"HTML 5",
"CSS",
"LAMP",
"MVC",
"REST"
]
},
{
"name": "JavaScript",
"keywords": [
"Node.js",
"Angular.js",
"jQuery",
"Bootstrap",
"React.js",
"Backbone.js"
]
},
{
"name": "Database",
"keywords": [
"MySQL",
"PostgreSQL",
"NoSQL",
"ORM",
"Hibernate"
]
},
{
"name": "Cloud",
"keywords": [
"AWS",
"EC2",
"RDS",
"S3",
"Azure",
"Dropbox"
]
},
{
"name": "Project",
"keywords": [
"Agile",
"TFS",
"Unified Process",
"MS Project"
]
}
],
"volunteer": [
{
"organization": "Technology for Tots",
"position": "Technical Consultant",
"startDate": "2003-11",
"endDate": "2005-06",
"website": "http://technology-for-tots.org",
"summary": "Summary of this volunteer stint.",
"highlights": [
"Accomplishment 1",
"Accomplishment 2",
"etc"
]
},
{
"organization": "US Army Reserves",
"position": "NCO",
"startDate": "1999-11",
"endDate": "2003-06",
"website": "http://www.usar.army.mil/",
"summary": "Summary of this military stint.",
"highlights": [
"Accomplishment 1",
"Accomplishment 2",
"etc"
]
}
],
"awards": [
{
"title": "Honorable Mention",
"date": "2012",
"awarder": "Google"
},
{
"title": "Summa cum laude",
"date": "2012",
"awarder": "Cornell University"
}
],
"publications": [
{
"name": "Building User Interfaces with Electron and Atom",
"publisher": "Code Project",
"releaseDate": "2011",
"website": "http://codeproject.com/build-ui-electron-atom.aspx"
},
{
"name": "Jane Fullstacker's Blog",
"publisher": "self",
"releaseDate": "2011",
"website": "http://janef.me"
},
{
"name": "Teach Yourself GORFF in 21 Days",
"publisher": "Amazon",
"releaseDate": "2008",
"website": "http://url.to.publication.com/blah",
"summary": "A primer on the programming language of GORFF, whose for loops are coterminous with all of time and space."
}
],
"interests": [
{
"name": "reading",
"summary": "Jane is a fan of mystery novels and courtroom dramas including Agatha Christie and John Grisham.",
"keywords": [
"mystery",
"Agatha Christie",
"John Grisham"
]
},
{
"name": "hiking",
"summary": "Jane enjoys hiking, light mountain climbing, and has four summits under her belt!"
},
{
"name": "yoga"
}
],
"references": [
{
"name": "John Davidson",
"reference": "Jane is awesome! I'd hire her again in a heartbeat."
},
{
"name": "Elias Fullstacker",
"reference": "I worked with Jane on Jabberwocky and can vouch for her awesome technical capabilities and attention to detail. Insta-hire."
},
{
"name": "Dana Nevins",
"reference": "I've known Jane personally and professionally for almost ten years. She is one in a million."
}
],
"languages": [
{
"language": "English",
"level": "Native"
},
{
"language": "Spanish",
"level": "Moderate",
"years": 10
}
]
}
@@ -1,130 +0,0 @@
{
"basics": {
"name": "Richard Hendriks",
"label": "Programmer",
"picture": "",
"email": "richard.hendriks@gmail.com",
"phone": "(912) 555-4321",
"website": "http://richardhendricks.com",
"summary": "Richard hails from Tulsa. He has earned degrees from the University of Oklahoma and Stanford. (Go Sooners and Cardinals!) Before starting Pied Piper, he worked for Hooli as a part time software developer. While his work focuses on applied information theory, mostly optimizing lossless compression schema of both the length-limited and adaptive variants, his non-work interests range widely, everything from quantum computing to chaos theory. He could tell you about it, but THAT would NOT be a “length-limited” conversation!",
"location": {
"address": "2712 Broadway St",
"postalCode": "CA 94115",
"city": "San Francisco",
"countryCode": "US",
"region": "California"
},
"profiles": [
{
"network": "Twitter",
"username": "neutralthoughts",
"url": ""
},
{
"network": "SoundCloud",
"username": "dandymusicnl",
"url": "https://soundcloud.com/dandymusicnl"
}
]
},
"work": [
{
"company": "Pied Piper",
"position": "CEO/President",
"website": "http://piedpiper.com",
"startDate": "2013-12-01",
"endDate": "2014-12-01",
"summary": "Pied Piper is a multi-platform technology based on a proprietary universal compression algorithm that has consistently fielded high Weisman Scores™ that are not merely competitive, but approach the theoretical limit of lossless compression.",
"highlights": [
"Build an algorithm for artist to detect if their music was violating copy right infringement laws",
"Successfully won Techcrunch Disrupt",
"Optimized an algorithm that holds the current world record for Weisman Scores"
]
}
],
"volunteer": [
{
"organization": "CoderDojo",
"position": "Teacher",
"website": "http://coderdojo.com/",
"startDate": "2012-01-01",
"endDate": "2013-01-01",
"summary": "Global movement of free coding clubs for young people.",
"highlights": [
"Awarded 'Teacher of the Month'"
]
}
],
"education": [
{
"institution": "University of Oklahoma",
"area": "Information Technology",
"studyType": "Bachelor",
"startDate": "2011-06-01",
"endDate": "2014-01-01",
"gpa": "4.0",
"courses": [
"DB1101 - Basic SQL",
"CS2011 - Java Introduction"
]
}
],
"awards": [
{
"title": "Digital Compression Pioneer Award",
"date": "2014-11-01",
"awarder": "Techcrunch",
"summary": "There is no spoon."
}
],
"publications": [
{
"name": "Video compression for 3d media",
"publisher": "Hooli",
"releaseDate": "2014-10-01",
"website": "http://en.wikipedia.org/wiki/Silicon_Valley_(TV_series)",
"summary": "Innovative middle-out compression algorithm that changes the way we store data."
}
],
"skills": [
{
"name": "Web Development",
"level": "Master",
"keywords": [
"HTML",
"CSS",
"Javascript"
]
},
{
"name": "Compression",
"level": "Master",
"keywords": [
"Mpeg",
"MP4",
"GIF"
]
}
],
"languages": [
{
"language": "English",
"fluency": "Native speaker"
}
],
"interests": [
{
"name": "Wildlife",
"keywords": [
"Ferrets",
"Unicorns"
]
}
],
"references": [
{
"name": "Erlich Bachman",
"reference": "It is my pleasure to recommend Richard, his performance working as a consultant for Main St. Company proved that he will be a valuable addition to any company."
}
]
}

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