diff --git a/dist/core/fresh-theme.js b/dist/core/fresh-theme.js index 80bc58b..4a1268e 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, _, _load, friendlyName, loadSafeJson, moment, parsePath, pathExists, validator; + var EXTEND, FRESHTheme, FS, HMSTATUS, PATH, READFILES, _, _load, _loadOne, friendlyName, loadSafeJson, moment, parsePath, pathExists, validator; FS = require('fs'); @@ -31,9 +31,7 @@ Definition of the FRESHTheme class. READFILES = require('recursive-readdir-sync'); - /* - The FRESHTheme class is a representation of a FRESH theme - asset. See also: JRSTheme. + /* A representation of a FRESH theme asset. @class FRESHTheme */ @@ -41,9 +39,7 @@ Definition of the FRESHTheme class. function FRESHTheme() {} - /* - Open and parse the specified theme. - */ + /* Open and parse the specified theme. */ FRESHTheme.prototype.open = function(themeFolder) { var cached, formatsHash, pathInfo, that, themeFile, themeInfo; @@ -96,9 +92,7 @@ Definition of the FRESHTheme class. })(); - /* Load the theme implicitly, by scanning the theme folder for files. TODO: - Refactor duplicated code with loadExplicit. - */ + /* Load and parse theme source files. */ _load = function(formatsHash) { var copyOnly, fmts, major, that, tplFolder; @@ -107,74 +101,8 @@ Definition of the FRESHTheme class. tplFolder = PATH.join(this.folder, 'src'); copyOnly = ['.ttf', '.otf', '.png', '.jpg', '.jpeg', '.pdf']; fmts = READFILES(tplFolder).map(function(absPath) { - var absPathSafe, act, idx, isMajor, obj, outFmt, pathInfo, portion, ref, ref1, reg, res; - pathInfo = parsePath(absPath); - absPathSafe = absPath.trim().toLowerCase(); - outFmt = ''; - isMajor = false; - 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; - } - } - 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; - } - } - } - } - if (!outFmt) { - idx = pathInfo.name.lastIndexOf('-'); - 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: act, - path: absPath, - major: isMajor, - orgPath: PATH.relative(tplFolder, 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; - }); + return _loadOne.call(this, absPath, formatsHash, tplFolder); + }, this); this.cssFiles = fmts.filter(function(fmt) { return fmt && (fmt.ext === 'css'); }); @@ -200,14 +128,87 @@ Definition of the FRESHTheme class. }; - /* - Return a more friendly name for certain formats. - TODO: Refactor - */ + /* Load a single theme file. */ + + _loadOne = function(absPath, formatsHash, tplFolder) { + var absPathSafe, act, idx, obj, outFmt, pathInfo, portion, ref, ref1, reg, res, shouldTransform; + pathInfo = parsePath(absPath); + absPathSafe = absPath.trim().toLowerCase(); + outFmt = ''; + shouldTransform = false; + if (this.explicit) { + outFmt = _.find(Object.keys(this.formats), function(fmtKey) { + var fmtVal; + fmtVal = this.formats[fmtKey]; + return _.some(fmtVal.transform, function(fpath) { + var absPathB; + absPathB = PATH.join(this.folder, fpath).trim().toLowerCase(); + return absPathB === absPathSafe; + }, this); + }, this); + if (outFmt) { + act = 'transform'; + } + } + 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]; + if (!this.explicit) { + act = 'transform'; + } + } else { + this.partials = this.partials || []; + this.partials.push({ + name: pathInfo.name, + path: absPath + }); + return null; + } + } + } + } + if (!outFmt) { + idx = pathInfo.name.lastIndexOf('-'); + outFmt = idx === -1 ? pathInfo.name : pathInfo.name.substr(idx + 1); + if (!this.explicit) { + act = 'transform'; + } + } + formatsHash[outFmt] = formatsHash[outFmt] || { + outFormat: outFmt, + files: [] + }; + if ((ref = this.formats) != null ? (ref1 = ref[outFmt]) != null ? ref1.symLinks : void 0 : void 0) { + formatsHash[outFmt].symLinks = this.formats[outFmt].symLinks; + } + obj = { + action: act, + path: absPath, + orgPath: PATH.relative(tplFolder, 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; + }; + + + /* Return a more friendly name for certain formats. */ friendlyName = function(val) { var friendly; - val = val.trim().toLowerCase(); + val = (val && val.trim().toLowerCase()) || ''; friendly = { yml: 'yaml', md: 'markdown', diff --git a/dist/generators/template-generator.js b/dist/generators/template-generator.js index bf93042..18124dc 100644 --- a/dist/generators/template-generator.js +++ b/dist/generators/template-generator.js @@ -131,7 +131,6 @@ Definition of the TemplateGenerator class. TODO: Refactor 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', diff --git a/dist/helpers/generic-helpers.js b/dist/helpers/generic-helpers.js index 52d7445..9529463 100644 --- a/dist/helpers/generic-helpers.js +++ b/dist/helpers/generic-helpers.js @@ -38,18 +38,21 @@ Generic template helper definitions for HackMyResume / FluentCV. GenericHelpers = module.exports = { /** - Convert the input date to a specified format through Moment.js. - If date is invalid, will return the time provided by the user, - or default to the fallback param or 'Present' if that is set to true - @method formatDate + Display a formatted date with optional fallback text. + Convert the input date to the specified format through Moment.js. If date is + valid, return the formatted date string. If date is null, undefined, or other + falsy value, return the value of the 'fallback' parameter, if specified, or + null if no fallback was specified. If date is invalid, but not null/undefined/ + falsy, return it as-is. */ - formatDate: function(datetime, format, fallback) { + formatDate: function(datetime, dtFormat, fallback) { var momentDate; - if (moment) { - momentDate = moment(datetime); - if (momentDate.isValid()) { - return momentDate.format(format); - } + if (dtFormat == null) { + dtFormat = 'YYYY-MM'; + } + momentDate = moment(datetime); + if (momentDate.isValid()) { + return momentDate.format(dtFormat); } return datetime || (typeof fallback === 'string' ? fallback : (fallback === true ? 'Present' : null)); }, @@ -99,8 +102,8 @@ Generic template helper definitions for HackMyResume / FluentCV. }, /** - Return true if the section is present on the resume and has at least one - element. + Block-level helper. Emit the enclosed content if the resume has a section with + the specified name. Otherwise, emit an empty string ''. @method section */ section: function(title, options) { @@ -292,7 +295,7 @@ Generic template helper definitions for HackMyResume / FluentCV. }, /** - Return true if the context has the property or subpropery. + Emit the enclosed content if the resume has the named property or subproperty. @method has */ has: function(title, options) { @@ -328,10 +331,7 @@ Generic template helper definitions for HackMyResume / FluentCV. return (this.opts.stitles && this.opts.stitles[sname.toLowerCase().trim()]) || stitle; }, - /** - Convert inline Markdown to inline WordProcessingML. - @method wpml - */ + /** Convert inline Markdown to inline WordProcessingML. */ wpml: function(txt, inline) { if (!txt) { return ''; diff --git a/src/core/fresh-theme.coffee b/src/core/fresh-theme.coffee index 055595f..a814da9 100644 --- a/src/core/fresh-theme.coffee +++ b/src/core/fresh-theme.coffee @@ -20,32 +20,26 @@ READFILES = require 'recursive-readdir-sync' -### -The FRESHTheme class is a representation of a FRESH theme -asset. See also: JRSTheme. -@class FRESHTheme -### +### A representation of a FRESH theme asset. +@class FRESHTheme ### class FRESHTheme - - ### - Open and parse the specified theme. - ### + ### Open and parse the specified theme. ### open: ( themeFolder ) -> this.folder = themeFolder; # Open the [theme-name].json file; should have the same name as folder - pathInfo = parsePath( themeFolder ) + pathInfo = parsePath themeFolder # Set up a formats hash for the theme formatsHash = { } # Load the theme - themeFile = PATH.join( themeFolder, 'theme.json' ) - themeInfo = loadSafeJson( themeFile ) + themeFile = PATH.join themeFolder, 'theme.json' + themeInfo = loadSafeJson themeFile if themeInfo.ex throw fluenterror: @@ -87,15 +81,11 @@ class FRESHTheme -### Load the theme implicitly, by scanning the theme folder for files. TODO: -Refactor duplicated code with loadExplicit. ### +### Load and parse theme source files. ### _load = (formatsHash) -> - # Set up a hash of formats supported by this theme. that = @ major = false - - # Establish the base theme folder tplFolder = PATH.join @folder, 'src' copyOnly = ['.ttf','.otf', '.png','.jpg','.jpeg','.pdf'] @@ -104,71 +94,8 @@ _load = (formatsHash) -> # containing info for each file. While we're doing that, also build up # the formatsHash object. fmts = READFILES(tplFolder).map (absPath) -> - - pathInfo = parsePath absPath - absPathSafe = absPath.trim().toLowerCase() - outFmt = '' - isMajor = false - - # 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. - if !outFmt - idx = pathInfo.name.lastIndexOf '-' - 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: act - path: absPath - major: isMajor - orgPath: PATH.relative tplFolder, absPath - ext: pathInfo.extname.slice 1 - title: friendlyName outFmt - pre: outFmt - # outFormat: outFmt || pathInfo.name, - data: FS.readFileSync absPath, 'utf8' - css: null - - # Add this file to the list of files for this format type. - formatsHash[ outFmt ].files.push( obj ) - obj + _loadOne.call @, absPath, formatsHash, tplFolder + , @ # Now, get all the CSS files... @cssFiles = fmts.filter (fmt) -> fmt and (fmt.ext == 'css') @@ -192,101 +119,86 @@ _load = (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 +### Load a single theme file. ### +_loadOne = ( absPath, formatsHash, tplFolder ) -> + + pathInfo = parsePath absPath + absPathSafe = absPath.trim().toLowerCase() + outFmt = '' + shouldTransform = false + + # If this is an "explicit" theme, all files of importance are specified in + # the "transform" section of the theme.json file. + if @explicit + + outFmt = _.find Object.keys( @formats ), ( fmtKey ) -> + fmtVal = @formats[ fmtKey ] + _.some fmtVal.transform, (fpath) -> + absPathB = PATH.join( @folder, fpath ).trim().toLowerCase() + absPathB == absPathSafe + , @ + , @ + act = 'transform' 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 implicit 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] + act = 'transform' if !@explicit + else + @partials = @partials || [] + @partials.push( { name: pathInfo.name, path: absPath } ) + return null + + # Otherwise, the output format is inferred from the filename, as in + # compact-[outputformat].[extension], for ex, compact-pdf.html + if !outFmt + idx = pathInfo.name.lastIndexOf '-' + outFmt = if idx == -1 then pathInfo.name else pathInfo.name.substr idx+1 + act = 'transform' if !@explicit + + # Make sure we have a valid formatsHash + formatsHash[ outFmt ] = formatsHash[outFmt] || { + outFormat: outFmt, + files: [] + } + + # Move symlink descriptions from theme.json to the format + if @formats?[ outFmt ]?.symLinks + formatsHash[ outFmt ].symLinks = @formats[ outFmt ].symLinks + + # Create the file representation object + obj = + action: act + path: absPath + orgPath: PATH.relative tplFolder, absPath + ext: pathInfo.extname.slice 1 + title: friendlyName outFmt + pre: outFmt + # outFormat: outFmt || pathInfo.name, + data: FS.readFileSync absPath, 'utf8' + css: null + + # Add this file to the list of files for this format type. + formatsHash[ outFmt ].files.push( obj ) + obj -### -Return a more friendly name for certain formats. -TODO: Refactor -### +### Return a more friendly name for certain formats. ### friendlyName = ( val ) -> - val = val.trim().toLowerCase() + val = (val && val.trim().toLowerCase()) || '' friendly = { yml: 'yaml', md: 'markdown', txt: 'text' } friendly[val] || val + module.exports = FRESHTheme diff --git a/src/generators/template-generator.coffee b/src/generators/template-generator.coffee index 97def9e..92b96a3 100644 --- a/src/generators/template-generator.coffee +++ b/src/generators/template-generator.coffee @@ -64,7 +64,7 @@ module.exports = class TemplateGenerator extends BaseGenerator # Run the transformation! results = curFmt.files.map ( tplInfo, idx ) -> if tplInfo.action == 'transform' - trx = @transform rez, tplInfo.data, this.format, opts, opts.themeObj, curFmt + trx = @transform rez, tplInfo.data, @format, opts, opts.themeObj, curFmt if tplInfo.ext == 'css' curFmt.files[idx].data = trx else tplInfo.ext == 'html' @@ -109,20 +109,17 @@ module.exports = class TemplateGenerator extends BaseGenerator if file.info.action != 'copy' and @onBeforeSave file.data = this.onBeforeSave theme: opts.themeObj - outputFile: thisFilePath #if file.info.major then f else thisFilePath + outputFile: thisFilePath mk: file.data opts: this.opts if !file.data - return # PDF etc + return # Write the file 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 diff --git a/src/helpers/generic-helpers.coffee b/src/helpers/generic-helpers.coffee index 2ac41ab..ba273bf 100644 --- a/src/helpers/generic-helpers.coffee +++ b/src/helpers/generic-helpers.coffee @@ -27,15 +27,18 @@ GenericHelpers = module.exports = ###* - Convert the input date to a specified format through Moment.js. - If date is invalid, will return the time provided by the user, - or default to the fallback param or 'Present' if that is set to true - @method formatDate + Display a formatted date with optional fallback text. + Convert the input date to the specified format through Moment.js. If date is + valid, return the formatted date string. If date is null, undefined, or other + falsy value, return the value of the 'fallback' parameter, if specified, or + null if no fallback was specified. If date is invalid, but not null/undefined/ + falsy, return it as-is. ### - formatDate: (datetime, format, fallback) -> - if moment - momentDate = moment datetime - return momentDate.format(format) if momentDate.isValid() + formatDate: (datetime, dtFormat, fallback) -> + + dtFormat ?= 'YYYY-MM' + momentDate = moment datetime + return momentDate.format(dtFormat) if momentDate.isValid() datetime || if typeof fallback == 'string' @@ -82,8 +85,8 @@ GenericHelpers = module.exports = ###* - Return true if the section is present on the resume and has at least one - element. + Block-level helper. Emit the enclosed content if the resume has a section with + the specified name. Otherwise, emit an empty string ''. @method section ### section: ( title, options ) -> @@ -273,7 +276,7 @@ GenericHelpers = module.exports = ###* - Return true if the context has the property or subpropery. + Emit the enclosed content if the resume has the named property or subproperty. @method has ### has: ( title, options ) -> @@ -319,10 +322,7 @@ GenericHelpers = module.exports = - ###* - Convert inline Markdown to inline WordProcessingML. - @method wpml - ### + ###* Convert inline Markdown to inline WordProcessingML. ### wpml: ( txt, inline ) -> return '' if !txt inline = (inline && !inline.hash) || false diff --git a/src/verbs/build.coffee b/src/verbs/build.coffee index 08d7514..598d765 100644 --- a/src/verbs/build.coffee +++ b/src/verbs/build.coffee @@ -239,10 +239,7 @@ _single = ( targInfo, theme, finished ) -> ret = theFormat.gen.generate _rezObj, f, _opts catch e - # Catch any errors caused by generating this file and don't let them - # propagate -- typically we want to continue processing other formats - # even if this format failed. - ex = e; + ex = e this.stat HMEVENT.afterGenerate, fmt: targInfo.fmt.outFormat,