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

|

|
||||||
|
|
||||||
FluentCV is a dev-friendly Swiss Army knife for resumes and CVs. Use it to:
|
HackMyResume is a dev-friendly, local-only Swiss Army knife for resumes and CVs.
|
||||||
|
Use it to:
|
||||||
|
|
||||||
1. **Generate** HTML, Markdown, LaTeX, MS Word, PDF, plain text, JSON, XML,
|
1. **Generate** HTML, Markdown, LaTeX, MS Word, PDF, plain text, JSON, XML,
|
||||||
YAML, print, smoke signal, carrier pigeon, and other arbitrary-format resumes
|
YAML, print, smoke signal, carrier pigeon, and other arbitrary-format resumes
|
||||||
@ -14,7 +16,8 @@ and CVs, from a single source of truth—without violating DRY.
|
|||||||
2. **Convert** resumes between [FRESH][fresca] and [JSON Resume][6] formats.
|
2. **Convert** resumes between [FRESH][fresca] and [JSON Resume][6] formats.
|
||||||
3. **Validate** resumes against either format.
|
3. **Validate** resumes against either format.
|
||||||
|
|
||||||
FluentCV supports both the [FRESH][fresca] and [JSON Resume][6] source formats.
|
HackMyResume is built with Node.js and runs on recent versions of OS X, Linux,
|
||||||
|
or Windows.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
@ -22,8 +25,6 @@ FluentCV supports both the [FRESH][fresca] and [JSON Resume][6] source formats.
|
|||||||
- Store your resume data as a durable, versionable JSON or YAML document.
|
- Store your resume data as a durable, versionable JSON or YAML document.
|
||||||
- Generate polished resumes in multiple formats without violating [DRY][dry].
|
- Generate polished resumes in multiple formats without violating [DRY][dry].
|
||||||
- Output to HTML, Markdown, LaTeX, PDF, MS Word, JSON, YAML, plain text, or XML.
|
- Output to HTML, Markdown, LaTeX, PDF, MS Word, JSON, YAML, plain text, or XML.
|
||||||
- Compatible with [FRESH][fresh], [JSON Resume][6], [FRESCA][fresca], and
|
|
||||||
[FCV Desktop][7].
|
|
||||||
- Validate resumes against the FRESH or JSON Resume schema.
|
- Validate resumes against the FRESH or JSON Resume schema.
|
||||||
- Support for multiple input and output resumes.
|
- Support for multiple input and output resumes.
|
||||||
- Use from your command line or [desktop][7].
|
- Use from your command line or [desktop][7].
|
||||||
@ -31,10 +32,10 @@ FluentCV supports both the [FRESH][fresca] and [JSON Resume][6] source formats.
|
|||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
Install FluentCV with NPM:
|
Install HackMyResume with NPM:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
[sudo] npm install fluentcv -g
|
[sudo] npm install hackmyresume -g
|
||||||
```
|
```
|
||||||
|
|
||||||
Note: for PDF generation you'll need to install a copy of [wkhtmltopdf][3] for
|
Note: for PDF generation you'll need to install a copy of [wkhtmltopdf][3] for
|
||||||
@ -43,49 +44,52 @@ access to `xelatex` and similar.
|
|||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
To use FluentCV you'll need to create a valid resume in either [FRESH][fresca]
|
To use HackMyResume you'll need to create a valid resume in either
|
||||||
or [JSON Resume][6] format. Then you can start using the command line tool.
|
[FRESH][fresca] or [JSON Resume][6] format. Then you can start using the command
|
||||||
There are four basic commands you should be aware of:
|
line tool. There are four basic commands you should be aware of:
|
||||||
|
|
||||||
- `build` generates resumes in HTML, Word, Markdown, PDF, and other formats. Use
|
- `**build**` generates resumes in HTML, Word, Markdown, PDF, and other formats.
|
||||||
it when you need to submit, upload, print, or email resumes in specific formats.
|
Use it when you need to submit, upload, print, or email resumes in specific
|
||||||
|
formats.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# fluentcv BUILD <INPUTS> TO <OUTPUTS> [-t THEME]
|
# hackmyresume BUILD <INPUTS> TO <OUTPUTS> [-t THEME]
|
||||||
fluentcv BUILD resume.json TO out/resume.all
|
hackmyresume BUILD resume.json TO out/resume.all
|
||||||
fluentcv BUILD r1.json r2.json TO out/rez.html out/rez.md foo/rez.all
|
hackmyresume BUILD r1.json r2.json TO out/rez.html out/rez.md foo/rez.all
|
||||||
```
|
```
|
||||||
|
|
||||||
- `new` creates a new resume in FRESH or JSON Resume format.
|
- `**new**` creates a new resume in FRESH or JSON Resume format.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# fluentcv NEW <OUTPUTS> [-f <FORMAT>]
|
# hackmyresume NEW <OUTPUTS> [-f <FORMAT>]
|
||||||
fluentcv NEW resume.json
|
hackmyresume NEW resume.json
|
||||||
fluentcv NEW resume.json -f fresh
|
hackmyresume NEW resume.json -f fresh
|
||||||
fluentcv NEW r1.json r2.json -f jrs
|
hackmyresume NEW r1.json r2.json -f jrs
|
||||||
```
|
```
|
||||||
|
|
||||||
- `convert` converts your source resume between FRESH and JSON Resume formats.
|
- `**convert**` converts your source resume between FRESH and JSON Resume
|
||||||
Use it to convert between the two formats to take advantage of tools and services.
|
formats.
|
||||||
|
Use it to convert between the two formats to take advantage of tools and
|
||||||
|
services.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# fluentcv CONVERT <INPUTS> TO <OUTPUTS>
|
# hackmyresume CONVERT <INPUTS> TO <OUTPUTS>
|
||||||
fluentcv CONVERT resume.json TO resume-jrs.json
|
hackmyresume CONVERT resume.json TO resume-jrs.json
|
||||||
fluentcv CONVERT 1.json 2.json 3.json TO out/1.json out/2.json out/3.json
|
hackmyresume CONVERT 1.json 2.json 3.json TO out/1.json out/2.json out/3.json
|
||||||
```
|
```
|
||||||
|
|
||||||
- `validate` validates the specified resume against either the FRESH or JSON
|
- `**validate**` validates the specified resume against either the FRESH or JSON
|
||||||
Resume schema. Use it to make sure your resume data is sufficient and complete.
|
Resume schema. Use it to make sure your resume data is sufficient and complete.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# fluentcv VALIDATE <INPUTS>
|
# hackmyresume VALIDATE <INPUTS>
|
||||||
fluentcv VALIDATE resume.json
|
hackmyresume VALIDATE resume.json
|
||||||
fluentcv VALIDATE r1.json r2.json r3.json
|
hackmyresume VALIDATE r1.json r2.json r3.json
|
||||||
```
|
```
|
||||||
|
|
||||||
## Supported Output Formats
|
## Supported Output Formats
|
||||||
|
|
||||||
FluentCV supports these output formats:
|
HackMyResume supports these output formats:
|
||||||
|
|
||||||
Output Format | Ext | Notes
|
Output Format | Ext | Notes
|
||||||
------------- | --- | -----
|
------------- | --- | -----
|
||||||
@ -103,11 +107,11 @@ image | .png, .bmp | Forthcoming.
|
|||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
FluentCV requires a recent version of [Node.js][4] and [NPM][5]. Then:
|
HackMyResume requires a recent version of [Node.js][4] and [NPM][5]. Then:
|
||||||
|
|
||||||
1. Install the latest official [wkhtmltopdf][3] binary for your platform.
|
1. Install the latest official [wkhtmltopdf][3] binary for your platform.
|
||||||
2. Optionally install an updated LaTeX environment (LaTeX resumes only).
|
2. Optionally install an updated LaTeX environment (LaTeX resumes only).
|
||||||
2. Install **fluentCV** with `[sudo] npm install fluentcv -g`.
|
2. Install **HackMyResume** with `[sudo] npm install hackmyresume -g`.
|
||||||
3. You're ready to go.
|
3. You're ready to go.
|
||||||
|
|
||||||
## Use
|
## Use
|
||||||
@ -116,7 +120,7 @@ Assuming you've got a JSON-formatted resume handy, generating resumes in
|
|||||||
different formats and combinations easy. Just run:
|
different formats and combinations easy. Just run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
fluentcv BUILD <INPUTS> <OUTPUTS> [-t theme].
|
hackmyresume BUILD <INPUTS> <OUTPUTS> [-t theme].
|
||||||
```
|
```
|
||||||
|
|
||||||
Where `<INPUTS>` is one or more .json resume files, separated by spaces;
|
Where `<INPUTS>` is one or more .json resume files, separated by spaces;
|
||||||
@ -125,25 +129,25 @@ theme (default to Modern). For example:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Generate all resume formats (HTML, PDF, DOC, TXT, YML, etc.)
|
# Generate all resume formats (HTML, PDF, DOC, TXT, YML, etc.)
|
||||||
fluentcv build resume.json -o out/resume.all -t modern
|
hackmyresume build resume.json -o out/resume.all -t modern
|
||||||
|
|
||||||
# Generate a specific resume format
|
# Generate a specific resume format
|
||||||
fluentcv build resume.json TO out/resume.html
|
hackmyresume build resume.json TO out/resume.html
|
||||||
fluentcv build resume.json TO out/resume.pdf
|
hackmyresume build resume.json TO out/resume.pdf
|
||||||
fluentcv build resume.json TO out/resume.md
|
hackmyresume build resume.json TO out/resume.md
|
||||||
fluentcv build resume.json TO out/resume.doc
|
hackmyresume build resume.json TO out/resume.doc
|
||||||
fluentcv build resume.json TO out/resume.json
|
hackmyresume build resume.json TO out/resume.json
|
||||||
fluentcv build resume.json TO out/resume.txt
|
hackmyresume build resume.json TO out/resume.txt
|
||||||
fluentcv build resume.json TO out/resume.yml
|
hackmyresume build resume.json TO out/resume.yml
|
||||||
|
|
||||||
# Specify 2 inputs and 3 outputs
|
# Specify 2 inputs and 3 outputs
|
||||||
fluentcv build in1.json in2.json TO out.html out.doc out.pdf
|
hackmyresume build in1.json in2.json TO out.html out.doc out.pdf
|
||||||
```
|
```
|
||||||
|
|
||||||
You should see something to the effect of:
|
You should see something to the effect of:
|
||||||
|
|
||||||
```
|
```
|
||||||
*** FluentCV v0.9.0 ***
|
*** HackMyResume v0.9.0 ***
|
||||||
Reading JSON resume: foo/resume.json
|
Reading JSON resume: foo/resume.json
|
||||||
Applying MODERN Theme (7 formats)
|
Applying MODERN Theme (7 formats)
|
||||||
Generating HTML resume: out/resume.html
|
Generating HTML resume: out/resume.html
|
||||||
@ -159,28 +163,37 @@ Generating YAML resume: out/resume.yml
|
|||||||
|
|
||||||
### Applying a theme
|
### Applying a theme
|
||||||
|
|
||||||
You can specify a predefined or custom theme via the optional `-t` parameter. For a predefined theme, include the theme name. For a custom theme, include the path to the custom theme's folder.
|
You can specify a predefined or custom theme via the optional `-t` parameter.
|
||||||
|
For a predefined theme, include the theme name. For a custom theme, include the
|
||||||
|
path to the custom theme's folder.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
fluentcv build resume.json -t modern
|
hackmyresume build resume.json -t modern
|
||||||
fluentcv build resume.json -t ~/foo/bar/my-custom-theme/
|
hackmyresume build resume.json -t ~/foo/bar/my-custom-theme/
|
||||||
```
|
```
|
||||||
|
|
||||||
As of v0.9.0, available predefined themes are `modern`, `minimist`, and `hello-world`, and `compact`.
|
As of v1.0.0, available predefined themes are `positive`, `modern`, `compact`,
|
||||||
|
`minimist`, and `hello-world`.
|
||||||
|
|
||||||
### Merging resumes
|
### Merging resumes
|
||||||
|
|
||||||
You can **merge multiple resumes together** by specifying them in order from most generic to most specific:
|
You can **merge multiple resumes together** by specifying them in order from
|
||||||
|
most generic to most specific:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Merge specific.json onto base.json and generate all formats
|
# Merge specific.json onto base.json and generate all formats
|
||||||
fluentcv build base.json specific.json -o resume.all
|
hackmyresume build base.json specific.json -o resume.all
|
||||||
```
|
```
|
||||||
|
|
||||||
This can be useful for overriding a base (generic) resume with information from a specific (targeted) resume. For example, you might override your generic catch-all "software developer" resume with specific details from your targeted "game developer" resume, or combine two partial resumes into a "complete" resume. Merging follows conventional [extend()][9]-style behavior and there's no arbitrary limit to how many resumes you can merge:
|
This can be useful for overriding a base (generic) resume with information from
|
||||||
|
a specific (targeted) resume. For example, you might override your generic
|
||||||
|
catch-all "software developer" resume with specific details from your targeted
|
||||||
|
"game developer" resume, or combine two partial resumes into a "complete"
|
||||||
|
resume. Merging follows conventional [extend()][9]-style behavior and there's
|
||||||
|
no arbitrary limit to how many resumes you can merge:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
fluentcv build in1.json in2.json in3.json in4.json TO out.html out.doc
|
hackmyresume build in1.json in2.json in3.json in4.json TO out.html out.doc
|
||||||
Reading JSON resume: in1.json
|
Reading JSON resume: in1.json
|
||||||
Reading JSON resume: in2.json
|
Reading JSON resume: in2.json
|
||||||
Reading JSON resume: in3.json
|
Reading JSON resume: in3.json
|
||||||
@ -192,72 +205,75 @@ Generating WORD resume: out.doc
|
|||||||
|
|
||||||
### Multiple targets
|
### Multiple targets
|
||||||
|
|
||||||
You can specify **multiple output targets** and FluentCV will build them:
|
You can specify **multiple output targets** and HackMyResume will build them:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Generate out1.doc, out1.pdf, and foo.txt from me.json.
|
# Generate out1.doc, out1.pdf, and foo.txt from me.json.
|
||||||
fluentcv build me.json -o out1.doc -o out1.pdf -o foo.txt
|
hackmyresume build me.json -o out1.doc -o out1.pdf -o foo.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also omit the output file(s) and/or theme completely:
|
You can also omit the output file(s) and/or theme completely:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Equivalent to "fluentcv resume.json resume.all -t modern"
|
# Equivalent to "hackmyresume resume.json resume.all -t modern"
|
||||||
fluentcv build resume.json
|
hackmyresume build resume.json
|
||||||
```
|
```
|
||||||
|
|
||||||
### Using .all
|
### Using .all
|
||||||
|
|
||||||
The special `.all` extension tells FluentCV to generate all supported output formats for the given resume. For example, this...
|
The special `.all` extension tells HackMyResume to generate all supported output
|
||||||
|
formats for the given resume. For example, this...
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Generate all resume formats (HTML, PDF, DOC, TXT, etc.)
|
# Generate all resume formats (HTML, PDF, DOC, TXT, etc.)
|
||||||
fluentcv build me.json -o out/resume.all
|
hackmyresume build me.json -o out/resume.all
|
||||||
```
|
```
|
||||||
|
|
||||||
..tells FluentCV to read `me.json` and generate `out/resume.md`, `out/resume.doc`, `out/resume.html`, `out/resume.txt`, `out/resume.pdf`, and `out/resume.json`.
|
..tells HackMyResume to read `me.json` and generate `out/resume.md`,
|
||||||
|
`out/resume.doc`, `out/resume.html`, `out/resume.txt`, `out/resume.pdf`, and
|
||||||
|
`out/resume.json`.
|
||||||
|
|
||||||
### Validating
|
### Validating
|
||||||
|
|
||||||
FluentCV can also validate your resumes against either the [FRESH /
|
HackMyResume can also validate your resumes against either the [FRESH /
|
||||||
FRESCA][fresca] or [JSON Resume][6] formats. To validate one or more existing
|
FRESCA][fresca] or [JSON Resume][6] formats. To validate one or more existing
|
||||||
resumes, use the `validate` command:
|
resumes, use the `validate` command:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Validate myresume.json against either the FRESH or JSON Resume schema.
|
# Validate myresume.json against either the FRESH or JSON Resume schema.
|
||||||
fluentcv validate resumeA.json resumeB.json
|
hackmyresume validate resumeA.json resumeB.json
|
||||||
```
|
```
|
||||||
|
|
||||||
FluentCV will validate each specified resume in turn:
|
HackMyResume will validate each specified resume in turn:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
*** FluentCV v0.9.0 ***
|
*** HackMyResume v0.9.0 ***
|
||||||
Validating JSON resume: resumeA.json (INVALID)
|
Validating JSON resume: resumeA.json (INVALID)
|
||||||
Validating JSON resume: resumeB.json (VALID)
|
Validating JSON resume: resumeB.json (VALID)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Converting
|
### Converting
|
||||||
|
|
||||||
FluentCV can convert between the [FRESH][fresca] and [JSON Resume][6] formats.
|
HackMyResume can convert between the [FRESH][fresca] and [JSON Resume][6]
|
||||||
Just run:
|
formats. Just run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
fluentcv CONVERT <INPUTS> <OUTPUTS>
|
hackmyresume CONVERT <INPUTS> <OUTPUTS>
|
||||||
```
|
```
|
||||||
|
|
||||||
where <INPUTS> is one or more resumes in FRESH or JSON Resume format, and
|
where <INPUTS> is one or more resumes in FRESH or JSON Resume format, and
|
||||||
<OUTPUTS> is a corresponding list of output file names. FluentCV will autodetect
|
<OUTPUTS> is a corresponding list of output file names. HackMyResume will
|
||||||
the format (FRESH or JRS) of each input resume and convert it to the other
|
autodetect the format (FRESH or JRS) of each input resume and convert it to the
|
||||||
format (JRS or FRESH).
|
other format (JRS or FRESH).
|
||||||
|
|
||||||
### Prettifying
|
### Prettifying
|
||||||
|
|
||||||
FluentCV applies [js-beautify][10]-style HTML prettification by default to
|
HackMyResume applies [js-beautify][10]-style HTML prettification by default to
|
||||||
HTML-formatted resumes. To disable prettification, the `--nopretty` or `-n` flag
|
HTML-formatted resumes. To disable prettification, the `--nopretty` or `-n` flag
|
||||||
can be used:
|
can be used:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
fluentcv generate resume.json out.all --nopretty
|
hackmyresume generate resume.json out.all --nopretty
|
||||||
```
|
```
|
||||||
|
|
||||||
### Silent Mode
|
### Silent Mode
|
||||||
@ -265,8 +281,8 @@ fluentcv generate resume.json out.all --nopretty
|
|||||||
Use `-s` or `--silent` to run in silent mode:
|
Use `-s` or `--silent` to run in silent mode:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
fluentcv generate resume.json -o someFile.all -s
|
hackmyresume generate resume.json -o someFile.all -s
|
||||||
fluentcv generate resume.json -o someFile.all --silent
|
hackmyresume generate resume.json -o someFile.all --silent
|
||||||
```
|
```
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 53 KiB |
BIN
assets/hackmyresume_cli.png
Normal file
BIN
assets/hackmyresume_cli.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 27 KiB |
BIN
assets/resume-bouqet.png
Normal file
BIN
assets/resume-bouqet.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 170 KiB |
29
package.json
29
package.json
@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"name": "fluentcv",
|
"name": "hackmyresume",
|
||||||
"version": "0.10.3",
|
"version": "1.0.1",
|
||||||
"description": "Generate polished résumés and CVs in HTML, Markdown, LaTeX, MS Word, PDF, plain text, JSON, XML, YAML, smoke signal, and carrier pigeon.",
|
"description": "Generate polished résumés and CVs in HTML, Markdown, LaTeX, MS Word, PDF, plain text, JSON, XML, YAML, smoke signal, and carrier pigeon.",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/fluentdesk/fluentcv.git"
|
"url": "https://github.com/hacksalot/HackMyResume.git"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"resume",
|
"resume",
|
||||||
@ -18,23 +18,27 @@
|
|||||||
"PDF",
|
"PDF",
|
||||||
"YAML",
|
"YAML",
|
||||||
"HTML",
|
"HTML",
|
||||||
"CLI"
|
"LaTeX",
|
||||||
|
"CLI",
|
||||||
|
"Handlebars",
|
||||||
|
"Underscore",
|
||||||
|
"template"
|
||||||
],
|
],
|
||||||
"author": "James M. Devlin",
|
"author": "hacksalot <hacksalot@fluentdesk.com> (https://github.com/hacksalot)",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"preferGlobal": "true",
|
"preferGlobal": "true",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/fluentdesk/fluentcv/issues"
|
"url": "https://github.com/hacksalot/HackMyResume/issues"
|
||||||
},
|
},
|
||||||
"main": "src/fluentcmd.js",
|
"main": "src/hackmycmd.js",
|
||||||
"bin": {
|
"bin": {
|
||||||
"fluentcv": "src/index.js"
|
"hackmyresume": "src/index.js"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/fluentdesk/fluentcv",
|
"homepage": "https://github.com/hacksalot/HackMyResume",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"colors": "^1.1.2",
|
"colors": "^1.1.2",
|
||||||
"fluent-themes": "~0.6.3-beta",
|
"fluent-themes": "~0.7.0-beta",
|
||||||
"fresca": "~0.2.1",
|
"fresca": "~0.2.2",
|
||||||
"fs-extra": "^0.24.0",
|
"fs-extra": "^0.24.0",
|
||||||
"handlebars": "^4.0.5",
|
"handlebars": "^4.0.5",
|
||||||
"html": "0.0.10",
|
"html": "0.0.10",
|
||||||
@ -54,10 +58,11 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"chai": "*",
|
"chai": "*",
|
||||||
"grunt": "*",
|
"grunt": "*",
|
||||||
|
"grunt-contrib-clean": "^0.7.0",
|
||||||
"grunt-contrib-jshint": "^0.11.3",
|
"grunt-contrib-jshint": "^0.11.3",
|
||||||
"grunt-contrib-yuidoc": "^0.10.0",
|
"grunt-contrib-yuidoc": "^0.10.0",
|
||||||
"grunt-simple-mocha": "*",
|
"grunt-simple-mocha": "*",
|
||||||
"is-my-json-valid": "^2.12.2",
|
"jane-q-fullstacker": "fluentdesk/jane-q-fullstacker",
|
||||||
"mocha": "*",
|
"mocha": "*",
|
||||||
"resample": "fluentdesk/resample"
|
"resample": "fluentdesk/resample"
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
The FluentCV date representation.
|
The HackMyResume date representation.
|
||||||
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
|
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
|
||||||
@module fluent-date.js
|
@module fluent-date.js
|
||||||
*/
|
*/
|
||||||
@ -13,7 +13,7 @@ formats to be aware of here.
|
|||||||
2. The default "YYYY-MM-DD" format used in JSON Resume ("2015-02-10")
|
2. The default "YYYY-MM-DD" format used in JSON Resume ("2015-02-10")
|
||||||
3. Year-and-month only ("2015-04")
|
3. Year-and-month only ("2015-04")
|
||||||
4. Year-only "YYYY" ("2015")
|
4. Year-only "YYYY" ("2015")
|
||||||
5. The friendly FluentCV "mmm YYYY" format ("Mar 2015" or "Dec 2008")
|
5. The friendly HackMyResume "mmm YYYY" format ("Mar 2015" or "Dec 2008")
|
||||||
6. Empty dates ("", " ")
|
6. Empty dates ("", " ")
|
||||||
7. Any other date format that Moment.js can parse from
|
7. Any other date format that Moment.js can parse from
|
||||||
Note: Moment can transparently parse all or most of these, without requiring us
|
Note: Moment can transparently parse all or most of these, without requiring us
|
||||||
|
@ -16,7 +16,7 @@ Definition of the Theme class.
|
|||||||
, RECURSIVE_READ_DIR = require('recursive-readdir-sync');
|
, RECURSIVE_READ_DIR = require('recursive-readdir-sync');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
The Theme class is a representation of a FluentCV theme asset.
|
The Theme class is a representation of a HackMyResume theme asset.
|
||||||
@class Theme
|
@class Theme
|
||||||
*/
|
*/
|
||||||
function Theme() {
|
function Theme() {
|
||||||
@ -100,6 +100,7 @@ Definition of the Theme class.
|
|||||||
var outFmt = '', isMajor = false;
|
var outFmt = '', isMajor = false;
|
||||||
var portion = pathInfo.dir.replace(tplFolder,'');
|
var portion = pathInfo.dir.replace(tplFolder,'');
|
||||||
if( portion && portion.trim() ) {
|
if( portion && portion.trim() ) {
|
||||||
|
if( portion[1] === '_' ) return;
|
||||||
var reg = /^(?:\/|\\)(html|latex|doc|pdf|partials)(?:\/|\\)?/ig;
|
var reg = /^(?:\/|\\)(html|latex|doc|pdf|partials)(?:\/|\\)?/ig;
|
||||||
var res = reg.exec( portion );
|
var res = reg.exec( portion );
|
||||||
if( res ) {
|
if( res ) {
|
||||||
@ -152,7 +153,7 @@ Definition of the Theme class.
|
|||||||
.forEach(function( cssf ) {
|
.forEach(function( cssf ) {
|
||||||
// For each CSS file, get its corresponding HTML file
|
// For each CSS file, get its corresponding HTML file
|
||||||
var idx = _.findIndex(fmts, function( fmt ) {
|
var idx = _.findIndex(fmts, function( fmt ) {
|
||||||
return fmt.pre === cssf.pre && fmt.ext === 'html';
|
return fmt && fmt.pre === cssf.pre && fmt.ext === 'html';
|
||||||
});
|
});
|
||||||
cssf.action = null;
|
cssf.action = null;
|
||||||
fmts[ idx ].css = cssf.data;
|
fmts[ idx ].css = cssf.data;
|
||||||
|
@ -11,93 +11,40 @@ Definition of the HandlebarsGenerator class.
|
|||||||
var _ = require('underscore')
|
var _ = require('underscore')
|
||||||
, HANDLEBARS = require('handlebars')
|
, HANDLEBARS = require('handlebars')
|
||||||
, FS = require('fs')
|
, FS = require('fs')
|
||||||
, moment = require('moment')
|
, registerHelpers = require('./handlebars-helpers');
|
||||||
, MD = require('marked')
|
|
||||||
, H2W = require('../utils/html-to-wpml');
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Perform template-based resume generation using Handlebars.js.
|
Perform template-based resume generation using Handlebars.js.
|
||||||
@method generate
|
@class HandlebarsGenerator
|
||||||
*/
|
*/
|
||||||
module.exports = function( json, jst, format, cssInfo, opts, theme ) {
|
var HandlebarsGenerator = module.exports = {
|
||||||
|
|
||||||
// Pre-compile any partials present in the theme.
|
generate: function( json, jst, format, cssInfo, opts, 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.
|
// Pre-compile any partials present in the theme.
|
||||||
registerHelpers();
|
_.each( theme.partials, function( el ) {
|
||||||
|
var tplData = FS.readFileSync( el.path, 'utf8' );
|
||||||
|
var compiledTemplate = HANDLEBARS.compile( tplData );
|
||||||
|
HANDLEBARS.registerPartial( el.name, compiledTemplate );
|
||||||
|
});
|
||||||
|
|
||||||
// Compile and run the Handlebars template.
|
// Register necessary helpers.
|
||||||
var template = HANDLEBARS.compile(jst);
|
registerHelpers();
|
||||||
return template({
|
|
||||||
r: json,
|
// Compile and run the Handlebars template.
|
||||||
filt: opts.filters,
|
var template = HANDLEBARS.compile(jst);
|
||||||
cssInfo: cssInfo,
|
return template({
|
||||||
headFragment: opts.headFragment || ''
|
r: format === 'html' || format === 'pdf' ? json.markdownify() : json,
|
||||||
});
|
RAW: json,
|
||||||
|
filt: opts.filters,
|
||||||
|
cssInfo: cssInfo,
|
||||||
|
headFragment: opts.headFragment || ''
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
Register useful Handlebars helpers.
|
|
||||||
@method registerHelpers
|
|
||||||
*/
|
|
||||||
function registerHelpers() {
|
|
||||||
|
|
||||||
// Set up a date formatting helper so we can do:
|
|
||||||
// {{#formatDate val 'YYYY-MM'}}
|
|
||||||
HANDLEBARS.registerHelper("formatDate", function(datetime, format) {
|
|
||||||
if( moment ) {
|
|
||||||
return moment( datetime ).format( format );
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return datetime;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Set up a Markdown-to-WordProcessingML helper so we can do:
|
|
||||||
// {{#wmpl val [true|false]}}
|
|
||||||
HANDLEBARS.registerHelper("wpml", function( txt, inline ) {
|
|
||||||
inline = (inline && !inline.hash) || false;
|
|
||||||
txt = inline ?
|
|
||||||
MD(txt.trim()).replace(/^\s*<p>|<\/p>\s*$/gi, '') :
|
|
||||||
MD(txt.trim());
|
|
||||||
txt = H2W( txt.trim() );
|
|
||||||
return txt;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Set up a generic conditional helper so we can do:
|
|
||||||
// {{#compare val otherVal operator="<"}}
|
|
||||||
// http://doginthehat.com.au/2012/02/comparison-block-helper-for-handlebars-templates/
|
|
||||||
HANDLEBARS.registerHelper('compare', function(lvalue, rvalue, options) {
|
|
||||||
if (arguments.length < 3)
|
|
||||||
throw new Error("Handlerbars Helper 'compare' needs 2 parameters");
|
|
||||||
var operator = options.hash.operator || "==";
|
|
||||||
var operators = {
|
|
||||||
'==': function(l,r) { return l == r; },
|
|
||||||
'===': function(l,r) { return l === r; },
|
|
||||||
'!=': function(l,r) { return l != r; },
|
|
||||||
'<': function(l,r) { return l < r; },
|
|
||||||
'>': function(l,r) { return l > r; },
|
|
||||||
'<=': function(l,r) { return l <= r; },
|
|
||||||
'>=': function(l,r) { return l >= r; },
|
|
||||||
'typeof': function(l,r) { return typeof l == r; }
|
|
||||||
};
|
|
||||||
if (!operators[operator])
|
|
||||||
throw new Error("Handlerbars Helper 'compare' doesn't know the operator "+operator);
|
|
||||||
var result = operators[operator](lvalue,rvalue);
|
|
||||||
return result ? options.fn(this) : options.inverse(this);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}());
|
}());
|
||||||
|
124
src/eng/handlebars-helpers.js
Normal file
124
src/eng/handlebars-helpers.js
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
/**
|
||||||
|
Template helper definitions for Handlebars.
|
||||||
|
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
|
||||||
|
@module handlebars-helpers.js
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
|
||||||
|
var HANDLEBARS = require('handlebars')
|
||||||
|
, MD = require('marked')
|
||||||
|
, H2W = require('../utils/html-to-wpml')
|
||||||
|
, moment = require('moment')
|
||||||
|
, _ = require('underscore');
|
||||||
|
|
||||||
|
/**
|
||||||
|
Register useful Handlebars helpers.
|
||||||
|
@method registerHelpers
|
||||||
|
*/
|
||||||
|
module.exports = function() {
|
||||||
|
|
||||||
|
// Set up a date formatting helper so we can do:
|
||||||
|
// {{formatDate val 'YYYY-MM'}}
|
||||||
|
HANDLEBARS.registerHelper("formatDate", function(datetime, format) {
|
||||||
|
return moment ? moment( datetime ).format( format ) : datetime;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set up a Markdown-to-WordProcessingML helper so we can do:
|
||||||
|
// {{wmpl val [true|false]}}
|
||||||
|
HANDLEBARS.registerHelper("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;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set up a last-word helper so we can do:
|
||||||
|
// {{lastWord val [true|false]}}
|
||||||
|
HANDLEBARS.registerHelper("link", function( text, url ) {
|
||||||
|
return url && url.trim() ?
|
||||||
|
('<a href="' + url + '">' + text + '</a>') : text;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set up a last-word helper so we can do:
|
||||||
|
// {{lastWord val [true|false]}}
|
||||||
|
HANDLEBARS.registerHelper("lastWord", function( txt ) {
|
||||||
|
return txt && txt.trim() ? _.last( txt.split(' ') ) : '';
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set up a skill colorizing helper:
|
||||||
|
// {{skillColor val}}
|
||||||
|
HANDLEBARS.registerHelper("skillColor", function( lvl ) {
|
||||||
|
switch(lvl) {
|
||||||
|
case 'beginner': return '#5CB85C';
|
||||||
|
case 'intermediate': return '#F1C40F';
|
||||||
|
case 'advanced': return '#428BCA';
|
||||||
|
case 'master': return '#C00000';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set up a skill colorizing helper:
|
||||||
|
// {{skillColor val}}
|
||||||
|
HANDLEBARS.registerHelper("skillHeight", function( lvl ) {
|
||||||
|
switch(lvl) {
|
||||||
|
case 'beginner': return '30';
|
||||||
|
case 'intermediate': return '16';
|
||||||
|
case 'advanced': return '8';
|
||||||
|
case 'master': return '0';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set up a Markdown-to-WordProcessingML helper so we can do:
|
||||||
|
// {{initialWords val [true|false]}}
|
||||||
|
HANDLEBARS.registerHelper("initialWords", function( txt ) {
|
||||||
|
return txt && txt.trim() ? _.initial( txt.split(' ') ).join(' ') : '';
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set up a URL-trimming helper to drop the protocol so we can do:
|
||||||
|
// {{trimURL url}}
|
||||||
|
HANDLEBARS.registerHelper("trimURL", function( url ) {
|
||||||
|
return url && url.trim() ? url.trim().replace(/^https?:\/\//i, '') : '';
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set up a URL-trimming helper to drop the protocol so we can do:
|
||||||
|
// {{trimURL url}}
|
||||||
|
HANDLEBARS.registerHelper("toLower", function( txt ) {
|
||||||
|
return txt && txt.trim() ? txt.toLowerCase() : '';
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set up a Markdown-to-WordProcessingML helper so we can do:
|
||||||
|
// {{either A B}}
|
||||||
|
HANDLEBARS.registerHelper("either", function( lhs, rhs, options ) {
|
||||||
|
if (lhs || rhs) return options.fn(this);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set up a generic conditional helper so we can do:
|
||||||
|
// {{compare val otherVal operator="<"}}
|
||||||
|
// http://doginthehat.com.au/2012/02/comparison-block-helper-for-handlebars-templates/
|
||||||
|
HANDLEBARS.registerHelper('compare', function(lvalue, rvalue, options) {
|
||||||
|
if (arguments.length < 3)
|
||||||
|
throw new Error("Handlerbars Helper 'compare' needs 2 parameters");
|
||||||
|
var operator = options.hash.operator || "==";
|
||||||
|
var operators = {
|
||||||
|
'==': function(l,r) { return l == r; },
|
||||||
|
'===': function(l,r) { return l === r; },
|
||||||
|
'!=': function(l,r) { return l != r; },
|
||||||
|
'<': function(l,r) { return l < r; },
|
||||||
|
'>': function(l,r) { return l > r; },
|
||||||
|
'<=': function(l,r) { return l <= r; },
|
||||||
|
'>=': function(l,r) { return l >= r; },
|
||||||
|
'typeof': function(l,r) { return typeof l == r; }
|
||||||
|
};
|
||||||
|
if (!operators[operator])
|
||||||
|
throw new Error("Handlerbars Helper 'compare' doesn't know the operator "+operator);
|
||||||
|
var result = operators[operator](lvalue,rvalue);
|
||||||
|
return result ? options.fn(this) : options.inverse(this);
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}());
|
@ -1,37 +1,52 @@
|
|||||||
/**
|
/**
|
||||||
Definition of the UnderscoreGenerator class.
|
Definition of the UnderscoreGenerator class.
|
||||||
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
|
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
|
||||||
|
@module underscore-generator.js
|
||||||
*/
|
*/
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var _ = require('underscore');
|
var _ = require('underscore');
|
||||||
|
|
||||||
module.exports = function( json, jst, format, cssInfo, opts, theme ) {
|
|
||||||
|
|
||||||
// Tweak underscore's default template delimeters
|
|
||||||
var delims = (opts.themeObj && opts.themeObj.delimeters) || opts.template;
|
/**
|
||||||
if( opts.themeObj && opts.themeObj.delimeters ) {
|
Perform template-based resume generation using Underscore.js.
|
||||||
delims = _.mapObject( delims, function(val,key) {
|
@class UnderscoreGenerator
|
||||||
return new RegExp( val, "ig");
|
*/
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
_.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;
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}());
|
}());
|
||||||
|
@ -11,7 +11,7 @@ Definition of the HtmlPdfGenerator class.
|
|||||||
, HTML = require( 'html' );
|
, HTML = require( 'html' );
|
||||||
|
|
||||||
/**
|
/**
|
||||||
An HTML-based PDF resume generator for FluentCV.
|
An HTML-based PDF resume generator for HackMyResume.
|
||||||
*/
|
*/
|
||||||
var HtmlPdfGenerator = module.exports = TemplateGenerator.extend({
|
var HtmlPdfGenerator = module.exports = TemplateGenerator.extend({
|
||||||
|
|
||||||
|
@ -142,9 +142,8 @@ Definition of the TemplateGenerator class.
|
|||||||
*/
|
*/
|
||||||
single: function( json, jst, format, cssInfo, opts, theme ) {
|
single: function( json, jst, format, cssInfo, opts, theme ) {
|
||||||
this.opts.freezeBreaks && ( jst = freeze(jst) );
|
this.opts.freezeBreaks && ( jst = freeze(jst) );
|
||||||
var eng = require( '../eng/' + ((opts.themeObj && opts.themeObj.engine) ||
|
var eng = require( '../eng/' + theme.engine + '-generator' );
|
||||||
opts.engine) + '-generator' );
|
var result = eng.generate( json, jst, format, cssInfo, opts, theme );
|
||||||
var result = eng( json, jst, format, cssInfo, opts, theme );
|
|
||||||
this.opts.freezeBreaks && ( result = unfreeze(result) );
|
this.opts.freezeBreaks && ( result = unfreeze(result) );
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
External API surface for FluentCV:CLI.
|
External API surface for HackMyResume.
|
||||||
@license MIT. Copyright (c) 2015 James M. Devlin / FluentDesk.
|
@license MIT. Copyright (c) 2015 James M. Devlin / FluentDesk.
|
||||||
@module fluentlib.js
|
@module hackmyapi.js
|
||||||
*/
|
*/
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
Internal resume generation logic for FluentCV.
|
Internal resume generation logic for HackMyResume.
|
||||||
@license MIT. Copyright (c) 2015 James M. Devlin / FluentDesk.
|
@license MIT. Copyright (c) 2015 James M. Devlin / FluentDesk.
|
||||||
@module fluentcmd.js
|
@module hackmycmd.js
|
||||||
*/
|
*/
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
@ -12,7 +12,7 @@ Internal resume generation logic for FluentCV.
|
|||||||
, unused = require('./utils/string')
|
, unused = require('./utils/string')
|
||||||
, FS = require('fs')
|
, FS = require('fs')
|
||||||
, _ = require('underscore')
|
, _ = require('underscore')
|
||||||
, FLUENT = require('./fluentlib')
|
, FLUENT = require('./hackmyapi')
|
||||||
, PATH = require('path')
|
, PATH = require('path')
|
||||||
, MKDIRP = require('mkdirp')
|
, MKDIRP = require('mkdirp')
|
||||||
//, COLORS = require('colors')
|
//, COLORS = require('colors')
|
||||||
@ -106,7 +106,7 @@ Internal resume generation logic for FluentCV.
|
|||||||
|
|
||||||
_log( 'Generating '.useful +
|
_log( 'Generating '.useful +
|
||||||
targInfo.fmt.outFormat.toUpperCase().useful.bold +
|
targInfo.fmt.outFormat.toUpperCase().useful.bold +
|
||||||
' resume: '.useful + path.relative(process.cwd(), f ).useful.bold);
|
' resume: '.useful + path.relative(process.cwd(), f ).replace(/\\/g,'/').useful.bold);
|
||||||
|
|
||||||
theFormat = _fmts.filter(
|
theFormat = _fmts.filter(
|
||||||
function(fmt) { return fmt.name === targInfo.fmt.outFormat; })[0];
|
function(fmt) { return fmt.name === targInfo.fmt.outFormat; })[0];
|
||||||
@ -133,7 +133,7 @@ Internal resume generation logic for FluentCV.
|
|||||||
else {
|
else {
|
||||||
_log( 'Generating '.useful +
|
_log( 'Generating '.useful +
|
||||||
targInfo.fmt.outFormat.toUpperCase().useful.bold +
|
targInfo.fmt.outFormat.toUpperCase().useful.bold +
|
||||||
' resume: '.useful + path.relative(process.cwd(), f ).useful.bold);
|
' resume: '.useful + path.relative(process.cwd(), f ).replace(/\\/g,'/').useful.bold);
|
||||||
|
|
||||||
theFormat = _fmts.filter(
|
theFormat = _fmts.filter(
|
||||||
function(fmt) { return fmt.name === targInfo.fmt.outFormat; })[0];
|
function(fmt) { return fmt.name === targInfo.fmt.outFormat; })[0];
|
||||||
@ -312,7 +312,7 @@ Internal resume generation logic for FluentCV.
|
|||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Default FluentCV options.
|
Default HackMyResume options.
|
||||||
*/
|
*/
|
||||||
var _opts = {
|
var _opts = {
|
||||||
theme: 'modern',
|
theme: 'modern',
|
||||||
@ -335,7 +335,7 @@ Internal resume generation logic for FluentCV.
|
|||||||
new: create,
|
new: create,
|
||||||
help: help
|
help: help
|
||||||
},
|
},
|
||||||
lib: require('./fluentlib'),
|
lib: require('./hackmyapi'),
|
||||||
options: _opts,
|
options: _opts,
|
||||||
formats: _fmts
|
formats: _fmts
|
||||||
};
|
};
|
@ -1,19 +1,19 @@
|
|||||||
#! /usr/bin/env node
|
#! /usr/bin/env node
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Command-line interface (CLI) for FluentCV:CLI.
|
Command-line interface (CLI) for HackMyResume.
|
||||||
@license MIT. Copyright (c) 2015 James M. Devlin / FluentDesk.
|
@license MIT. Copyright (c) 2015 James M. Devlin / FluentDesk.
|
||||||
@module index.js
|
@module index.js
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var ARGS = require( 'minimist' )
|
var ARGS = require( 'minimist' )
|
||||||
, FCMD = require( './fluentcmd')
|
, FCMD = require( './hackmycmd')
|
||||||
, PKG = require('../package.json')
|
, PKG = require('../package.json')
|
||||||
, COLORS = require('colors')
|
, COLORS = require('colors')
|
||||||
, FS = require('fs')
|
, FS = require('fs')
|
||||||
, PATH = require('path')
|
, PATH = require('path')
|
||||||
, opts = { }
|
, opts = { }
|
||||||
, title = ('*** FluentCV v' + PKG.version + ' ***').bold.white
|
, title = ('\n*** HackMyResume v' + PKG.version + ' ***').bold.white
|
||||||
, _ = require('underscore');
|
, _ = require('underscore');
|
||||||
|
|
||||||
|
|
||||||
|
18
src/use.txt
18
src/use.txt
@ -1,6 +1,6 @@
|
|||||||
Usage:
|
Usage:
|
||||||
|
|
||||||
fluentcv <COMMAND> <SOURCES> [TO <TARGETS>] [-t <THEME>] [-f <FORMAT>]
|
hackmyresume <COMMAND> <SOURCES> [TO <TARGETS>] [-t <THEME>] [-f <FORMAT>]
|
||||||
|
|
||||||
<COMMAND> should be BUILD, NEW, CONVERT, VALIDATE, or HELP. <SOURCES> should
|
<COMMAND> should be BUILD, NEW, CONVERT, VALIDATE, or HELP. <SOURCES> should
|
||||||
be the path to one or more FRESH or JSON Resume format resumes. <TARGETS>
|
be the path to one or more FRESH or JSON Resume format resumes. <TARGETS>
|
||||||
@ -10,16 +10,16 @@ COMPACT, MINIMIST, MODERN, or HELLO-WORLD) or the relative path to a custom
|
|||||||
theme. <FORMAT> should be either FRESH (for a FRESH-format resume) or JRS
|
theme. <FORMAT> should be either FRESH (for a FRESH-format resume) or JRS
|
||||||
(for a JSON Resume-format resume).
|
(for a JSON Resume-format resume).
|
||||||
|
|
||||||
fluentcv BUILD resume.json TO out/resume.all
|
hackmyresume BUILD resume.json TO out/resume.all
|
||||||
fluentcv NEW resume.json
|
hackmyresume NEW resume.json
|
||||||
fluentcv CONVERT resume.json TO resume-jrs.json
|
hackmyresume CONVERT resume.json TO resume-jrs.json
|
||||||
fluentcv VALIDATE resume.json
|
hackmyresume VALIDATE resume.json
|
||||||
|
|
||||||
Both SOURCES and TARGETS can accept multiple files:
|
Both SOURCES and TARGETS can accept multiple files:
|
||||||
|
|
||||||
fluentCV BUILD r1.json r2.json TO out/resume.all out2/resume.html
|
hackmyresume BUILD r1.json r2.json TO out/resume.all out2/resume.html
|
||||||
fluentCV NEW r1.json r2.json r3.json
|
hackmyresume NEW r1.json r2.json r3.json
|
||||||
fluentCV VALIDATE resume.json resume2.json resume3.json
|
hackmyresume VALIDATE resume.json resume2.json resume3.json
|
||||||
|
|
||||||
See https://github.com/fluentdesk/fluentCV/blob/master/README.md for more
|
See https://github.com/hacksalot/hackmyresume/blob/master/README.md for more
|
||||||
information.
|
information.
|
||||||
|
@ -11,49 +11,49 @@ Definition of the Markdown to WordProcessingML conversion routine.
|
|||||||
|
|
||||||
module.exports = function( html ) {
|
module.exports = function( html ) {
|
||||||
|
|
||||||
var final = '';
|
// Tokenize the HTML stream.
|
||||||
var is_bold = false, is_italic = false, is_link = false;
|
|
||||||
var depth = 0;
|
|
||||||
|
|
||||||
var tokens = HTML5Tokenizer.tokenize( html );
|
var tokens = HTML5Tokenizer.tokenize( html );
|
||||||
|
|
||||||
|
var final = '', is_bold, is_italic, is_link, link_url;
|
||||||
|
|
||||||
|
// Process <em>, <strong>, and <a> elements in the HTML stream, producing
|
||||||
|
// equivalent WordProcessingML that can be dumped into a <w:p> or other
|
||||||
|
// text container element.
|
||||||
_.each( tokens, function( tok ) {
|
_.each( tokens, function( tok ) {
|
||||||
|
|
||||||
switch( tok.type ) {
|
switch( tok.type ) {
|
||||||
|
|
||||||
case 'StartTag':
|
case 'StartTag':
|
||||||
switch( tok.tagName ) {
|
switch( tok.tagName ) {
|
||||||
case 'p':
|
case 'p': final += '<w:p>'; break;
|
||||||
final += '<w:p>';
|
case 'strong': is_bold = true; break;
|
||||||
break;
|
case 'em': is_italic = true; break;
|
||||||
case 'strong':
|
|
||||||
is_bold = true;
|
|
||||||
break;
|
|
||||||
case 'em':
|
|
||||||
is_italic = true;
|
|
||||||
break;
|
|
||||||
case 'a':
|
case 'a':
|
||||||
is_link = true;
|
is_link = true;
|
||||||
|
link_url = tok.attributes.filter(function(attr){
|
||||||
|
return attr[0] === 'href'; }
|
||||||
|
)[0][1];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'EndTag':
|
case 'EndTag':
|
||||||
switch( tok.tagName ) {
|
switch( tok.tagName ) {
|
||||||
case 'p':
|
case 'p': final += '</w:p>'; break;
|
||||||
final += '</w:p>';
|
case 'strong': is_bold = false; break;
|
||||||
break;
|
case 'em': is_italic = false; break;
|
||||||
case 'strong':
|
case 'a': is_link = false; break;
|
||||||
is_bold = false;
|
|
||||||
break;
|
|
||||||
case 'em':
|
|
||||||
is_italic = false;
|
|
||||||
break;
|
|
||||||
case 'a':
|
|
||||||
is_link = false;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'Chars':
|
case 'Chars':
|
||||||
var style = is_bold ? '<w:b/>' : '';
|
var style = is_bold ? '<w:b/>' : '';
|
||||||
style += is_italic ? '<w:i/>': '';
|
style += is_italic ? '<w:i/>': '';
|
||||||
final += '<w:r><w:rPr>' + style + '</w:rPr><w:t>' + tok.chars + '</w:t></w:r>';
|
style += is_link ? '<w:rStyle w:val="Hyperlink"/>' : '';
|
||||||
|
final +=
|
||||||
|
(is_link ? ('<w:hlink w:dest="' + link_url + '">') : '') +
|
||||||
|
'<w:r><w:rPr>' + style + '</w:rPr><w:t>' + tok.chars +
|
||||||
|
'</w:t></w:r>' + (is_link ? '</w:hlink>' : '');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
{
|
{
|
||||||
"basics": {
|
"basics": {
|
||||||
"name": "Jane Doe",
|
"name": "Jane Q. Fullstacker",
|
||||||
"label": "Senior Developer / Code Ninja",
|
"label": "Senior Developer",
|
||||||
"summary": "**Full-stack software developer with 6+ years industry experience** specializing in scalable cloud architectures for this, that, and the other. A native of southern CA, Jane enjoys hiking, mystery novels, and the company of Rufus, her two-year-old beagle.",
|
"summary": "**Full-stack software developer with 6+ years industry experience** specializing in scalable cloud architectures for this, that, and the other. A native of southern CA, Jane enjoys hiking, mystery novels, and the company of Rufus, her two-year-old beagle.",
|
||||||
"website": "http://jane-doe.me",
|
"website": "http://janef.me/blog",
|
||||||
"phone": "1-650-999-7777",
|
"phone": "1-650-999-7777",
|
||||||
"email": "jdoe@onecoolstartup.io",
|
"email": "jdoe@onecoolstartup.io",
|
||||||
"picture": "jane_doe.png",
|
"picture": "jane_doe.png",
|
||||||
"location": {
|
"location": {
|
||||||
"address": "Jane Doe\n123 Somewhere Rd.\nMountain View, CA 94035",
|
"address": "Jane Fullstacker\n123 Somewhere Rd.\nMountain View, CA 94035",
|
||||||
"postalCode": "94035",
|
"postalCode": "94035",
|
||||||
"city": "Mountain View",
|
"city": "Mountain View",
|
||||||
"countryCode": "US",
|
"countryCode": "US",
|
||||||
@ -17,13 +17,13 @@
|
|||||||
"profiles": [
|
"profiles": [
|
||||||
{
|
{
|
||||||
"network": "GitHub",
|
"network": "GitHub",
|
||||||
"username": "jane-doe-was-here",
|
"username": "janef-was-here",
|
||||||
"url": "https://github.com/jane-doe-was-here"
|
"url": "https://github.com/janef-was-here"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"network": "Twitter",
|
"network": "Twitter",
|
||||||
"username": "jane-doe-was-here",
|
"username": "janef-was-here",
|
||||||
"url": "https://twitter.com/jane-doe-was-here"
|
"url": "https://twitter.com/janef-was-here"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -104,17 +104,55 @@
|
|||||||
],
|
],
|
||||||
"skills": [
|
"skills": [
|
||||||
{
|
{
|
||||||
"name": "Programming",
|
"name": "Web Dev",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"C++",
|
"JavaScript",
|
||||||
"Ruby",
|
"HTML 5",
|
||||||
"Xcode"
|
"CSS",
|
||||||
|
"LAMP",
|
||||||
|
"MVC",
|
||||||
|
"REST"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Project Management",
|
"name": "JavaScript",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"Agile"
|
"Node.js",
|
||||||
|
"Angular.js",
|
||||||
|
"jQuery",
|
||||||
|
"Bootstrap",
|
||||||
|
"React.js",
|
||||||
|
"Backbone.js"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Database",
|
||||||
|
"keywords": [
|
||||||
|
"MySQL",
|
||||||
|
"PostgreSQL",
|
||||||
|
"NoSQL",
|
||||||
|
"ORM",
|
||||||
|
"Hibernate"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Cloud",
|
||||||
|
"keywords": [
|
||||||
|
"AWS",
|
||||||
|
"EC2",
|
||||||
|
"RDS",
|
||||||
|
"S3",
|
||||||
|
"Azure",
|
||||||
|
"Dropbox"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Project",
|
||||||
|
"keywords": [
|
||||||
|
"Agile",
|
||||||
|
"TFS",
|
||||||
|
"Unified Process",
|
||||||
|
"MS Project"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@ -166,10 +204,10 @@
|
|||||||
"website": "http://codeproject.com/build-ui-electron-atom.aspx"
|
"website": "http://codeproject.com/build-ui-electron-atom.aspx"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Jane Doe Unplugged",
|
"name": "Jane Fullstacker's Blog",
|
||||||
"publisher": "self",
|
"publisher": "self",
|
||||||
"releaseDate": "2011",
|
"releaseDate": "2011",
|
||||||
"website": "http://jane-doe.me"
|
"website": "http://janef.me"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Teach Yourself GORFF in 21 Days",
|
"name": "Teach Yourself GORFF in 21 Days",
|
||||||
@ -218,7 +256,8 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"language": "Spanish",
|
"language": "Spanish",
|
||||||
"level": "Moderate"
|
"level": "Moderate",
|
||||||
|
"years": 10
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
@ -7,6 +7,7 @@ var chai = require('chai')
|
|||||||
, FRESHResume = require('../src/core/fresh-resume')
|
, FRESHResume = require('../src/core/fresh-resume')
|
||||||
, CONVERTER = require('../src/core/convert')
|
, CONVERTER = require('../src/core/convert')
|
||||||
, FS = require('fs')
|
, FS = require('fs')
|
||||||
|
, MKDIRP = require('mkdirp')
|
||||||
, _ = require('underscore');
|
, _ = require('underscore');
|
||||||
|
|
||||||
chai.config.includeStack = false;
|
chai.config.includeStack = false;
|
||||||
@ -21,6 +22,7 @@ describe('FRESH/JRS converter', function () {
|
|||||||
var fileB = path.join( __dirname, 'sandbox/richard-hendriks.json' );
|
var fileB = path.join( __dirname, 'sandbox/richard-hendriks.json' );
|
||||||
|
|
||||||
_sheet = new FRESHResume().open( fileA );
|
_sheet = new FRESHResume().open( fileA );
|
||||||
|
MKDIRP.sync( path.parse(fileB).dir );
|
||||||
_sheet.saveAs( fileB, 'JRS' );
|
_sheet.saveAs( fileB, 'JRS' );
|
||||||
|
|
||||||
var rawA = FS.readFileSync( fileA, 'utf8' );
|
var rawA = FS.readFileSync( fileA, 'utf8' );
|
||||||
|
@ -16,7 +16,7 @@ describe('jane-doe.json (FRESH)', function () {
|
|||||||
it('should open without throwing an exception', function () {
|
it('should open without throwing an exception', function () {
|
||||||
function tryOpen() {
|
function tryOpen() {
|
||||||
_sheet = new FRESHResume().open(
|
_sheet = new FRESHResume().open(
|
||||||
'node_modules/FRESCA/exemplar/jane-doe.json' );
|
'node_modules/jane-q-fullstacker/resume/jane-resume.json' );
|
||||||
}
|
}
|
||||||
tryOpen.should.not.Throw();
|
tryOpen.should.not.Throw();
|
||||||
});
|
});
|
||||||
@ -43,13 +43,13 @@ describe('jane-doe.json (FRESH)', function () {
|
|||||||
|
|
||||||
it('should save without throwing an exception', function(){
|
it('should save without throwing an exception', function(){
|
||||||
function trySave() {
|
function trySave() {
|
||||||
_sheet.save( 'tests/sandbox/jane-doe.json' );
|
_sheet.save( 'tests/sandbox/jane-q-fullstacker.json' );
|
||||||
}
|
}
|
||||||
trySave.should.not.Throw();
|
trySave.should.not.Throw();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not be modified after saving', function() {
|
it('should not be modified after saving', function() {
|
||||||
var savedSheet = new FRESHResume().open('tests/sandbox/jane-doe.json');
|
var savedSheet = new FRESHResume().open('tests/sandbox/jane-q-fullstacker.json');
|
||||||
_sheet.stringify().should.equal( savedSheet.stringify() )
|
_sheet.stringify().should.equal( savedSheet.stringify() )
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ describe('jane-doe.json (JRS)', function () {
|
|||||||
it('should open without throwing an exception', function () {
|
it('should open without throwing an exception', function () {
|
||||||
function tryOpen() {
|
function tryOpen() {
|
||||||
_sheet = new JRSResume().open(
|
_sheet = new JRSResume().open(
|
||||||
path.join( __dirname, 'resumes/jrs/jane-doe.json' ) );
|
path.join( __dirname, 'resumes/jrs/jane-q-fullstacker.json' ) );
|
||||||
}
|
}
|
||||||
tryOpen.should.not.Throw();
|
tryOpen.should.not.Throw();
|
||||||
});
|
});
|
||||||
@ -39,13 +39,13 @@ describe('jane-doe.json (JRS)', function () {
|
|||||||
|
|
||||||
it('should save without throwing an exception', function(){
|
it('should save without throwing an exception', function(){
|
||||||
function trySave() {
|
function trySave() {
|
||||||
_sheet.save( 'tests/sandbox/jane-doe.json' );
|
_sheet.save( 'tests/sandbox/jane-q-fullstacker.json' );
|
||||||
}
|
}
|
||||||
trySave.should.not.Throw();
|
trySave.should.not.Throw();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not be modified after saving', function() {
|
it('should not be modified after saving', function() {
|
||||||
var savedSheet = new JRSResume().open( 'tests/sandbox/jane-doe.json' );
|
var savedSheet = new JRSResume().open( 'tests/sandbox/jane-q-fullstacker.json' );
|
||||||
_sheet.stringify().should.equal( savedSheet.stringify() )
|
_sheet.stringify().should.equal( savedSheet.stringify() )
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ var chai = require('chai')
|
|||||||
, path = require('path')
|
, path = require('path')
|
||||||
, _ = require('underscore')
|
, _ = require('underscore')
|
||||||
, FRESHResume = require('../src/core/fresh-resume')
|
, FRESHResume = require('../src/core/fresh-resume')
|
||||||
, FCMD = require( '../src/fluentcmd')
|
, FCMD = require( '../src/hackmycmd')
|
||||||
, validator = require('is-my-json-valid')
|
, validator = require('is-my-json-valid')
|
||||||
, COLORS = require('colors');
|
, COLORS = require('colors');
|
||||||
|
|
||||||
@ -29,8 +29,8 @@ describe('Testing themes', function () {
|
|||||||
function genTheme( themeName ) {
|
function genTheme( themeName ) {
|
||||||
it( themeName.toUpperCase() + ' theme should generate without throwing an exception', function () {
|
it( themeName.toUpperCase() + ' theme should generate without throwing an exception', function () {
|
||||||
function tryOpen() {
|
function tryOpen() {
|
||||||
var src = ['node_modules/FRESCA/exemplar/jane-doe.json'];
|
var src = ['node_modules/jane-q-fullstacker/resume/jane-resume.json'];
|
||||||
var dst = ['tests/sandbox/hello-world/resume.all'];
|
var dst = ['tests/sandbox/' + themeName + '/resume.all'];
|
||||||
var opts = {
|
var opts = {
|
||||||
theme: themeName,
|
theme: themeName,
|
||||||
format: 'FRESH',
|
format: 'FRESH',
|
||||||
@ -48,5 +48,6 @@ describe('Testing themes', function () {
|
|||||||
genTheme('modern');
|
genTheme('modern');
|
||||||
genTheme('minimist');
|
genTheme('minimist');
|
||||||
genTheme('awesome');
|
genTheme('awesome');
|
||||||
|
genTheme('positive');
|
||||||
|
|
||||||
});
|
});
|
||||||
|
Reference in New Issue
Block a user