This commit is contained in:
hacksalot 2016-02-11 11:48:44 -05:00
parent aaa5e1fc1f
commit 317a250917
7 changed files with 203 additions and 297 deletions

View File

@ -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',

View File

@ -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',

View File

@ -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 '';

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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,