From aaa5e1fc1ffe2c5ab33fc76ce898177db37105a7 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Tue, 9 Feb 2016 15:27:34 -0500 Subject: [PATCH] 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. --- dist/core/event-codes.js | 5 +- dist/core/fresh-theme.js | 146 ++++---------- dist/generators/template-generator.js | 85 +++++--- dist/helpers/underscore-helpers.js | 4 +- dist/renderers/underscore-generator.js | 13 +- dist/verbs/build.js | 13 +- src/core/event-codes.coffee | 3 + src/core/fresh-theme.coffee | 233 ++++++++++++---------- src/generators/template-generator.coffee | 91 ++++++--- src/renderers/underscore-generator.coffee | 11 +- src/verbs/build.coffee | 14 +- 11 files changed, 336 insertions(+), 282 deletions(-) diff --git a/dist/core/event-codes.js b/dist/core/event-codes.js index 8ceeb92..3db4e24 100644 --- a/dist/core/event-codes.js +++ b/dist/core/event-codes.js @@ -33,7 +33,10 @@ Event code definitions. beforeInlineConvert: 22, afterInlineConvert: 23, beforeValidate: 24, - afterValidate: 25 + afterValidate: 25, + beforeWrite: 26, + afterWrite: 27, + applyTheme: 28 }; }).call(this); diff --git a/dist/core/fresh-theme.js b/dist/core/fresh-theme.js index d70740a..80bc58b 100644 --- a/dist/core/fresh-theme.js +++ b/dist/core/fresh-theme.js @@ -6,7 +6,7 @@ Definition of the FRESHTheme class. */ (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'); @@ -71,12 +71,7 @@ Definition of the FRESHTheme class. return formatsHash[key] = cached[th].getFormat(key); }); } - if (!!this.formats) { - formatsHash = loadExplicit.call(this, formatsHash); - this.explicit = true; - } else { - formatsHash = loadImplicit.call(this, formatsHash); - } + formatsHash = _load.call(this, formatsHash); this.formats = formatsHash; this.name = parsePath(this.folder).name; return this; @@ -105,33 +100,51 @@ Definition of the FRESHTheme class. Refactor duplicated code with loadExplicit. */ - loadImplicit = function(formatsHash) { - var fmts, major, that, tplFolder; + _load = function(formatsHash) { + var copyOnly, fmts, major, that, tplFolder; that = this; major = false; tplFolder = PATH.join(this.folder, 'src'); + copyOnly = ['.ttf', '.otf', '.png', '.jpg', '.jpeg', '.pdf']; 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); + absPathSafe = absPath.trim().toLowerCase(); outFmt = ''; isMajor = false; - portion = pathInfo.dirname.replace(tplFolder, ''); - if (portion && portion.trim()) { - if (portion[1] === '_') { - return; + if (that.formats) { + 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) { + isMajor = true; } - 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 (!outFmt) { + portion = pathInfo.dirname.replace(tplFolder, ''); + if (portion && portion.trim()) { + if (portion[1] === '_') { + return; + } + 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; + } } } } @@ -140,12 +153,16 @@ Definition of the FRESHTheme class. outFmt = idx === -1 ? pathInfo.name : pathInfo.name.substr(idx + 1); isMajor = true; } + act = _.contains(copyOnly, pathInfo.extname) ? 'copy' : 'transform'; formatsHash[outFmt] = formatsHash[outFmt] || { outFormat: outFmt, files: [] }; + if ((ref = that.formats) != null ? (ref1 = ref[outFmt]) != null ? ref1.symLinks : void 0 : void 0) { + formatsHash[outFmt].symLinks = that.formats[outFmt].symLinks; + } obj = { - action: 'transform', + action: act, path: absPath, major: isMajor, 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. TODO: Refactor diff --git a/dist/generators/template-generator.js b/dist/generators/template-generator.js index 247875c..bf93042 100644 --- a/dist/generators/template-generator.js +++ b/dist/generators/template-generator.js @@ -6,7 +6,7 @@ Definition of the TemplateGenerator class. TODO: Refactor */ (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; }, hasProp = {}.hasOwnProperty; @@ -74,11 +74,18 @@ Definition of the TemplateGenerator class. TODO: Refactor }); results = curFmt.files.map(function(tplInfo, idx) { var trx; - trx = this.single(rez, tplInfo.data, this.format, opts, opts.themeObj, curFmt); - if (tplInfo.ext === 'css') { - curFmt.files[idx].data = trx; + if (tplInfo.action === 'transform') { + trx = this.transform(rez, tplInfo.data, this.format, opts, opts.themeObj, curFmt); + if (tplInfo.ext === 'css') { + curFmt.files[idx].data = trx; + } else { + tplInfo.ext === 'html'; + } } else { - tplInfo.ext === 'html'; + + } + if (typeof opts.onTransform === "function") { + opts.onTransform(tplInfo); } return { info: tplInfo, @@ -106,13 +113,13 @@ Definition of the TemplateGenerator class. TODO: Refactor outFolder = parsePath(f).dirname; curFmt = opts.themeObj.getFormat(this.format); genInfo.files.forEach(function(file) { - var fileName, thisFilePath; + var thisFilePath; file.info.orgPath = file.info.orgPath || ''; thisFilePath = PATH.join(outFolder, file.info.orgPath); - if (this.onBeforeSave) { + if (file.info.action !== 'copy' && this.onBeforeSave) { file.data = this.onBeforeSave({ theme: opts.themeObj, - outputFile: file.info.major ? f : thisFilePath, + outputFile: thisFilePath, mk: file.data, opts: this.opts }); @@ -120,12 +127,22 @@ Definition of the TemplateGenerator class. TODO: Refactor return; } } - fileName = file.info.major ? f : thisFilePath; - MKDIRP.sync(PATH.dirname(fileName)); - FS.writeFileSync(fileName, file.data, { - encoding: 'utf8', - flags: 'w' - }); + if (typeof opts.beforeWrite === "function") { + opts.beforeWrite(thisFilePath); + } + MKDIRP.sync(PATH.dirname(thisFilePath)); + 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) { return this.onAfterSave({ outputFile: fileName, @@ -134,17 +151,7 @@ Definition of the TemplateGenerator class. TODO: Refactor }); } }, this); - if (curFmt.symLinks) { - 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); - }); - } + createSymLinks(curFmt, outFolder); return genInfo; }; @@ -158,7 +165,7 @@ Definition of the TemplateGenerator class. TODO: Refactor @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; if (this.opts.freezeBreaks) { jst = freeze(jst); @@ -175,6 +182,32 @@ Definition of the TemplateGenerator class. TODO: Refactor })(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. */ diff --git a/dist/helpers/underscore-helpers.js b/dist/helpers/underscore-helpers.js index 72f96e6..1ef7248 100644 --- a/dist/helpers/underscore-helpers.js +++ b/dist/helpers/underscore-helpers.js @@ -1,7 +1,7 @@ /** 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 */ @@ -26,7 +26,7 @@ Template helper definitions for Underscore. helpers.cssInfo = cssInfo; helpers.engine = eng; ctx.h = helpers; - return _.each(helpers, function(hVal, hKey) { + _.each(helpers, function(hVal, hKey) { if (_.isFunction(hVal)) { return _.bind(hVal, ctx); } diff --git a/dist/renderers/underscore-generator.js b/dist/renderers/underscore-generator.js index 4154e07..2a1894b 100644 --- a/dist/renderers/underscore-generator.js +++ b/dist/renderers/underscore-generator.js @@ -6,14 +6,12 @@ Definition of the UnderscoreGenerator class. */ (function() { - var HMSTATUS, UnderscoreGenerator, _, registerHelpers; + var UnderscoreGenerator, _, registerHelpers; _ = require('underscore'); registerHelpers = require('../helpers/underscore-helpers'); - HMSTATUS = require('../core/status-codes'); - /** Perform template-based resume generation using Underscore.js. @@ -22,13 +20,14 @@ Definition of the UnderscoreGenerator class. UnderscoreGenerator = module.exports = { generateSimple: function(data, tpl) { - var template; + var HMS, t; try { - template = _.template(tpl); - return template(data); + t = _.template(tpl); + return t(data); } catch (_error) { + HMS = require('../core/status-codes'); throw { - fluenterror: template ? HMSTATUS.invokeTemplate : HMSTATUS.compileTemplate, + fluenterror: HMS[t ? 'invokeTemplate' : 'compileTemplate'], inner: _error }; } diff --git a/dist/verbs/build.js b/dist/verbs/build.js index 208b58d..2220acd 100644 --- a/dist/verbs/build.js +++ b/dist/verbs/build.js @@ -103,7 +103,7 @@ Implementation of the 'build' verb for HackMyResume. }); return null; } - _prep(src, dst, opts); + _prep.call(this, src, dst, opts); sheetObjects = ResumeFactory.load(src, { format: null, objectify: false, @@ -230,6 +230,7 @@ Implementation of the 'build' verb for HackMyResume. */ _prep = function(src, dst, opts) { + var that; _opts.theme = (opts.theme && opts.theme.toLowerCase().trim()) || 'modern'; _opts.prettify = opts.prettify === true; _opts.css = opts.css; @@ -241,6 +242,16 @@ Implementation of the 'build' verb for HackMyResume. _opts.noTips = opts.noTips; _opts.debug = opts.debug; _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()); }; diff --git a/src/core/event-codes.coffee b/src/core/event-codes.coffee index 44c55a0..420a0ed 100644 --- a/src/core/event-codes.coffee +++ b/src/core/event-codes.coffee @@ -33,3 +33,6 @@ module.exports = afterInlineConvert: 23 beforeValidate: 24 afterValidate: 25 + beforeWrite: 26 + afterWrite: 27 + applyTheme: 28 diff --git a/src/core/fresh-theme.coffee b/src/core/fresh-theme.coffee index 443321c..055595f 100644 --- a/src/core/fresh-theme.coffee +++ b/src/core/fresh-theme.coffee @@ -69,13 +69,8 @@ class FRESHTheme cached[ th ] = cached[th] || new FRESHTheme().open( themePath ) formatsHash[ key ] = cached[ th ].getFormat( key ) - # Check for an explicit "formats" entry in the theme JSON. If it has one, - # then this theme declares its files explicitly. - if !!@formats - formatsHash = loadExplicit.call this, formatsHash - @explicit = true; - else - formatsHash = loadImplicit.call this, formatsHash + # Load theme files + formatsHash = _load.call @, formatsHash # Cache @formats = formatsHash @@ -91,9 +86,10 @@ class FRESHTheme getFormat: ( fmt ) -> @formats[ fmt ] + ### Load the theme implicitly, by scanning the theme folder for files. TODO: Refactor duplicated code with loadExplicit. ### -loadImplicit = (formatsHash) -> +_load = (formatsHash) -> # Set up a hash of formats supported by this theme. that = @ @@ -102,31 +98,43 @@ loadImplicit = (formatsHash) -> # Establish the base theme folder tplFolder = PATH.join @folder, 'src' + copyOnly = ['.ttf','.otf', '.png','.jpg','.jpeg','.pdf'] + # Iterate over all files in the theme folder, producing an array, fmts, # containing info for each file. While we're doing that, also build up # the formatsHash object. 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 + absPathSafe = absPath.trim().toLowerCase() outFmt = '' 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 # 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 ) isMajor = true + act = if _.contains copyOnly, pathInfo.extname then 'copy' else 'transform' + # We should have a valid output format now. formatsHash[ outFmt ] = formatsHash[outFmt] || { outFormat: outFmt, files: [] } + if that.formats?[ outFmt ]?.symLinks + formatsHash[ outFmt ].symLinks = that.formats[ outFmt ].symLinks # Create the file representation object. obj = - action: 'transform' + action: act path: absPath major: isMajor - orgPath: PATH.relative(tplFolder, absPath) - ext: pathInfo.extname.slice(1) - title: friendlyName( outFmt ) + orgPath: PATH.relative tplFolder, absPath + ext: pathInfo.extname.slice 1 + title: friendlyName outFmt pre: outFmt # outFormat: outFmt || pathInfo.name, - data: FS.readFileSync( absPath, 'utf8' ) + data: FS.readFileSync absPath, 'utf8' css: null # 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 -in the theme's JSON settings file. -### -loadExplicit = (formatsHash) -> - - # Housekeeping - tplFolder = PATH.join this.folder, 'src' - act = null - that = this - - # Iterate over all files in the theme folder, producing an array, fmts, - # containing info for each file. While we're doing that, also build up - # the formatsHash object. - fmts = READFILES( tplFolder ).map (absPath) -> - - act = null - # If this file is mentioned in the theme's JSON file under "transforms" - pathInfo = parsePath(absPath) - absPathSafe = absPath.trim().toLowerCase() - 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 - - act = 'transform' 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. - 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]) - - # Otherwise, the output format is inferred from the filename, as in - # compact-[outputformat].[extension], for ex, compact-pdf.html. - if !outFmt - idx = pathInfo.name.lastIndexOf '-' - outFmt = if (idx == -1) then pathInfo.name else pathInfo.name.substr(idx + 1) - - # We should have a valid output format now. - formatsHash[ outFmt ] = - formatsHash[ outFmt ] || { - outFormat: outFmt, - files: [], - symLinks: that.formats[ outFmt ].symLinks - }; - - # Create the file representation object. - obj = - action: act - orgPath: PATH.relative(that.folder, absPath) - path: absPath - ext: pathInfo.extname.slice(1) - title: friendlyName( outFmt ) - pre: outFmt - # outFormat: outFmt || pathInfo.name, - data: FS.readFileSync( absPath, 'utf8' ) - css: null - - # Add this file to the list of files for this format type. - formatsHash[ outFmt ].files.push( obj ) - obj - - # Now, get all the CSS files... - @cssFiles = fmts.filter ( fmt ) -> fmt.ext == 'css' - - # For each CSS file, get its corresponding HTML file - @cssFiles.forEach ( cssf ) -> - # For each CSS file, get its corresponding HTML file - idx = _.findIndex fmts, ( fmt ) -> - fmt.pre == cssf.pre && fmt.ext == 'html' - fmts[ idx ].css = cssf.data; - fmts[ idx ].cssPath = cssf.path; - # Remove CSS files from the formats array - fmts = fmts.filter ( fmt) -> fmt.ext != 'css' - formatsHash +# ### +# Load the theme explicitly, by following the 'formats' hash +# in the theme's JSON settings file. +# ### +# loadExplicit = (formatsHash) -> +# +# # Housekeeping +# tplFolder = PATH.join this.folder, 'src' +# act = null +# that = this +# +# # Iterate over all files in the theme folder, producing an array, fmts, +# # containing info for each file. While we're doing that, also build up +# # the formatsHash object. +# fmts = READFILES( tplFolder ).map (absPath) -> +# +# act = null +# +# pathInfo = parsePath absPath +# absPathSafe = absPath.trim().toLowerCase() +# +# # If this file is mentioned in the theme's JSON file under "transforms" +# 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 +# +# act = 'transform' 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. +# 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]) +# +# # Otherwise, the output format is inferred from the filename, as in +# # compact-[outputformat].[extension], for ex, compact-pdf.html. +# if !outFmt +# idx = pathInfo.name.lastIndexOf '-' +# outFmt = if (idx == -1) then pathInfo.name else pathInfo.name.substr(idx + 1) +# +# # We should have a valid output format now. +# formatsHash[ outFmt ] = +# formatsHash[ outFmt ] || { +# outFormat: outFmt, +# files: [], +# symLinks: that.formats[ outFmt ].symLinks +# }; +# +# # Create the file representation object. +# obj = +# action: act +# orgPath: PATH.relative(that.folder, absPath) +# path: absPath +# ext: pathInfo.extname.slice(1) +# title: friendlyName( outFmt ) +# pre: outFmt +# # outFormat: outFmt || pathInfo.name, +# data: FS.readFileSync( absPath, 'utf8' ) +# css: null +# +# # Add this file to the list of files for this format type. +# formatsHash[ outFmt ].files.push( obj ) +# obj +# +# # Now, get all the CSS files... +# @cssFiles = fmts.filter ( fmt ) -> fmt.ext == 'css' +# +# # For each CSS file, get its corresponding HTML file +# @cssFiles.forEach ( cssf ) -> +# # For each CSS file, get its corresponding HTML file +# idx = _.findIndex fmts, ( fmt ) -> +# fmt.pre == cssf.pre && fmt.ext == 'html' +# fmts[ idx ].css = cssf.data +# fmts[ idx ].cssPath = cssf.path +# +# # Remove CSS files from the formats array +# fmts = fmts.filter ( fmt) -> fmt.ext != 'css' +# formatsHash diff --git a/src/generators/template-generator.coffee b/src/generators/template-generator.coffee index ae615d9..97def9e 100644 --- a/src/generators/template-generator.coffee +++ b/src/generators/template-generator.coffee @@ -29,6 +29,8 @@ plain text, and XML versions of Microsoft Word, Excel, and OpenOffice. module.exports = class TemplateGenerator extends BaseGenerator + + ###* Constructor. Set the output format and template format for this generator. Will usually be called by a derived generator such as HTMLGenerator or MarkdownGenerator. ### @@ -60,19 +62,24 @@ module.exports = class TemplateGenerator extends BaseGenerator 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 + results = curFmt.files.map ( tplInfo, idx ) -> + if tplInfo.action == 'transform' + trx = @transform 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 + else + # Images and non-transformable binary files + opts.onTransform? tplInfo return info: tplInfo, data: trx - , @) + , @ files: results + ###* Generate a resume using file-based inputs and outputs. Requires access to the local filesystem. @method generate @@ -83,7 +90,7 @@ module.exports = class TemplateGenerator extends BaseGenerator generate: ( rez, f, opts ) -> # Prepare - this.opts = EXTEND( true, { }, _defaultOpts, opts ); + @opts = EXTEND( true, { }, _defaultOpts, opts ); # Call the string-based generation method to perform the generation. 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 # output format for a theme may have multiple HTML files, CSS files, # etc. Process them here. - genInfo.files.forEach(( file ) -> + genInfo.files.forEach ( file ) -> # Pre-processing - file.info.orgPath = file.info.orgPath || '' # <-- For JRS themes + file.info.orgPath = file.info.orgPath || '' thisFilePath = PATH.join( outFolder, file.info.orgPath ) - if this.onBeforeSave + + if file.info.action != 'copy' and @onBeforeSave file.data = this.onBeforeSave 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 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' } + opts.beforeWrite? thisFilePath + + 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 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. - 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 + createSymLinks curFmt, outFolder 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. @@ -138,8 +151,8 @@ module.exports = class TemplateGenerator extends BaseGenerator @param cssInfo Needs to be refactored. @param opts Options and passthrough data. ### - single: ( json, jst, format, opts, theme, curFmt ) -> - if this.opts.freezeBreaks + transform: ( json, jst, format, opts, theme, curFmt ) -> + if @opts.freezeBreaks jst = freeze jst eng = require '../renderers/' + theme.engine + '-generator' 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 = ( markup ) -> markup.replace( _reg.regN, _defaultOpts.nSym ) @@ -162,6 +198,7 @@ unfreeze = ( markup ) -> markup.replace _reg.regSymN, '\n' + ###* Default template generator options. ### _defaultOpts = engine: 'underscore' diff --git a/src/renderers/underscore-generator.coffee b/src/renderers/underscore-generator.coffee index 38ea0c0..45ef731 100644 --- a/src/renderers/underscore-generator.coffee +++ b/src/renderers/underscore-generator.coffee @@ -22,12 +22,13 @@ UnderscoreGenerator = module.exports = generateSimple: ( data, tpl ) -> try # Compile and run the Handlebars template. - tpl = _.template tpl - template data + t = _.template tpl + t data catch + #console.dir _error HMS = require '../core/status-codes' throw - fluenterror: HMS[if tpl then 'invokeTemplate' else 'compileTemplate'] + fluenterror: HMS[if t then 'invokeTemplate' else 'compileTemplate'] inner: _error @@ -49,9 +50,9 @@ UnderscoreGenerator = module.exports = XML: require 'xml-escape' RAW: json cssInfo: cssInfo - #engine: this + #engine: @ headFragment: opts.headFragment || '' opts: opts - registerHelpers theme, opts, cssInfo, ctx, this + registerHelpers theme, opts, cssInfo, ctx, @ @generateSimple ctx, jst diff --git a/src/verbs/build.coffee b/src/verbs/build.coffee index f47ccfe..08d7514 100644 --- a/src/verbs/build.coffee +++ b/src/verbs/build.coffee @@ -59,7 +59,7 @@ _build = ( src, dst, opts ) -> @err HMSTATUS.resumeNotFound, quit: true return null - _prep src, dst, opts + _prep.call @, src, dst, opts # Load input resumes as JSON... sheetObjects = ResumeFactory.load src, @@ -175,6 +175,16 @@ _prep = ( src, dst, opts ) -> _opts.noTips = opts.noTips _opts.debug = opts.debug _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 # keyword is omitted, the last file specifies the output file. @@ -203,7 +213,7 @@ _single = ( targInfo, theme, finished ) -> fName = PATH.basename f, '.' + fType theFormat = null - @.stat HMEVENT.beforeGenerate, + @stat HMEVENT.beforeGenerate, fmt: targInfo.fmt.outFormat file: PATH.relative(process.cwd(), f)