1
0
mirror of https://github.com/JuanCanham/HackMyResume.git synced 2024-11-25 01:40:10 +00:00

Refactor generation.

Merge implicit and explicit generation paths, start emitting file
transform & copy signals, fix various bugs, introduce new bugs, support
better --debug outputs in the future.
This commit is contained in:
hacksalot 2016-02-09 15:27:34 -05:00
parent 1bc4263a46
commit aaa5e1fc1f
11 changed files with 336 additions and 282 deletions

View File

@ -33,7 +33,10 @@ Event code definitions.
beforeInlineConvert: 22, beforeInlineConvert: 22,
afterInlineConvert: 23, afterInlineConvert: 23,
beforeValidate: 24, beforeValidate: 24,
afterValidate: 25 afterValidate: 25,
beforeWrite: 26,
afterWrite: 27,
applyTheme: 28
}; };
}).call(this); }).call(this);

View File

@ -6,7 +6,7 @@ Definition of the FRESHTheme class.
*/ */
(function() { (function() {
var EXTEND, FRESHTheme, FS, HMSTATUS, PATH, READFILES, _, friendlyName, loadExplicit, loadImplicit, loadSafeJson, moment, parsePath, pathExists, validator; var EXTEND, FRESHTheme, FS, HMSTATUS, PATH, READFILES, _, _load, friendlyName, loadSafeJson, moment, parsePath, pathExists, validator;
FS = require('fs'); FS = require('fs');
@ -71,12 +71,7 @@ Definition of the FRESHTheme class.
return formatsHash[key] = cached[th].getFormat(key); return formatsHash[key] = cached[th].getFormat(key);
}); });
} }
if (!!this.formats) { formatsHash = _load.call(this, formatsHash);
formatsHash = loadExplicit.call(this, formatsHash);
this.explicit = true;
} else {
formatsHash = loadImplicit.call(this, formatsHash);
}
this.formats = formatsHash; this.formats = formatsHash;
this.name = parsePath(this.folder).name; this.name = parsePath(this.folder).name;
return this; return this;
@ -105,33 +100,51 @@ Definition of the FRESHTheme class.
Refactor duplicated code with loadExplicit. Refactor duplicated code with loadExplicit.
*/ */
loadImplicit = function(formatsHash) { _load = function(formatsHash) {
var fmts, major, that, tplFolder; var copyOnly, fmts, major, that, tplFolder;
that = this; that = this;
major = false; major = false;
tplFolder = PATH.join(this.folder, 'src'); tplFolder = PATH.join(this.folder, 'src');
copyOnly = ['.ttf', '.otf', '.png', '.jpg', '.jpeg', '.pdf'];
fmts = READFILES(tplFolder).map(function(absPath) { fmts = READFILES(tplFolder).map(function(absPath) {
var idx, isMajor, obj, outFmt, pathInfo, portion, reg, res; var absPathSafe, act, idx, isMajor, obj, outFmt, pathInfo, portion, ref, ref1, reg, res;
pathInfo = parsePath(absPath); pathInfo = parsePath(absPath);
absPathSafe = absPath.trim().toLowerCase();
outFmt = ''; outFmt = '';
isMajor = false; isMajor = false;
portion = pathInfo.dirname.replace(tplFolder, ''); if (that.formats) {
if (portion && portion.trim()) { outFmt = _.find(Object.keys(that.formats), function(fmtKey) {
if (portion[1] === '_') { var fmtVal;
return; fmtVal = that.formats[fmtKey];
return _.some(fmtVal.transform, function(fpath) {
var absPathB;
absPathB = PATH.join(that.folder, fpath).trim().toLowerCase();
return absPathB === absPathSafe;
});
});
if (outFmt) {
isMajor = true;
} }
reg = /^(?:\/|\\)(html|latex|doc|pdf|png|partials)(?:\/|\\)?/ig; }
res = reg.exec(portion); if (!outFmt) {
if (res) { portion = pathInfo.dirname.replace(tplFolder, '');
if (res[1] !== 'partials') { if (portion && portion.trim()) {
outFmt = res[1]; if (portion[1] === '_') {
} else { return;
that.partials = that.partials || []; }
that.partials.push({ reg = /^(?:\/|\\)(html|latex|doc|pdf|png|partials)(?:\/|\\)?/ig;
name: pathInfo.name, res = reg.exec(portion);
path: absPath if (res) {
}); if (res[1] !== 'partials') {
return null; outFmt = res[1];
} else {
that.partials = that.partials || [];
that.partials.push({
name: pathInfo.name,
path: absPath
});
return null;
}
} }
} }
} }
@ -140,12 +153,16 @@ Definition of the FRESHTheme class.
outFmt = idx === -1 ? pathInfo.name : pathInfo.name.substr(idx + 1); outFmt = idx === -1 ? pathInfo.name : pathInfo.name.substr(idx + 1);
isMajor = true; isMajor = true;
} }
act = _.contains(copyOnly, pathInfo.extname) ? 'copy' : 'transform';
formatsHash[outFmt] = formatsHash[outFmt] || { formatsHash[outFmt] = formatsHash[outFmt] || {
outFormat: outFmt, outFormat: outFmt,
files: [] files: []
}; };
if ((ref = that.formats) != null ? (ref1 = ref[outFmt]) != null ? ref1.symLinks : void 0 : void 0) {
formatsHash[outFmt].symLinks = that.formats[outFmt].symLinks;
}
obj = { obj = {
action: 'transform', action: act,
path: absPath, path: absPath,
major: isMajor, major: isMajor,
orgPath: PATH.relative(tplFolder, absPath), orgPath: PATH.relative(tplFolder, absPath),
@ -183,81 +200,6 @@ Definition of the FRESHTheme class.
}; };
/*
Load the theme explicitly, by following the 'formats' hash
in the theme's JSON settings file.
*/
loadExplicit = function(formatsHash) {
var act, fmts, that, tplFolder;
tplFolder = PATH.join(this.folder, 'src');
act = null;
that = this;
fmts = READFILES(tplFolder).map(function(absPath) {
var absPathSafe, idx, obj, outFmt, pathInfo, portion, reg, res;
act = null;
pathInfo = parsePath(absPath);
absPathSafe = absPath.trim().toLowerCase();
outFmt = _.find(Object.keys(that.formats), function(fmtKey) {
var fmtVal;
fmtVal = that.formats[fmtKey];
return _.some(fmtVal.transform, function(fpath) {
var absPathB;
absPathB = PATH.join(that.folder, fpath).trim().toLowerCase();
return absPathB === absPathSafe;
});
});
if (outFmt) {
act = 'transform';
}
if (!outFmt) {
portion = pathInfo.dirname.replace(tplFolder, '');
if (portion && portion.trim()) {
reg = /^(?:\/|\\)(html|latex|doc|pdf)(?:\/|\\)?/ig;
res = reg.exec(portion);
res && (outFmt = res[1]);
}
}
if (!outFmt) {
idx = pathInfo.name.lastIndexOf('-');
outFmt = idx === -1 ? pathInfo.name : pathInfo.name.substr(idx + 1);
}
formatsHash[outFmt] = formatsHash[outFmt] || {
outFormat: outFmt,
files: [],
symLinks: that.formats[outFmt].symLinks
};
obj = {
action: act,
orgPath: PATH.relative(that.folder, absPath),
path: absPath,
ext: pathInfo.extname.slice(1),
title: friendlyName(outFmt),
pre: outFmt,
data: FS.readFileSync(absPath, 'utf8'),
css: null
};
formatsHash[outFmt].files.push(obj);
return obj;
});
this.cssFiles = fmts.filter(function(fmt) {
return fmt.ext === 'css';
});
this.cssFiles.forEach(function(cssf) {
var idx;
idx = _.findIndex(fmts, function(fmt) {
return fmt.pre === cssf.pre && fmt.ext === 'html';
});
fmts[idx].css = cssf.data;
return fmts[idx].cssPath = cssf.path;
});
fmts = fmts.filter(function(fmt) {
return fmt.ext !== 'css';
});
return formatsHash;
};
/* /*
Return a more friendly name for certain formats. Return a more friendly name for certain formats.
TODO: Refactor TODO: Refactor

View File

@ -6,7 +6,7 @@ Definition of the TemplateGenerator class. TODO: Refactor
*/ */
(function() { (function() {
var BaseGenerator, EXTEND, FRESHTheme, FS, JRSTheme, MD, MKDIRP, PATH, TemplateGenerator, XML, _, _defaultOpts, _reg, freeze, parsePath, unfreeze, var BaseGenerator, EXTEND, FRESHTheme, FS, JRSTheme, MD, MKDIRP, PATH, TemplateGenerator, XML, _, _defaultOpts, _reg, createSymLinks, freeze, parsePath, unfreeze,
extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
hasProp = {}.hasOwnProperty; hasProp = {}.hasOwnProperty;
@ -74,11 +74,18 @@ Definition of the TemplateGenerator class. TODO: Refactor
}); });
results = curFmt.files.map(function(tplInfo, idx) { results = curFmt.files.map(function(tplInfo, idx) {
var trx; var trx;
trx = this.single(rez, tplInfo.data, this.format, opts, opts.themeObj, curFmt); if (tplInfo.action === 'transform') {
if (tplInfo.ext === 'css') { trx = this.transform(rez, tplInfo.data, this.format, opts, opts.themeObj, curFmt);
curFmt.files[idx].data = trx; if (tplInfo.ext === 'css') {
curFmt.files[idx].data = trx;
} else {
tplInfo.ext === 'html';
}
} else { } else {
tplInfo.ext === 'html';
}
if (typeof opts.onTransform === "function") {
opts.onTransform(tplInfo);
} }
return { return {
info: tplInfo, info: tplInfo,
@ -106,13 +113,13 @@ Definition of the TemplateGenerator class. TODO: Refactor
outFolder = parsePath(f).dirname; outFolder = parsePath(f).dirname;
curFmt = opts.themeObj.getFormat(this.format); curFmt = opts.themeObj.getFormat(this.format);
genInfo.files.forEach(function(file) { genInfo.files.forEach(function(file) {
var fileName, thisFilePath; var thisFilePath;
file.info.orgPath = file.info.orgPath || ''; file.info.orgPath = file.info.orgPath || '';
thisFilePath = PATH.join(outFolder, file.info.orgPath); thisFilePath = PATH.join(outFolder, file.info.orgPath);
if (this.onBeforeSave) { if (file.info.action !== 'copy' && this.onBeforeSave) {
file.data = this.onBeforeSave({ file.data = this.onBeforeSave({
theme: opts.themeObj, theme: opts.themeObj,
outputFile: file.info.major ? f : thisFilePath, outputFile: thisFilePath,
mk: file.data, mk: file.data,
opts: this.opts opts: this.opts
}); });
@ -120,12 +127,22 @@ Definition of the TemplateGenerator class. TODO: Refactor
return; return;
} }
} }
fileName = file.info.major ? f : thisFilePath; if (typeof opts.beforeWrite === "function") {
MKDIRP.sync(PATH.dirname(fileName)); opts.beforeWrite(thisFilePath);
FS.writeFileSync(fileName, file.data, { }
encoding: 'utf8', MKDIRP.sync(PATH.dirname(thisFilePath));
flags: 'w' console.log(file.info.path);
}); if (file.info.action !== 'copy') {
FS.writeFileSync(thisFilePath, file.data, {
encoding: 'utf8',
flags: 'w'
});
} else {
FS.copySync(file.info.path, thisFilePath);
}
if (typeof opts.afterWrite === "function") {
opts.afterWrite(thisFilePath);
}
if (this.onAfterSave) { if (this.onAfterSave) {
return this.onAfterSave({ return this.onAfterSave({
outputFile: fileName, outputFile: fileName,
@ -134,17 +151,7 @@ Definition of the TemplateGenerator class. TODO: Refactor
}); });
} }
}, this); }, this);
if (curFmt.symLinks) { createSymLinks(curFmt, outFolder);
Object.keys(curFmt.symLinks).forEach(function(loc) {
var absLoc, absTarg, ref, type;
absLoc = PATH.join(outFolder, loc);
absTarg = PATH.join(PATH.dirname(absLoc), curFmt.symLinks[loc]);
type = (ref = parsePath(absLoc).extname) != null ? ref : {
'file': 'junction'
};
return FS.symlinkSync(absTarg, absLoc, type);
});
}
return genInfo; return genInfo;
}; };
@ -158,7 +165,7 @@ Definition of the TemplateGenerator class. TODO: Refactor
@param opts Options and passthrough data. @param opts Options and passthrough data.
*/ */
TemplateGenerator.prototype.single = function(json, jst, format, opts, theme, curFmt) { TemplateGenerator.prototype.transform = function(json, jst, format, opts, theme, curFmt) {
var eng, result; var eng, result;
if (this.opts.freezeBreaks) { if (this.opts.freezeBreaks) {
jst = freeze(jst); jst = freeze(jst);
@ -175,6 +182,32 @@ Definition of the TemplateGenerator class. TODO: Refactor
})(BaseGenerator); })(BaseGenerator);
createSymLinks = function(curFmt, outFolder) {
if (curFmt.symLinks) {
Object.keys(curFmt.symLinks).forEach(function(loc) {
var absLoc, absTarg, succeeded, type;
absLoc = PATH.join(outFolder, loc);
absTarg = PATH.join(PATH.dirname(absLoc), curFmt.symLinks[loc]);
type = parsePath(absLoc).extname ? 'file' : 'junction';
try {
return FS.symlinkSync(absTarg, absLoc, type);
} catch (_error) {
succeeded = false;
if (_error.code === 'EEXIST') {
FS.unlinkSync(absLoc);
try {
FS.symlinkSync(absTarg, absLoc, type);
succeeded = true;
} catch (_error) {}
}
if (!succeeded) {
throw ex;
}
}
});
}
};
/** Freeze newlines for protection against errant JST parsers. */ /** Freeze newlines for protection against errant JST parsers. */

View File

@ -1,7 +1,7 @@
/** /**
Template helper definitions for Underscore. Template helper definitions for Underscore.
@license MIT. Copyright (c) 2016 hacksalot (https://github.com/hacksalot) @license MIT. See LICENSE.md for details.
@module handlebars-helpers.js @module handlebars-helpers.js
*/ */
@ -26,7 +26,7 @@ Template helper definitions for Underscore.
helpers.cssInfo = cssInfo; helpers.cssInfo = cssInfo;
helpers.engine = eng; helpers.engine = eng;
ctx.h = helpers; ctx.h = helpers;
return _.each(helpers, function(hVal, hKey) { _.each(helpers, function(hVal, hKey) {
if (_.isFunction(hVal)) { if (_.isFunction(hVal)) {
return _.bind(hVal, ctx); return _.bind(hVal, ctx);
} }

View File

@ -6,14 +6,12 @@ Definition of the UnderscoreGenerator class.
*/ */
(function() { (function() {
var HMSTATUS, UnderscoreGenerator, _, registerHelpers; var UnderscoreGenerator, _, registerHelpers;
_ = require('underscore'); _ = require('underscore');
registerHelpers = require('../helpers/underscore-helpers'); registerHelpers = require('../helpers/underscore-helpers');
HMSTATUS = require('../core/status-codes');
/** /**
Perform template-based resume generation using Underscore.js. Perform template-based resume generation using Underscore.js.
@ -22,13 +20,14 @@ Definition of the UnderscoreGenerator class.
UnderscoreGenerator = module.exports = { UnderscoreGenerator = module.exports = {
generateSimple: function(data, tpl) { generateSimple: function(data, tpl) {
var template; var HMS, t;
try { try {
template = _.template(tpl); t = _.template(tpl);
return template(data); return t(data);
} catch (_error) { } catch (_error) {
HMS = require('../core/status-codes');
throw { throw {
fluenterror: template ? HMSTATUS.invokeTemplate : HMSTATUS.compileTemplate, fluenterror: HMS[t ? 'invokeTemplate' : 'compileTemplate'],
inner: _error inner: _error
}; };
} }

13
dist/verbs/build.js vendored
View File

@ -103,7 +103,7 @@ Implementation of the 'build' verb for HackMyResume.
}); });
return null; return null;
} }
_prep(src, dst, opts); _prep.call(this, src, dst, opts);
sheetObjects = ResumeFactory.load(src, { sheetObjects = ResumeFactory.load(src, {
format: null, format: null,
objectify: false, objectify: false,
@ -230,6 +230,7 @@ Implementation of the 'build' verb for HackMyResume.
*/ */
_prep = function(src, dst, opts) { _prep = function(src, dst, opts) {
var that;
_opts.theme = (opts.theme && opts.theme.toLowerCase().trim()) || 'modern'; _opts.theme = (opts.theme && opts.theme.toLowerCase().trim()) || 'modern';
_opts.prettify = opts.prettify === true; _opts.prettify = opts.prettify === true;
_opts.css = opts.css; _opts.css = opts.css;
@ -241,6 +242,16 @@ Implementation of the 'build' verb for HackMyResume.
_opts.noTips = opts.noTips; _opts.noTips = opts.noTips;
_opts.debug = opts.debug; _opts.debug = opts.debug;
_opts.sort = opts.sort; _opts.sort = opts.sort;
that = this;
_opts.onTransform = function(info) {
that.stat(HMEVENT.afterTransform, info);
};
_opts.beforeWrite = function(info) {
that.stat(HMEVENT.beforeWrite, info);
};
_opts.afterWrite = function(info) {
that.stat(HMEVENT.afterWrite, info);
};
(src.length > 1 && (!dst || !dst.length)) && dst.push(src.pop()); (src.length > 1 && (!dst || !dst.length)) && dst.push(src.pop());
}; };

View File

@ -33,3 +33,6 @@ module.exports =
afterInlineConvert: 23 afterInlineConvert: 23
beforeValidate: 24 beforeValidate: 24
afterValidate: 25 afterValidate: 25
beforeWrite: 26
afterWrite: 27
applyTheme: 28

View File

@ -69,13 +69,8 @@ class FRESHTheme
cached[ th ] = cached[th] || new FRESHTheme().open( themePath ) cached[ th ] = cached[th] || new FRESHTheme().open( themePath )
formatsHash[ key ] = cached[ th ].getFormat( key ) formatsHash[ key ] = cached[ th ].getFormat( key )
# Check for an explicit "formats" entry in the theme JSON. If it has one, # Load theme files
# then this theme declares its files explicitly. formatsHash = _load.call @, formatsHash
if !!@formats
formatsHash = loadExplicit.call this, formatsHash
@explicit = true;
else
formatsHash = loadImplicit.call this, formatsHash
# Cache # Cache
@formats = formatsHash @formats = formatsHash
@ -91,9 +86,10 @@ class FRESHTheme
getFormat: ( fmt ) -> @formats[ fmt ] getFormat: ( fmt ) -> @formats[ fmt ]
### Load the theme implicitly, by scanning the theme folder for files. TODO: ### Load the theme implicitly, by scanning the theme folder for files. TODO:
Refactor duplicated code with loadExplicit. ### Refactor duplicated code with loadExplicit. ###
loadImplicit = (formatsHash) -> _load = (formatsHash) ->
# Set up a hash of formats supported by this theme. # Set up a hash of formats supported by this theme.
that = @ that = @
@ -102,31 +98,43 @@ loadImplicit = (formatsHash) ->
# Establish the base theme folder # Establish the base theme folder
tplFolder = PATH.join @folder, 'src' tplFolder = PATH.join @folder, 'src'
copyOnly = ['.ttf','.otf', '.png','.jpg','.jpeg','.pdf']
# Iterate over all files in the theme folder, producing an array, fmts, # 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 # containing info for each file. While we're doing that, also build up
# the formatsHash object. # the formatsHash object.
fmts = READFILES(tplFolder).map (absPath) -> fmts = READFILES(tplFolder).map (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.
pathInfo = parsePath absPath pathInfo = parsePath absPath
absPathSafe = absPath.trim().toLowerCase()
outFmt = '' outFmt = ''
isMajor = false isMajor = false
portion = pathInfo.dirname.replace tplFolder,''
if portion && portion.trim()
return if portion[1] == '_'
reg = /^(?:\/|\\)(html|latex|doc|pdf|png|partials)(?:\/|\\)?/ig
res = reg.exec( portion )
if res
if res[1] != 'partials'
outFmt = res[1]
else
that.partials = that.partials || []
that.partials.push( { name: pathInfo.name, path: absPath } )
return null
# If this file is mentioned in the theme's JSON file under "transforms"
if that.formats
outFmt = _.find Object.keys( that.formats ), ( fmtKey ) ->
fmtVal = that.formats[ fmtKey ]
_.some fmtVal.transform, (fpath) ->
absPathB = PATH.join( that.folder, fpath ).trim().toLowerCase()
absPathB == absPathSafe
isMajor = true if outFmt
if !outFmt
# 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.
portion = pathInfo.dirname.replace tplFolder,''
if portion && portion.trim()
return if portion[1] == '_'
reg = /^(?:\/|\\)(html|latex|doc|pdf|png|partials)(?:\/|\\)?/ig
res = reg.exec( portion )
if res
if res[1] != 'partials'
outFmt = res[1]
else
that.partials = that.partials || []
that.partials.push( { name: pathInfo.name, path: absPath } )
return null
# Otherwise, the output format is inferred from the filename, as in # Otherwise, the output format is inferred from the filename, as in
# compact-[outputformat].[extension], for ex, compact-pdf.html. # compact-[outputformat].[extension], for ex, compact-pdf.html.
@ -135,23 +143,27 @@ loadImplicit = (formatsHash) ->
outFmt = if idx == -1 then pathInfo.name else pathInfo.name.substr( idx + 1 ) outFmt = if idx == -1 then pathInfo.name else pathInfo.name.substr( idx + 1 )
isMajor = true isMajor = true
act = if _.contains copyOnly, pathInfo.extname then 'copy' else 'transform'
# We should have a valid output format now. # We should have a valid output format now.
formatsHash[ outFmt ] = formatsHash[outFmt] || { formatsHash[ outFmt ] = formatsHash[outFmt] || {
outFormat: outFmt, outFormat: outFmt,
files: [] files: []
} }
if that.formats?[ outFmt ]?.symLinks
formatsHash[ outFmt ].symLinks = that.formats[ outFmt ].symLinks
# Create the file representation object. # Create the file representation object.
obj = obj =
action: 'transform' action: act
path: absPath path: absPath
major: isMajor major: isMajor
orgPath: PATH.relative(tplFolder, absPath) orgPath: PATH.relative tplFolder, absPath
ext: pathInfo.extname.slice(1) ext: pathInfo.extname.slice 1
title: friendlyName( outFmt ) title: friendlyName outFmt
pre: outFmt pre: outFmt
# outFormat: outFmt || pathInfo.name, # outFormat: outFmt || pathInfo.name,
data: FS.readFileSync( absPath, 'utf8' ) data: FS.readFileSync absPath, 'utf8'
css: null css: null
# Add this file to the list of files for this format type. # Add this file to the list of files for this format type.
@ -180,87 +192,90 @@ loadImplicit = (formatsHash) ->
### # ###
Load the theme explicitly, by following the 'formats' hash # Load the theme explicitly, by following the 'formats' hash
in the theme's JSON settings file. # in the theme's JSON settings file.
### # ###
loadExplicit = (formatsHash) -> # loadExplicit = (formatsHash) ->
#
# Housekeeping # # Housekeeping
tplFolder = PATH.join this.folder, 'src' # tplFolder = PATH.join this.folder, 'src'
act = null # act = null
that = this # that = this
#
# Iterate over all files in the theme folder, producing an array, fmts, # # 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 # # containing info for each file. While we're doing that, also build up
# the formatsHash object. # # the formatsHash object.
fmts = READFILES( tplFolder ).map (absPath) -> # fmts = READFILES( tplFolder ).map (absPath) ->
#
act = null # act = null
# If this file is mentioned in the theme's JSON file under "transforms" #
pathInfo = parsePath(absPath) # pathInfo = parsePath absPath
absPathSafe = absPath.trim().toLowerCase() # absPathSafe = absPath.trim().toLowerCase()
outFmt = _.find Object.keys( that.formats ), ( fmtKey ) -> #
fmtVal = that.formats[ fmtKey ] # # If this file is mentioned in the theme's JSON file under "transforms"
_.some fmtVal.transform, (fpath) -> # outFmt = _.find Object.keys( that.formats ), ( fmtKey ) ->
absPathB = PATH.join( that.folder, fpath ).trim().toLowerCase() # fmtVal = that.formats[ fmtKey ]
absPathB == absPathSafe # _.some fmtVal.transform, (fpath) ->
# absPathB = PATH.join( that.folder, fpath ).trim().toLowerCase()
act = 'transform' if outFmt # absPathB == absPathSafe
#
# If this file lives in a specific format folder within the theme, # act = 'transform' if outFmt
# such as "/latex" or "/html", then that format is the output format #
# for all files within the folder. # # If this file lives in a specific format folder within the theme,
if !outFmt # # such as "/latex" or "/html", then that format is the output format
portion = pathInfo.dirname.replace tplFolder,'' # # for all files within the folder.
if portion && portion.trim() # if !outFmt
reg = /^(?:\/|\\)(html|latex|doc|pdf)(?:\/|\\)?/ig # portion = pathInfo.dirname.replace tplFolder,''
res = reg.exec portion # if portion && portion.trim()
res && (outFmt = res[1]) # reg = /^(?:\/|\\)(html|latex|doc|pdf)(?:\/|\\)?/ig
# res = reg.exec portion
# Otherwise, the output format is inferred from the filename, as in # res && (outFmt = res[1])
# compact-[outputformat].[extension], for ex, compact-pdf.html. #
if !outFmt # # Otherwise, the output format is inferred from the filename, as in
idx = pathInfo.name.lastIndexOf '-' # # compact-[outputformat].[extension], for ex, compact-pdf.html.
outFmt = if (idx == -1) then pathInfo.name else pathInfo.name.substr(idx + 1) # if !outFmt
# idx = pathInfo.name.lastIndexOf '-'
# We should have a valid output format now. # outFmt = if (idx == -1) then pathInfo.name else pathInfo.name.substr(idx + 1)
formatsHash[ outFmt ] = #
formatsHash[ outFmt ] || { # # We should have a valid output format now.
outFormat: outFmt, # formatsHash[ outFmt ] =
files: [], # formatsHash[ outFmt ] || {
symLinks: that.formats[ outFmt ].symLinks # outFormat: outFmt,
}; # files: [],
# symLinks: that.formats[ outFmt ].symLinks
# Create the file representation object. # };
obj = #
action: act # # Create the file representation object.
orgPath: PATH.relative(that.folder, absPath) # obj =
path: absPath # action: act
ext: pathInfo.extname.slice(1) # orgPath: PATH.relative(that.folder, absPath)
title: friendlyName( outFmt ) # path: absPath
pre: outFmt # ext: pathInfo.extname.slice(1)
# outFormat: outFmt || pathInfo.name, # title: friendlyName( outFmt )
data: FS.readFileSync( absPath, 'utf8' ) # pre: outFmt
css: null # # outFormat: outFmt || pathInfo.name,
# data: FS.readFileSync( absPath, 'utf8' )
# Add this file to the list of files for this format type. # css: null
formatsHash[ outFmt ].files.push( obj ) #
obj # # Add this file to the list of files for this format type.
# formatsHash[ outFmt ].files.push( obj )
# Now, get all the CSS files... # obj
@cssFiles = fmts.filter ( fmt ) -> fmt.ext == 'css' #
# # Now, get all the CSS files...
# For each CSS file, get its corresponding HTML file # @cssFiles = fmts.filter ( fmt ) -> fmt.ext == 'css'
@cssFiles.forEach ( cssf ) -> #
# For each CSS file, get its corresponding HTML file # # For each CSS file, get its corresponding HTML file
idx = _.findIndex fmts, ( fmt ) -> # @cssFiles.forEach ( cssf ) ->
fmt.pre == cssf.pre && fmt.ext == 'html' # # For each CSS file, get its corresponding HTML file
fmts[ idx ].css = cssf.data; # idx = _.findIndex fmts, ( fmt ) ->
fmts[ idx ].cssPath = cssf.path; # fmt.pre == cssf.pre && fmt.ext == 'html'
# Remove CSS files from the formats array # fmts[ idx ].css = cssf.data
fmts = fmts.filter ( fmt) -> fmt.ext != 'css' # fmts[ idx ].cssPath = cssf.path
formatsHash #
# # Remove CSS files from the formats array
# fmts = fmts.filter ( fmt) -> fmt.ext != 'css'
# formatsHash

View File

@ -29,6 +29,8 @@ plain text, and XML versions of Microsoft Word, Excel, and OpenOffice.
module.exports = class TemplateGenerator extends BaseGenerator module.exports = class TemplateGenerator extends BaseGenerator
###* Constructor. Set the output format and template format for this ###* Constructor. Set the output format and template format for this
generator. Will usually be called by a derived generator such as generator. Will usually be called by a derived generator such as
HTMLGenerator or MarkdownGenerator. ### HTMLGenerator or MarkdownGenerator. ###
@ -60,19 +62,24 @@ module.exports = class TemplateGenerator extends BaseGenerator
curFmt.files = _.sortBy curFmt.files, (fi) -> fi.ext != 'css' curFmt.files = _.sortBy curFmt.files, (fi) -> fi.ext != 'css'
# Run the transformation! # Run the transformation!
results = curFmt.files.map( ( tplInfo, idx ) -> results = curFmt.files.map ( tplInfo, idx ) ->
trx = @.single rez, tplInfo.data, this.format, opts, opts.themeObj, curFmt if tplInfo.action == 'transform'
if tplInfo.ext == 'css' trx = @transform rez, tplInfo.data, this.format, opts, opts.themeObj, curFmt
curFmt.files[idx].data = trx if tplInfo.ext == 'css'
else tplInfo.ext == 'html' curFmt.files[idx].data = trx
#tplInfo.css contains the CSS data loaded by theme else tplInfo.ext == 'html'
#tplInfo.cssPath contains the absolute path to the source CSS File #tplInfo.css contains the CSS data loaded by theme
#tplInfo.cssPath contains the absolute path to the source CSS File
else
# Images and non-transformable binary files
opts.onTransform? tplInfo
return info: tplInfo, data: trx return info: tplInfo, data: trx
, @) , @
files: results files: results
###* Generate a resume using file-based inputs and outputs. Requires access ###* Generate a resume using file-based inputs and outputs. Requires access
to the local filesystem. to the local filesystem.
@method generate @method generate
@ -83,7 +90,7 @@ module.exports = class TemplateGenerator extends BaseGenerator
generate: ( rez, f, opts ) -> generate: ( rez, f, opts ) ->
# Prepare # Prepare
this.opts = EXTEND( true, { }, _defaultOpts, opts ); @opts = EXTEND( true, { }, _defaultOpts, opts );
# Call the string-based generation method to perform the generation. # Call the string-based generation method to perform the generation.
genInfo = this.invoke( rez, null ) genInfo = this.invoke( rez, null )
@ -93,43 +100,49 @@ module.exports = class TemplateGenerator extends BaseGenerator
# Process individual files within this format. For example, the HTML # Process individual files within this format. For example, the HTML
# output format for a theme may have multiple HTML files, CSS files, # output format for a theme may have multiple HTML files, CSS files,
# etc. Process them here. # etc. Process them here.
genInfo.files.forEach(( file ) -> genInfo.files.forEach ( file ) ->
# Pre-processing # Pre-processing
file.info.orgPath = file.info.orgPath || '' # <-- For JRS themes file.info.orgPath = file.info.orgPath || ''
thisFilePath = PATH.join( outFolder, file.info.orgPath ) thisFilePath = PATH.join( outFolder, file.info.orgPath )
if this.onBeforeSave
if file.info.action != 'copy' and @onBeforeSave
file.data = this.onBeforeSave file.data = this.onBeforeSave
theme: opts.themeObj theme: opts.themeObj
outputFile: if file.info.major then f else thisFilePath outputFile: thisFilePath #if file.info.major then f else thisFilePath
mk: file.data mk: file.data
opts: this.opts opts: this.opts
if !file.data if !file.data
return # PDF etc return # PDF etc
# Write the file # Write the file
fileName = if file.info.major then f else thisFilePath opts.beforeWrite? thisFilePath
MKDIRP.sync PATH.dirname( fileName )
FS.writeFileSync fileName, file.data, { encoding: 'utf8', flags: 'w' } MKDIRP.sync PATH.dirname( thisFilePath )
#console.log( Object.keys(file.info) )
console.log file.info.path
if file.info.action != 'copy'
FS.writeFileSync thisFilePath, file.data, encoding: 'utf8', flags: 'w'
else
FS.copySync file.info.path, thisFilePath
opts.afterWrite? thisFilePath
# Post-processing # Post-processing
if @onAfterSave if @onAfterSave
@onAfterSave( outputFile: fileName, mk: file.data, opts: this.opts ) @onAfterSave outputFile: fileName, mk: file.data, opts: this.opts
, @) , @
# Some themes require a symlink structure. If so, create it. # Some themes require a symlink structure. If so, create it.
if curFmt.symLinks createSymLinks curFmt, outFolder
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 genInfo
###* Perform a single resume resume transformation using string-based inputs ###* Perform a single resume resume transformation using string-based inputs
and outputs without touching the local file system. and outputs without touching the local file system.
@param json A FRESH or JRS resume object. @param json A FRESH or JRS resume object.
@ -138,8 +151,8 @@ module.exports = class TemplateGenerator extends BaseGenerator
@param cssInfo Needs to be refactored. @param cssInfo Needs to be refactored.
@param opts Options and passthrough data. ### @param opts Options and passthrough data. ###
single: ( json, jst, format, opts, theme, curFmt ) -> transform: ( json, jst, format, opts, theme, curFmt ) ->
if this.opts.freezeBreaks if @opts.freezeBreaks
jst = freeze jst jst = freeze jst
eng = require '../renderers/' + theme.engine + '-generator' eng = require '../renderers/' + theme.engine + '-generator'
result = eng.generate json, jst, format, curFmt, opts, theme result = eng.generate json, jst, format, curFmt, opts, theme
@ -149,6 +162,29 @@ module.exports = class TemplateGenerator extends BaseGenerator
createSymLinks = ( curFmt, outFolder ) ->
# 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]
# Set type to 'file', 'dir', or 'junction' (Windows only)
type = if parsePath( absLoc ).extname then 'file' else 'junction'
try
FS.symlinkSync absTarg, absLoc, type
catch
succeeded = false
if _error.code == 'EEXIST'
FS.unlinkSync absLoc
try
FS.symlinkSync absTarg, absLoc, type
succeeded = true
if !succeeded
throw ex
return
###* Freeze newlines for protection against errant JST parsers. ### ###* Freeze newlines for protection against errant JST parsers. ###
freeze = ( markup ) -> freeze = ( markup ) ->
markup.replace( _reg.regN, _defaultOpts.nSym ) markup.replace( _reg.regN, _defaultOpts.nSym )
@ -162,6 +198,7 @@ unfreeze = ( markup ) ->
markup.replace _reg.regSymN, '\n' markup.replace _reg.regSymN, '\n'
###* Default template generator options. ### ###* Default template generator options. ###
_defaultOpts = _defaultOpts =
engine: 'underscore' engine: 'underscore'

View File

@ -22,12 +22,13 @@ UnderscoreGenerator = module.exports =
generateSimple: ( data, tpl ) -> generateSimple: ( data, tpl ) ->
try try
# Compile and run the Handlebars template. # Compile and run the Handlebars template.
tpl = _.template tpl t = _.template tpl
template data t data
catch catch
#console.dir _error
HMS = require '../core/status-codes' HMS = require '../core/status-codes'
throw throw
fluenterror: HMS[if tpl then 'invokeTemplate' else 'compileTemplate'] fluenterror: HMS[if t then 'invokeTemplate' else 'compileTemplate']
inner: _error inner: _error
@ -49,9 +50,9 @@ UnderscoreGenerator = module.exports =
XML: require 'xml-escape' XML: require 'xml-escape'
RAW: json RAW: json
cssInfo: cssInfo cssInfo: cssInfo
#engine: this #engine: @
headFragment: opts.headFragment || '' headFragment: opts.headFragment || ''
opts: opts opts: opts
registerHelpers theme, opts, cssInfo, ctx, this registerHelpers theme, opts, cssInfo, ctx, @
@generateSimple ctx, jst @generateSimple ctx, jst

View File

@ -59,7 +59,7 @@ _build = ( src, dst, opts ) ->
@err HMSTATUS.resumeNotFound, quit: true @err HMSTATUS.resumeNotFound, quit: true
return null return null
_prep src, dst, opts _prep.call @, src, dst, opts
# Load input resumes as JSON... # Load input resumes as JSON...
sheetObjects = ResumeFactory.load src, sheetObjects = ResumeFactory.load src,
@ -175,6 +175,16 @@ _prep = ( src, dst, opts ) ->
_opts.noTips = opts.noTips _opts.noTips = opts.noTips
_opts.debug = opts.debug _opts.debug = opts.debug
_opts.sort = opts.sort _opts.sort = opts.sort
that = @
# Set up callbacks for internal generators
_opts.onTransform = (info) ->
that.stat HMEVENT.afterTransform, info; return
_opts.beforeWrite = (info) ->
that.stat HMEVENT.beforeWrite, info; return
_opts.afterWrite = (info) ->
that.stat HMEVENT.afterWrite, info; return
# If two or more files are passed to the GENERATE command and the TO # If two or more files are passed to the GENERATE command and the TO
# keyword is omitted, the last file specifies the output file. # keyword is omitted, the last file specifies the output file.
@ -203,7 +213,7 @@ _single = ( targInfo, theme, finished ) ->
fName = PATH.basename f, '.' + fType fName = PATH.basename f, '.' + fType
theFormat = null theFormat = null
@.stat HMEVENT.beforeGenerate, @stat HMEVENT.beforeGenerate,
fmt: targInfo.fmt.outFormat fmt: targInfo.fmt.outFormat
file: PATH.relative(process.cwd(), f) file: PATH.relative(process.cwd(), f)