mirror of
https://github.com/JuanCanham/HackMyResume.git
synced 2025-05-10 07:47:07 +01:00
Finish HackMyCore reshaping.
Reintroduce HackMyCore, dropping the interim submodule, and reorganize and improve tests.
This commit is contained in:
28
src/generators/base-generator.coffee
Normal file
28
src/generators/base-generator.coffee
Normal file
@ -0,0 +1,28 @@
|
||||
###*
|
||||
Definition of the BaseGenerator class.
|
||||
@module base-generator.js
|
||||
@license MIT. See LICENSE.md for details.
|
||||
###
|
||||
|
||||
|
||||
|
||||
# Use J. Resig's nifty class implementation
|
||||
Class = require '../utils/class'
|
||||
|
||||
|
||||
|
||||
###*
|
||||
The BaseGenerator class is the root of the generator hierarchy. Functionality
|
||||
common to ALL generators lives here.
|
||||
###
|
||||
|
||||
BaseGenerator = module.exports = Class.extend
|
||||
|
||||
###* Base-class initialize. ###
|
||||
init: ( outputFormat ) -> @format = outputFormat
|
||||
|
||||
###* Status codes. ###
|
||||
codes: require '../core/status-codes'
|
||||
|
||||
###* Generator options. ###
|
||||
opts: { }
|
30
src/generators/html-generator.coffee
Normal file
30
src/generators/html-generator.coffee
Normal file
@ -0,0 +1,30 @@
|
||||
###*
|
||||
Definition of the HTMLGenerator class.
|
||||
@license MIT. See LICENSE.md for details.
|
||||
@module html-generator.js
|
||||
###
|
||||
|
||||
|
||||
|
||||
TemplateGenerator = require './template-generator'
|
||||
FS = require 'fs-extra'
|
||||
HTML = require 'html'
|
||||
PATH = require 'path'
|
||||
require 'string.prototype.endswith'
|
||||
|
||||
|
||||
|
||||
HtmlGenerator = module.exports = TemplateGenerator.extend
|
||||
|
||||
init: -> @_super 'html'
|
||||
|
||||
###*
|
||||
Copy satellite CSS files to the destination and optionally pretty-print
|
||||
the HTML resume prior to saving.
|
||||
###
|
||||
onBeforeSave: ( info ) ->
|
||||
if info.outputFile.endsWith '.css'
|
||||
return info.mk
|
||||
if @opts.prettify
|
||||
then HTML.prettyPrint info.mk, this.opts.prettify
|
||||
else info.mk
|
91
src/generators/html-pdf-cli-generator.coffee
Normal file
91
src/generators/html-pdf-cli-generator.coffee
Normal file
@ -0,0 +1,91 @@
|
||||
###*
|
||||
Definition of the HtmlPdfCLIGenerator class.
|
||||
@module html-pdf-generator.js
|
||||
@license MIT. See LICENSE.md for details.
|
||||
###
|
||||
|
||||
|
||||
|
||||
TemplateGenerator = require('./template-generator')
|
||||
FS = require('fs-extra')
|
||||
HTML = require( 'html' )
|
||||
PATH = require('path')
|
||||
SPAWN = require('../utils/safe-spawn')
|
||||
SLASH = require('slash');
|
||||
|
||||
|
||||
|
||||
###*
|
||||
An HTML-driven PDF resume generator for HackMyResume. Talks to Phantom,
|
||||
wkhtmltopdf, and other PDF engines over a CLI (command-line interface).
|
||||
If an engine isn't installed for a particular platform, error out gracefully.
|
||||
###
|
||||
|
||||
HtmlPdfCLIGenerator = module.exports = TemplateGenerator.extend
|
||||
|
||||
|
||||
init: () -> @_super 'pdf', 'html'
|
||||
|
||||
|
||||
###* Generate the binary PDF. ###
|
||||
onBeforeSave: ( info ) ->
|
||||
try
|
||||
safe_eng = info.opts.pdf || 'wkhtmltopdf';
|
||||
if safe_eng != 'none'
|
||||
engines[ safe_eng ].call this, info.mk, info.outputFile
|
||||
return null # halt further processing
|
||||
catch ex
|
||||
# { [Error: write EPIPE] code: 'EPIPE', errno: 'EPIPE', ... }
|
||||
# { [Error: ENOENT] }
|
||||
if ex.inner && ex.inner.code == 'ENOENT'
|
||||
throw
|
||||
fluenterror: this.codes.notOnPath
|
||||
inner: ex.inner
|
||||
engine: ex.cmd,
|
||||
stack: ex.inner && ex.inner.stack
|
||||
else
|
||||
throw
|
||||
fluenterror: this.codes.pdfGeneration
|
||||
inner: ex
|
||||
stack: ex.stack
|
||||
|
||||
|
||||
|
||||
# TODO: Move each engine to a separate module
|
||||
engines =
|
||||
|
||||
|
||||
|
||||
###*
|
||||
Generate a PDF from HTML using wkhtmltopdf's CLI interface.
|
||||
Spawns a child process with `wkhtmltopdf <source> <target>`. wkhtmltopdf
|
||||
must be installed and path-accessible.
|
||||
TODO: If HTML generation has run, reuse that output
|
||||
TODO: Local web server to ease wkhtmltopdf rendering
|
||||
###
|
||||
wkhtmltopdf: (markup, fOut) ->
|
||||
# Save the markup to a temporary file
|
||||
tempFile = fOut.replace /\.pdf$/i, '.pdf.html'
|
||||
FS.writeFileSync tempFile, markup, 'utf8'
|
||||
info = SPAWN 'wkhtmltopdf', [ tempFile, fOut ]
|
||||
|
||||
|
||||
|
||||
###*
|
||||
Generate a PDF from HTML using Phantom's CLI interface.
|
||||
Spawns a child process with `phantomjs <script> <source> <target>`. Phantom
|
||||
must be installed and path-accessible.
|
||||
TODO: If HTML generation has run, reuse that output
|
||||
TODO: Local web server to ease Phantom rendering
|
||||
###
|
||||
|
||||
phantom: ( markup, fOut ) ->
|
||||
|
||||
# Save the markup to a temporary file
|
||||
tempFile = fOut.replace(/\.pdf$/i, '.pdf.html');
|
||||
FS.writeFileSync tempFile, markup, 'utf8'
|
||||
scriptPath = SLASH( PATH.relative( process.cwd(),
|
||||
PATH.resolve( __dirname, '../utils/rasterize.js' ) ) );
|
||||
sourcePath = SLASH( PATH.relative( process.cwd(), tempFile) );
|
||||
destPath = SLASH( PATH.relative( process.cwd(), fOut) );
|
||||
info = SPAWN('phantomjs', [ scriptPath, sourcePath, destPath ]);
|
52
src/generators/html-png-generator.coffee
Normal file
52
src/generators/html-png-generator.coffee
Normal file
@ -0,0 +1,52 @@
|
||||
###*
|
||||
Definition of the HtmlPngGenerator class.
|
||||
@license MIT. See LICENSE.MD for details.
|
||||
@module html-png-generator.js
|
||||
###
|
||||
|
||||
|
||||
|
||||
TemplateGenerator = require './template-generator'
|
||||
FS = require 'fs-extra'
|
||||
HTML = require 'html'
|
||||
SLASH = require 'slash'
|
||||
SPAWN = require '../utils/safe-spawn'
|
||||
PATH = require 'path'
|
||||
|
||||
|
||||
###*
|
||||
An HTML-based PNG resume generator for HackMyResume.
|
||||
###
|
||||
HtmlPngGenerator = module.exports = TemplateGenerator.extend
|
||||
|
||||
init: -> @_super 'png', 'html'
|
||||
|
||||
invoke: ( rez, themeMarkup, cssInfo, opts ) ->
|
||||
# TODO: Not currently called or callable.
|
||||
|
||||
generate: ( rez, f, opts ) ->
|
||||
htmlResults = opts.targets.filter (t) -> t.fmt.outFormat == 'html'
|
||||
htmlFile = htmlResults[0].final.files.filter (fl) ->
|
||||
fl.info.ext == 'html'
|
||||
phantom htmlFile[0].data, f
|
||||
return
|
||||
|
||||
###*
|
||||
Generate a PDF from HTML using Phantom's CLI interface.
|
||||
Spawns a child process with `phantomjs <script> <source> <target>`. Phantom
|
||||
must be installed and path-accessible.
|
||||
TODO: If HTML generation has run, reuse that output
|
||||
TODO: Local web server to ease Phantom rendering
|
||||
###
|
||||
|
||||
phantom = ( markup, fOut ) ->
|
||||
|
||||
# Save the markup to a temporary file
|
||||
tempFile = fOut.replace(/\.png$/i, '.png.html');
|
||||
FS.writeFileSync tempFile, markup, 'utf8'
|
||||
scriptPath = SLASH( PATH.relative( process.cwd(),
|
||||
PATH.resolve( __dirname, '../utils/rasterize.js' ) ) );
|
||||
sourcePath = SLASH( PATH.relative( process.cwd(), tempFile) );
|
||||
destPath = SLASH( PATH.relative( process.cwd(), fOut) );
|
||||
info = SPAWN('phantomjs', [ scriptPath, sourcePath, destPath ]);
|
||||
return
|
35
src/generators/json-generator.coffee
Normal file
35
src/generators/json-generator.coffee
Normal file
@ -0,0 +1,35 @@
|
||||
###*
|
||||
Definition of the JsonGenerator class.
|
||||
@license MIT. See LICENSE.md for details.
|
||||
@module generators/json-generator
|
||||
###
|
||||
|
||||
BaseGenerator = require './base-generator'
|
||||
FS = require 'fs'
|
||||
_ = require 'underscore'
|
||||
|
||||
###*
|
||||
The JsonGenerator generates a JSON resume directly.
|
||||
###
|
||||
|
||||
JsonGenerator = module.exports = BaseGenerator.extend
|
||||
|
||||
init: () -> @_super 'json'
|
||||
|
||||
keys: ['imp', 'warnings', 'computed', 'filt', 'ctrl', 'index',
|
||||
'safeStartDate', 'safeEndDate', 'safeDate', 'safeReleaseDate', 'result',
|
||||
'isModified', 'htmlPreview', 'safe' ]
|
||||
|
||||
invoke: ( rez ) ->
|
||||
|
||||
# TODO: merge with FCVD
|
||||
replacer = ( key,value ) -> # Exclude these keys from stringification
|
||||
if (_.some @keys, (val) -> key.trim() == val)
|
||||
return undefined
|
||||
else
|
||||
value
|
||||
JSON.stringify rez, replacer, 2
|
||||
|
||||
generate: ( rez, f ) ->
|
||||
FS.writeFileSync( f, this.invoke(rez), 'utf8' )
|
||||
return
|
30
src/generators/json-yaml-generator.coffee
Normal file
30
src/generators/json-yaml-generator.coffee
Normal file
@ -0,0 +1,30 @@
|
||||
###*
|
||||
Definition of the JsonYamlGenerator class.
|
||||
@module json-yaml-generator.js
|
||||
@license MIT. See LICENSE.md for details.
|
||||
###
|
||||
|
||||
|
||||
|
||||
BaseGenerator = require('./base-generator')
|
||||
FS = require('fs')
|
||||
YAML = require('yamljs')
|
||||
|
||||
|
||||
|
||||
###*
|
||||
JsonYamlGenerator takes a JSON resume object and translates it directly to
|
||||
JSON without a template, producing an equivalent YAML-formatted resume. See
|
||||
also YamlGenerator (yaml-generator.js).
|
||||
###
|
||||
|
||||
JsonYamlGenerator = module.exports = BaseGenerator.extend
|
||||
|
||||
init: () -> @_super 'yml'
|
||||
|
||||
invoke: ( rez, themeMarkup, cssInfo, opts ) ->
|
||||
YAML.stringify JSON.parse( rez.stringify() ), Infinity, 2
|
||||
|
||||
generate: ( rez, f, opts ) ->
|
||||
data = YAML.stringify JSON.parse( rez.stringify() ), Infinity, 2
|
||||
FS.writeFileSync f, data, 'utf8'
|
14
src/generators/latex-generator.coffee
Normal file
14
src/generators/latex-generator.coffee
Normal file
@ -0,0 +1,14 @@
|
||||
###*
|
||||
Definition of the LaTeXGenerator class.
|
||||
@license MIT. See LICENSE.md for details.
|
||||
@module generators/latex-generator
|
||||
###
|
||||
|
||||
TemplateGenerator = require './template-generator'
|
||||
|
||||
###*
|
||||
LaTeXGenerator generates a LaTeX resume via TemplateGenerator.
|
||||
###
|
||||
LaTeXGenerator = module.exports = TemplateGenerator.extend
|
||||
|
||||
init: () -> @_super 'latex', 'tex'
|
14
src/generators/markdown-generator.coffee
Normal file
14
src/generators/markdown-generator.coffee
Normal file
@ -0,0 +1,14 @@
|
||||
###*
|
||||
Definition of the MarkdownGenerator class.
|
||||
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
|
||||
@module markdown-generator.js
|
||||
###
|
||||
|
||||
TemplateGenerator = require './template-generator'
|
||||
|
||||
###*
|
||||
MarkdownGenerator generates a Markdown-formatted resume via TemplateGenerator.
|
||||
###
|
||||
MarkdownGenerator = module.exports = TemplateGenerator.extend
|
||||
|
||||
init: () -> @_super 'md', 'txt'
|
204
src/generators/template-generator.coffee
Normal file
204
src/generators/template-generator.coffee
Normal file
@ -0,0 +1,204 @@
|
||||
###*
|
||||
Definition of the TemplateGenerator class. TODO: Refactor
|
||||
@license MIT. See LICENSE.md for details.
|
||||
@module template-generator.js
|
||||
###
|
||||
|
||||
|
||||
|
||||
FS = require 'fs-extra'
|
||||
_ = require 'underscore'
|
||||
MD = require 'marked'
|
||||
XML = require 'xml-escape'
|
||||
PATH = require 'path'
|
||||
parsePath = require 'parse-filepath'
|
||||
MKDIRP = require 'mkdirp'
|
||||
BaseGenerator = require './base-generator'
|
||||
EXTEND = require 'extend'
|
||||
FRESHTheme = require '../core/fresh-theme'
|
||||
JRSTheme = require '../core/jrs-theme'
|
||||
|
||||
|
||||
|
||||
###*
|
||||
TemplateGenerator performs resume generation via local Handlebar or Underscore
|
||||
style template expansion and is appropriate for text-based formats like HTML,
|
||||
plain text, and XML versions of Microsoft Word, Excel, and OpenOffice.
|
||||
@class TemplateGenerator
|
||||
###
|
||||
|
||||
TemplateGenerator = module.exports = BaseGenerator.extend
|
||||
|
||||
###* Constructor. Set the output format and template format for this
|
||||
generator. Will usually be called by a derived generator such as
|
||||
HTMLGenerator or MarkdownGenerator. ###
|
||||
|
||||
init: ( outputFormat, templateFormat, cssFile ) ->
|
||||
@_super outputFormat
|
||||
@tplFormat = templateFormat || outputFormat
|
||||
return
|
||||
|
||||
|
||||
|
||||
###* Generate a resume using string-based inputs and outputs without touching
|
||||
the filesystem.
|
||||
@method invoke
|
||||
@param rez A FreshResume object.
|
||||
@param opts Generator options.
|
||||
@returns {Array} An array of objects representing the generated output
|
||||
files. ###
|
||||
|
||||
invoke: ( rez, opts ) ->
|
||||
|
||||
opts =
|
||||
if opts
|
||||
then (this.opts = EXTEND( true, { }, _defaultOpts, opts ))
|
||||
else this.opts
|
||||
|
||||
# Sort such that CSS files are processed before others
|
||||
curFmt = opts.themeObj.getFormat( this.format )
|
||||
curFmt.files = _.sortBy curFmt.files, (fi) -> fi.ext != 'css'
|
||||
|
||||
# Run the transformation!
|
||||
results = curFmt.files.map( ( tplInfo, idx ) ->
|
||||
trx = @.single rez, tplInfo.data, this.format, opts, opts.themeObj, curFmt
|
||||
if tplInfo.ext == 'css'
|
||||
curFmt.files[idx].data = trx
|
||||
else tplInfo.ext == 'html'
|
||||
#tplInfo.css contains the CSS data loaded by theme
|
||||
#tplInfo.cssPath contains the absolute path to the source CSS File
|
||||
return info: tplInfo, data: trx
|
||||
, @)
|
||||
|
||||
files: results
|
||||
|
||||
|
||||
###* Generate a resume using file-based inputs and outputs. Requires access
|
||||
to the local filesystem.
|
||||
@method generate
|
||||
@param rez A FreshResume object.
|
||||
@param f Full path to the output resume file to generate.
|
||||
@param opts Generator options. ###
|
||||
|
||||
generate: ( rez, f, opts ) ->
|
||||
|
||||
# Prepare
|
||||
this.opts = EXTEND( true, { }, _defaultOpts, opts );
|
||||
|
||||
# Call the string-based generation method to perform the generation.
|
||||
genInfo = this.invoke( rez, null )
|
||||
outFolder = parsePath( f ).dirname
|
||||
curFmt = opts.themeObj.getFormat( this.format )
|
||||
|
||||
# Process individual files within this format. For example, the HTML
|
||||
# output format for a theme may have multiple HTML files, CSS files,
|
||||
# etc. Process them here.
|
||||
genInfo.files.forEach(( file ) ->
|
||||
|
||||
# Pre-processing
|
||||
file.info.orgPath = file.info.orgPath || '' # <-- For JRS themes
|
||||
thisFilePath = PATH.join( outFolder, file.info.orgPath )
|
||||
if this.onBeforeSave
|
||||
file.data = this.onBeforeSave
|
||||
theme: opts.themeObj
|
||||
outputFile: if file.info.major then f else thisFilePath
|
||||
mk: file.data
|
||||
opts: this.opts
|
||||
if !file.data
|
||||
return # PDF etc
|
||||
|
||||
# Write the file
|
||||
fileName = if file.info.major then f else thisFilePath
|
||||
MKDIRP.sync PATH.dirname( fileName )
|
||||
FS.writeFileSync fileName, file.data, { encoding: 'utf8', flags: 'w' }
|
||||
|
||||
# Post-processing
|
||||
if @onAfterSave
|
||||
@onAfterSave( outputFile: fileName, mk: file.data, opts: this.opts )
|
||||
|
||||
, @)
|
||||
|
||||
# Some themes require a symlink structure. If so, create it.
|
||||
if curFmt.symLinks
|
||||
Object.keys( curFmt.symLinks ).forEach (loc) ->
|
||||
absLoc = PATH.join outFolder, loc
|
||||
absTarg = PATH.join PATH.dirname(absLoc), curFmt.symLinks[loc]
|
||||
# 'file', 'dir', or 'junction' (Windows only)
|
||||
type = parsePath( absLoc ).extname ? 'file' : 'junction'
|
||||
FS.symlinkSync absTarg, absLoc, type
|
||||
|
||||
genInfo
|
||||
|
||||
|
||||
###* Perform a single resume resume transformation using string-based inputs
|
||||
and outputs without touching the local file system.
|
||||
@param json A FRESH or JRS resume object.
|
||||
@param jst The stringified template data
|
||||
@param format The format name, such as "html" or "latex"
|
||||
@param cssInfo Needs to be refactored.
|
||||
@param opts Options and passthrough data. ###
|
||||
|
||||
single: ( json, jst, format, opts, theme, curFmt ) ->
|
||||
if this.opts.freezeBreaks
|
||||
jst = freeze jst
|
||||
eng = require '../renderers/' + theme.engine + '-generator'
|
||||
result = eng.generate json, jst, format, curFmt, opts, theme
|
||||
if this.opts.freezeBreaks
|
||||
result = unfreeze result
|
||||
result
|
||||
|
||||
|
||||
|
||||
###* Export the TemplateGenerator function/ctor. ###
|
||||
module.exports = TemplateGenerator
|
||||
|
||||
|
||||
|
||||
###* Freeze newlines for protection against errant JST parsers. ###
|
||||
freeze = ( markup ) ->
|
||||
markup.replace( _reg.regN, _defaultOpts.nSym )
|
||||
markup.replace( _reg.regR, _defaultOpts.rSym )
|
||||
|
||||
|
||||
|
||||
###* Unfreeze newlines when the coast is clear. ###
|
||||
unfreeze = ( markup ) ->
|
||||
markup.replace _reg.regSymR, '\r'
|
||||
markup.replace _reg.regSymN, '\n'
|
||||
|
||||
|
||||
###* Default template generator options. ###
|
||||
_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: ( txt ) -> txt
|
||||
raw: ( txt ) -> txt
|
||||
xml: ( txt ) -> XML(txt)
|
||||
md: ( txt ) -> MD( txt || '' )
|
||||
mdin: ( txt ) -> MD(txt || '' ).replace(/^\s*<p>|<\/p>\s*$/gi, '')
|
||||
lower: ( txt ) -> txt.toLowerCase()
|
||||
link: ( name, url ) ->
|
||||
return if url then '<a href="' + url + '">' + name + '</a>' else name
|
||||
prettify: # ← See https://github.com/beautify-web/js-beautify#options
|
||||
indent_size: 2
|
||||
unformatted: ['em','strong','a']
|
||||
max_char: 80 # ← See lib/html.js in above-linked repo
|
||||
#wrap_line_length: 120, <-- Don't use this
|
||||
|
||||
|
||||
|
||||
###* Regexes for linebreak preservation. ###
|
||||
_reg =
|
||||
regN: new RegExp( '\n', 'g' )
|
||||
regR: new RegExp( '\r', 'g' )
|
||||
regSymN: new RegExp( _defaultOpts.nSym, 'g' )
|
||||
regSymR: new RegExp( _defaultOpts.rSym, 'g' )
|
14
src/generators/text-generator.coffee
Normal file
14
src/generators/text-generator.coffee
Normal file
@ -0,0 +1,14 @@
|
||||
###*
|
||||
Definition of the TextGenerator class.
|
||||
@license MIT. See LICENSE.md for details.
|
||||
@module text-generator.js
|
||||
###
|
||||
|
||||
TemplateGenerator = require './template-generator'
|
||||
|
||||
###*
|
||||
The TextGenerator generates a plain-text resume via the TemplateGenerator.
|
||||
###
|
||||
TextGenerator = module.exports = TemplateGenerator.extend
|
||||
|
||||
init: () -> @_super 'txt'
|
11
src/generators/word-generator.coffee
Normal file
11
src/generators/word-generator.coffee
Normal file
@ -0,0 +1,11 @@
|
||||
###
|
||||
Definition of the WordGenerator class.
|
||||
@license MIT. See LICENSE.md for details.
|
||||
@module generators/word-generator
|
||||
###
|
||||
|
||||
|
||||
TemplateGenerator = require './template-generator'
|
||||
|
||||
WordGenerator = module.exports = TemplateGenerator.extend
|
||||
init: () -> @_super 'doc', 'xml'
|
13
src/generators/xml-generator.coffee
Normal file
13
src/generators/xml-generator.coffee
Normal file
@ -0,0 +1,13 @@
|
||||
###*
|
||||
Definition of the XMLGenerator class.
|
||||
@license MIT. See LICENSE.md for details.
|
||||
@module generatprs/xml-generator
|
||||
###
|
||||
|
||||
BaseGenerator = require './base-generator'
|
||||
|
||||
###*
|
||||
The XmlGenerator generates an XML resume via the TemplateGenerator.
|
||||
###
|
||||
XMLGenerator = module.exports = BaseGenerator.extend
|
||||
init: () -> @_super 'xml'
|
15
src/generators/yaml-generator.coffee
Normal file
15
src/generators/yaml-generator.coffee
Normal file
@ -0,0 +1,15 @@
|
||||
###*
|
||||
Definition of the YAMLGenerator class.
|
||||
@module yaml-generator.js
|
||||
@license MIT. See LICENSE.md for details.
|
||||
###
|
||||
|
||||
|
||||
TemplateGenerator = require './template-generator'
|
||||
|
||||
###*
|
||||
YamlGenerator generates a YAML-formatted resume via TemplateGenerator.
|
||||
###
|
||||
|
||||
YAMLGenerator = module.exports = TemplateGenerator.extend
|
||||
init: () -> @_super 'yml', 'yml'
|
Reference in New Issue
Block a user