From b21fd93d66731db3fc5c19ce2a12b3734f4d4cf7 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Wed, 30 Dec 2015 12:08:46 -0500 Subject: [PATCH 01/23] Introduce JRSTheme class. Start splitting out logic into dedicated abstractions for both FRESH and JSON Resume themes given the different structure and use cases of each. --- src/core/jrs-theme.js | 70 ++++++++++++++++++++++++++++++++++++++++ src/core/status-codes.js | 4 ++- 2 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 src/core/jrs-theme.js diff --git a/src/core/jrs-theme.js b/src/core/jrs-theme.js new file mode 100644 index 0000000..26904c2 --- /dev/null +++ b/src/core/jrs-theme.js @@ -0,0 +1,70 @@ +/** +Definition of the JRSTheme class. +@module jrs-theme.js +@license MIT. See LICENSE.MD for details. +*/ + +(function() { + + + + var _ = require('underscore') + , PATH = require('path') + , parsePath = require('parse-filepath') + , pathExists = require('path-exists').sync; + + + + /** + The JRSTheme class is a representation of a JSON Resume theme asset. + @class JRSTheme + */ + function JRSTheme() { + + } + + /** + Open and parse the specified theme. + */ + JRSTheme.prototype.open = function( themeFolder ) { + + this.folder = themeFolder; + + // Open the [theme-name].json file; should have the same name as folder + var pathInfo = parsePath( themeFolder ); + + // Open and parse the theme's package.json file. + var packageJsonPath = PATH.join(themeFolder, 'package.json'); + if( pathExists( packageJsonPath ) ) { + var themePack = require( themeFolder ); + var themePkgJson = require( packageJsonPath ); + this.name = themePkgJson.name; + this.render = (themePack && themePack.render) || undefined; + this.formats = { + html: { title: 'html', outFormat: 'html', ext: 'html' } + }; + } + else { + throw { fluenterror: 10 }; + } + + return this; + }; + + /** + Determine if the theme supports the specified output format. + */ + JRSTheme.prototype.hasFormat = function( fmt ) { + return _.has( this.formats, fmt ); + }; + + /** + Determine if the theme supports the specified output format. + */ + JRSTheme.prototype.getFormat = function( fmt ) { + return this.formats[ fmt ]; + }; + + module.exports = JRSTheme; + +}()); diff --git a/src/core/status-codes.js b/src/core/status-codes.js index d0467c7..400467f 100644 --- a/src/core/status-codes.js +++ b/src/core/status-codes.js @@ -1,6 +1,7 @@ /** Status codes for HackMyResume. @module status-codes.js +@license MIT. See LICENSE.MD for details. */ (function(){ @@ -15,7 +16,8 @@ Status codes for HackMyResume. resumeNotFoundAlt: 6, inputOutputParity: 7, createNameMissing: 8, - wkhtmltopdf: 9 + wkhtmltopdf: 9, + missingPackageJSON: 10 }; }()); From 3e7d9c04112d613e6db2b0ce7f8e329f7dd9ea20 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Wed, 30 Dec 2015 12:37:26 -0500 Subject: [PATCH 02/23] Integrate JRSTheme class. --- src/core/{theme.js => fresh-theme.js} | 0 src/gen/template-generator.js | 27 ++++++++++++++++++--------- src/hackmyapi.js | 3 ++- src/verbs/generate.js | 6 +++++- 4 files changed, 25 insertions(+), 11 deletions(-) rename src/core/{theme.js => fresh-theme.js} (100%) diff --git a/src/core/theme.js b/src/core/fresh-theme.js similarity index 100% rename from src/core/theme.js rename to src/core/fresh-theme.js diff --git a/src/gen/template-generator.js b/src/gen/template-generator.js index 60a7a6f..20c3ea6 100644 --- a/src/gen/template-generator.js +++ b/src/gen/template-generator.js @@ -17,7 +17,8 @@ Definition of the TemplateGenerator class. , MKDIRP = require('mkdirp') , BaseGenerator = require( './base-generator' ) , EXTEND = require('../utils/extend') - , Theme = require('../core/theme'); + , FRESHTheme = require('../core/fresh-theme') + , JRSTheme = require('../core/jrs-theme'); @@ -220,20 +221,28 @@ Definition of the TemplateGenerator class. Given a theme title, load the corresponding theme. */ function themeFromMoniker() { + // Verify the specified theme name/path var tFolder = PATH.join( parsePath( require.resolve('fluent-themes') ).dirname, this.opts.theme ); - var exists = require('path-exists').sync; - if( !exists( tFolder ) ) { - tFolder = PATH.resolve( this.opts.theme ); - if( !exists( tFolder ) ) { - throw { fluenterror: this.codes.themeNotFound, data: this.opts.theme}; - } - } - var t = this.opts.themeObj || new Theme().open( tFolder ); + var t; + if( this.opts.theme.startsWith('jsonresume-theme-') ) { + console.log('LOADING JSON RESUME'); + t = new JRSTheme().open( tFolder ); + } + else { + var exists = require('path-exists').sync; + if( !exists( tFolder ) ) { + tFolder = PATH.resolve( this.opts.theme ); + if( !exists( tFolder ) ) { + throw { fluenterror: this.codes.themeNotFound, data: this.opts.theme}; + } + } + t = this.opts.themeObj || new FRESHTheme().open( tFolder ); + } // Load the theme and format return { diff --git a/src/hackmyapi.js b/src/hackmyapi.js index f36c40a..a118028 100644 --- a/src/hackmyapi.js +++ b/src/hackmyapi.js @@ -8,7 +8,8 @@ module.exports = { Sheet: require('./core/fresh-resume'), FRESHResume: require('./core/fresh-resume'), JRSResume: require('./core/jrs-resume'), - Theme: require('./core/theme'), + FRESHTheme: require('./core/fresh-theme'), + JRSTheme: require('./core/jrs-theme'), FluentDate: require('./core/fluent-date'), HtmlGenerator: require('./gen/html-generator'), TextGenerator: require('./gen/text-generator'), diff --git a/src/verbs/generate.js b/src/verbs/generate.js index 2291f53..344a9ba 100644 --- a/src/verbs/generate.js +++ b/src/verbs/generate.js @@ -6,10 +6,12 @@ , MD = require('marked') , MKDIRP = require('mkdirp') , _opts = require('../core/default-options') - , FluentTheme = require('../core/theme') + , FluentTheme = require('../core/fresh-theme') + , JRSTheme = require('../core/jrs-theme') , ResumeFactory = require('../core/resume-factory') , _ = require('underscore') , _fmts = require('../core/default-formats') + , unused = require('string.prototype.startswith') , _err, _log, rez; /** @@ -51,6 +53,8 @@ // Load the theme var theTheme = (new FluentTheme()).open( tFolder ); + var theTheme = _opts.theme.startsWith('jsonresume-theme') ? + new JRSTheme().open(tFolder) : new FluentTheme().open( tFolder ); _opts.themeObj = theTheme; var numFormats = theTheme.formats ? Object.keys(theTheme.formats).length : 2; _log( 'Applying '.info + theTheme.name.toUpperCase().infoBold + From 3c7868a750102c3475d392ee9a7166de6f9ff6dd Mon Sep 17 00:00:00 2001 From: hacksalot Date: Wed, 30 Dec 2015 12:38:01 -0500 Subject: [PATCH 03/23] Scrub. --- src/gen/base-generator.js | 2 +- src/verbs/generate.js | 22 ++++++++++++---------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/gen/base-generator.js b/src/gen/base-generator.js index 5d11777..b8a9538 100644 --- a/src/gen/base-generator.js +++ b/src/gen/base-generator.js @@ -1,7 +1,7 @@ /** Definition of the BaseGenerator class. -@license MIT. Copyright (c) 2015 James Devlin / FluentDesk. @module base-generator.js +@license MIT. See LICENSE.md for details. */ (function() { diff --git a/src/verbs/generate.js b/src/verbs/generate.js index 344a9ba..f9f1a76 100644 --- a/src/verbs/generate.js +++ b/src/verbs/generate.js @@ -24,8 +24,8 @@ module.exports = /** - Given a source JSON resume, a destination resume path, and a theme file, - generate 0..N resumes in the desired formats. + Given a source resume in FRESH or JRS format, a destination resume path, and a + theme file, generate 0..N resumes in the desired formats. @param src Path to the source JSON resume file: "rez/resume.json". @param dst An array of paths to the target resume file(s). @param theme Friendly name of the resume theme. Defaults to "modern". @@ -52,7 +52,6 @@ } // Load the theme - var theTheme = (new FluentTheme()).open( tFolder ); var theTheme = _opts.theme.startsWith('jsonresume-theme') ? new JRSTheme().open(tFolder) : new FluentTheme().open( tFolder ); _opts.themeObj = theTheme; @@ -118,8 +117,8 @@ , fName = PATH.basename(f, '.' + fType) , theFormat; - // If targInfo.fmt.files exists, this theme has an explicit "files" - // section in its theme.json file. + // If targInfo.fmt.files exists, this format is backed by a document. + // Fluent/FRESH themes are handled here. if( targInfo.fmt.files && targInfo.fmt.files.length ) { _log( 'Generating '.useful + @@ -131,19 +130,22 @@ MKDIRP.sync( PATH.dirname( f ) ); // Ensure dest folder exists; theFormat.gen.generate( rez, f, _opts ); } - // Otherwise the theme has no files section + + // Otherwise this is either a) a JSON Resume theme or b) an ad-hoc format + // (JSON, YML, or PNG) that every theme gets "for free". else { _log( 'Generating '.useful + targInfo.fmt.outFormat.toUpperCase().useful.bold + ' resume: '.useful + PATH.relative(process.cwd(), f ).replace(/\\/g,'/').useful.bold); - theFormat = _fmts.filter( - function(fmt) { return fmt.name === targInfo.fmt.outFormat; })[0]; + theFormat = _fmts.filter( function(fmt) { + return fmt.name === targInfo.fmt.outFormat; + })[0]; var outFolder = PATH.dirname( f ); MKDIRP.sync( outFolder ); // Ensure dest folder exists; - // TODO: refactor + // JSON Resume themes have a 'render' method that needs to be called if( theme.render ) { var COPY = require('copy'); var globs = [ /*'**',*/ '*.css', '*.js', '*.png', '*.jpg', '*.gif', '*.bmp' ]; @@ -155,7 +157,7 @@ // } }); - // Prevent JSON Resume theme .js from chattering + // Prevent JSON Resume theme .js from chattering (TODO: redirect IO) var consoleLog = console.log; console.log = function() { }; From a0c356941c20513db7808ca410d39a823194577b Mon Sep 17 00:00:00 2001 From: hacksalot Date: Wed, 30 Dec 2015 12:44:16 -0500 Subject: [PATCH 04/23] Remove unnecessary line. --- src/index.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/index.js b/src/index.js index 48d2503..c48d960 100644 --- a/src/index.js +++ b/src/index.js @@ -74,10 +74,9 @@ function main() { var src = a._.slice(1, splitAt === -1 ? undefined : splitAt ); var dst = splitAt === -1 ? [] : a._.slice( splitAt + 1 ); ( splitAt === -1 ) && src.length > 1 && dst.push( src.pop() ); // Allow omitting TO keyword - var parms = [ src, dst, opts, logMsg ]; // Invoke the action - FCMD.verbs[ verb ].apply( null, parms ); + FCMD.verbs[ verb ].apply( null, [ src, dst, opts, logMsg ] ); } From e3cb949992561b1043452e792c9629ae10a25204 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Wed, 30 Dec 2015 12:59:21 -0500 Subject: [PATCH 05/23] Fix: Exception when HMR is run without params. --- src/core/error-handler.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/core/error-handler.js b/src/core/error-handler.js index 53aa014..1bbc8ce 100644 --- a/src/core/error-handler.js +++ b/src/core/error-handler.js @@ -6,6 +6,9 @@ var HACKMYSTATUS = require('./status-codes') , PKG = require('../../package.json') + , FS = require('fs') + , FCMD = require('../hackmycmd') + , PATH = require('path') , title = ('\n*** HackMyResume v' + PKG.version + ' ***').bold.white; var ErrorHandler = module.exports = { @@ -36,7 +39,7 @@ return (idx === ar.length - 1 ? 'or '.guide : '') + v.toUpperCase().guide; }).join(', '.guide) + ").\n\n".guide + - FS.readFileSync( PATH.join(__dirname, 'use.txt'), 'utf8' ).info.bold; + FS.readFileSync( PATH.resolve(__dirname, '../use.txt'), 'utf8' ).info.bold; break; case HACKMYSTATUS.invalidCommand: From 1bcc2f7d0cc0eb8d3ee204b9498eeaa7af6363aa Mon Sep 17 00:00:00 2001 From: hacksalot Date: Wed, 30 Dec 2015 13:00:30 -0500 Subject: [PATCH 06/23] Add formal support for aliases. new/create and build/generate --- src/hackmycmd.js | 20 ++++++++++++-------- src/index.js | 4 ++-- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/hackmycmd.js b/src/hackmycmd.js index 0082f8e..e4449da 100644 --- a/src/hackmycmd.js +++ b/src/hackmycmd.js @@ -22,15 +22,19 @@ Internal resume generation logic for HackMyResume. /** Internal module interface. Used by FCV Desktop and HMR. */ + var v = { + build: require('./verbs/generate'), + validate: require('./verbs/validate'), + convert: require('./verbs/convert'), + new: require('./verbs/create'), + help: help + } + return { - verbs: { - generate: require('./verbs/generate'), - build: require('./verbs/generate'), - validate: require('./verbs/validate'), - convert: require('./verbs/convert'), - create: require('./verbs/create'), - new: require('./verbs/create'), - help: help + verbs: v, + alias: { + generate: v.build, + create: v.build }, lib: require('./hackmyapi'), options: require('./core/default-options'), diff --git a/src/index.js b/src/index.js index c48d960..d2f99cf 100644 --- a/src/index.js +++ b/src/index.js @@ -55,7 +55,7 @@ function main() { // Get the action to be performed var params = a._.map( function(p){ return p.toLowerCase().trim(); }); var verb = params[0]; - if( !FCMD.verbs[ verb ] ) { + if( !FCMD.verbs[ verb ] && !FCMD.alias[ verb ] ) { logMsg('Invalid command: "'.warn + verb.warn.bold + '"'.warn); return; } @@ -76,7 +76,7 @@ function main() { ( splitAt === -1 ) && src.length > 1 && dst.push( src.pop() ); // Allow omitting TO keyword // Invoke the action - FCMD.verbs[ verb ].apply( null, [ src, dst, opts, logMsg ] ); + (FCMD.verbs[verb] || FCMD.alias[verb]).apply(null, [src, dst, opts, logMsg]); } From 7a1eadb3fc21a0ad822757fde62fec666a4fbbb3 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Wed, 30 Dec 2015 13:12:51 -0500 Subject: [PATCH 07/23] Tweak error messages. Stay away from language like "please specify a valid input resume". The fluentcv fork can use corporate-speak. HackMyResume is more like a gremlin -- feed it, but never after midnight. --- src/core/error-handler.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/core/error-handler.js b/src/core/error-handler.js index 1bbc8ce..a2ade77 100644 --- a/src/core/error-handler.js +++ b/src/core/error-handler.js @@ -29,17 +29,21 @@ break; case HACKMYSTATUS.resumeNotFound: - msg = 'Please '.guide + 'specify a valid input resume'.guide.bold + + msg = 'Please '.guide + 'feed me a resume'.guide.bold + ' in FRESH or JSON Resume format.'.guide; break; case HACKMYSTATUS.missingCommand: - msg = title + "\nPlease ".guide + "specify a command".guide.bold + " (".guide + - Object.keys( FCMD.verbs ).map( function(v, idx, ar) { + msg = title + "\nPlease ".guide + "give me a command".guide.bold + + " (".guide; + + msg += Object.keys( FCMD.verbs ).map( function(v, idx, ar) { return (idx === ar.length - 1 ? 'or '.guide : '') + v.toUpperCase().guide; - }).join(', '.guide) + ").\n\n".guide + - FS.readFileSync( PATH.resolve(__dirname, '../use.txt'), 'utf8' ).info.bold; + }).join(', '.guide) + ").\n\n".guide; + + msg += FS.readFileSync( + PATH.resolve(__dirname, '../use.txt'), 'utf8' ).info.bold; break; case HACKMYSTATUS.invalidCommand: @@ -48,7 +52,7 @@ break; case HACKMYSTATUS.resumeNotFoundAlt: - msg = 'Please '.guide + 'specify a valid input resume'.guide.bold + + msg = 'Please '.guide + 'feed me a resume'.guide.bold + ' in either FRESH or JSON Resume format.'.guide; break; From 39d61c66b92a9fac1b4e4790923cac40a16155b3 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Wed, 30 Dec 2015 13:22:18 -0500 Subject: [PATCH 08/23] Finish Theme --> FreshTheme rename. --- src/core/fresh-theme.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/core/fresh-theme.js b/src/core/fresh-theme.js index b9fe152..1d8fdc2 100644 --- a/src/core/fresh-theme.js +++ b/src/core/fresh-theme.js @@ -1,7 +1,7 @@ /** -Definition of the Theme class. -@license MIT. Copyright (c) 2015 hacksalot / FluentDesk. -@module theme.js +Definition of the FRESHTheme class. +@module fresh-theme.js +@license MIT. See LICENSE.md for details. */ (function() { @@ -18,17 +18,17 @@ Definition of the Theme class. , RECURSIVE_READ_DIR = require('recursive-readdir-sync'); /** - The Theme class is a representation of a HackMyResume theme asset. - @class Theme + The FRESHTheme class is a representation of a HackMyResume theme asset. + @class FRESHTheme */ - function Theme() { + function FRESHTheme() { } /** Open and parse the specified theme. */ - Theme.prototype.open = function( themeFolder ) { + FRESHTheme.prototype.open = function( themeFolder ) { this.folder = themeFolder; @@ -83,14 +83,14 @@ Definition of the Theme class. /** Determine if the theme supports the specified output format. */ - Theme.prototype.hasFormat = function( fmt ) { + FRESHTheme.prototype.hasFormat = function( fmt ) { return _.has( this.formats, fmt ); }; /** Determine if the theme supports the specified output format. */ - Theme.prototype.getFormat = function( fmt ) { + FRESHTheme.prototype.getFormat = function( fmt ) { return this.formats[ fmt ]; }; @@ -285,6 +285,6 @@ Definition of the Theme class. return friendly[val] || val; } - module.exports = Theme; + module.exports = FRESHTheme; }()); From 97c9ba08d002bed74322a2a124dc3a097d443fd5 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Wed, 30 Dec 2015 14:00:09 -0500 Subject: [PATCH 09/23] Fix: Broken HELP command. --- src/hackmycmd.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/hackmycmd.js b/src/hackmycmd.js index e4449da..ad62e4e 100644 --- a/src/hackmycmd.js +++ b/src/hackmycmd.js @@ -8,15 +8,16 @@ Internal resume generation logic for HackMyResume. module.exports = function () { var unused = require('./utils/string') - , PATH = require('path'); + , PATH = require('path') + , FS = require('fs'); /** Display help documentation. */ function help() { - console.log( FS.readFileSync( PATH.join(__dirname, 'use.txt'), 'utf8' ) - .useful.bold ); + var manPage = FS.readFileSync( PATH.join(__dirname, 'use.txt'), 'utf8' ); + console.log( manPage.useful.bold ); } /** @@ -28,7 +29,7 @@ Internal resume generation logic for HackMyResume. convert: require('./verbs/convert'), new: require('./verbs/create'), help: help - } + }; return { verbs: v, From 5e51beddf70d205aa25ca12ed0bbee774626e0b0 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Wed, 30 Dec 2015 14:48:22 -0500 Subject: [PATCH 10/23] Refactor. --- src/gen/template-generator.js | 13 ++- src/verbs/generate.js | 161 ++++++++++++++++++++++------------ 2 files changed, 115 insertions(+), 59 deletions(-) diff --git a/src/gen/template-generator.js b/src/gen/template-generator.js index 20c3ea6..8d3629c 100644 --- a/src/gen/template-generator.js +++ b/src/gen/template-generator.js @@ -1,6 +1,6 @@ /** Definition of the TemplateGenerator class. -@license MIT. Copyright (c) 2015 James Devlin / FluentDesk. +@license MIT. See LICENSE.md for details. @module template-generator.js */ @@ -78,7 +78,14 @@ Definition of the TemplateGenerator class. @method invoke @param rez A FreshResume object. @param opts Generator options. - @returns An array of strings representing generated output files. + @returns An array of objects representing the generated output files. Each + object has this format: + + { + files: [ { info: { }, data: [ ] }, { ... } ], + themeInfo: { } + } + */ invoke: function( rez, opts ) { @@ -183,6 +190,8 @@ Definition of the TemplateGenerator class. }); } + return genInfo; + }, diff --git a/src/verbs/generate.js b/src/verbs/generate.js index f9f1a76..276fdf4 100644 --- a/src/verbs/generate.js +++ b/src/verbs/generate.js @@ -1,19 +1,27 @@ +/** +Implementation of the 'generate' verb for HackMyResume. +@module generate.js +@license MIT. See LICENSE.md for details. +*/ + (function() { var PATH = require('path') , FS = require('fs') - , parsePath = require('parse-filepath') , MD = require('marked') , MKDIRP = require('mkdirp') + , EXTEND = require('../utils/extend') + , parsePath = require('parse-filepath') , _opts = require('../core/default-options') , FluentTheme = require('../core/fresh-theme') , JRSTheme = require('../core/jrs-theme') , ResumeFactory = require('../core/resume-factory') , _ = require('underscore') , _fmts = require('../core/default-formats') - , unused = require('string.prototype.startswith') , _err, _log, rez; + + /** Handle an exception. */ @@ -21,7 +29,7 @@ throw ex; } - module.exports = + /** Given a source resume in FRESH or JRS format, a destination resume path, and a @@ -31,33 +39,18 @@ @param theme Friendly name of the resume theme. Defaults to "modern". @param logger Optional logging override. */ - function generate( src, dst, opts, logger, errHandler ) { + function build( src, dst, opts, logger, errHandler ) { + // Housekeeping... _log = logger || console.log; _err = errHandler || error; - //_opts = extend( true, _opts, opts ); _opts.theme = (opts.theme && opts.theme.toLowerCase().trim())|| 'modern'; _opts.prettify = opts.prettify === true ? _opts.prettify : false; - // Verify the specified theme name/path - var relativeThemeFolder = '../../node_modules/fluent-themes/themes'; - var tFolder = PATH.resolve( __dirname, relativeThemeFolder, _opts.theme); - var exists = require('path-exists').sync; - if( !exists( tFolder ) ) { - tFolder = PATH.resolve( _opts.theme ); - if (!exists( tFolder )) { - throw { fluenterror: 1, data: _opts.theme }; - } - } - - // Load the theme - var theTheme = _opts.theme.startsWith('jsonresume-theme') ? - new JRSTheme().open(tFolder) : new FluentTheme().open( tFolder ); - _opts.themeObj = theTheme; - var numFormats = theTheme.formats ? Object.keys(theTheme.formats).length : 2; - _log( 'Applying '.info + theTheme.name.toUpperCase().infoBold + - (' theme (' + numFormats + ' formats)').info); + // Load the theme... + var tFolder = verify_theme( _opts.theme ); + var theTheme = load_theme( tFolder ); // Load input resumes... if( !src || !src.length ) { throw { fluenterror: 3 }; } @@ -73,41 +66,28 @@ }); msg && _log(msg); - // Expand output resumes... (can't use map() here) - var targets = [], that = this; - ( (dst && dst.length && dst) || ['resume.all'] ).forEach( function(t) { - - var to = PATH.resolve(t), - pa = parsePath(to), - fmat = pa.extname || '.all'; - - targets.push.apply( - targets, fmat === '.all' ? - - Object.keys( theTheme.formats ).map(function(k){ - var z = theTheme.formats[k]; - return { file: to.replace(/all$/g,z.outFormat), fmt: z }; - }) : - - [{ file: to, fmt: theTheme.getFormat( fmat.slice(1) ) }]); - - }); + // Expand output resumes... + var targets = expand( dst, theTheme ); // Run the transformation! - var finished = targets.map( function(t) { return single(t, theTheme); }); + var finished = targets.map( function(t) { + return EXTEND(true, t, { markup: single(t, theTheme) }); + }); // Don't send the client back empty-handed return { sheet: rez, targets: targets, processed: finished }; - }; + } + + /** - Generate a single resume of a specific format. - @param f Full path to the destination resume to generate, for example, - "/foo/bar/resume.pdf" or "c:\foo\bar\resume.txt". + Generate a single resume of a specific format. TODO: Refactor. + @param targInfo Information for the target resume. + @param theme A FRESHTheme or JRSTheme object. */ function single( targInfo, theme ) { - function MDIN(txt) { + function MDIN(txt) { // TODO: Move this return MD(txt || '' ).replace(/^\s*

|<\/p>\s*$/gi, ''); } @@ -117,14 +97,13 @@ , fName = PATH.basename(f, '.' + fType) , theFormat; + _log( 'Generating '.useful + + targInfo.fmt.outFormat.toUpperCase().useful.bold + + ' resume: '.useful + PATH.relative(process.cwd(), f ).useful.bold ); + // If targInfo.fmt.files exists, this format is backed by a document. // Fluent/FRESH themes are handled here. if( targInfo.fmt.files && targInfo.fmt.files.length ) { - - _log( 'Generating '.useful + - targInfo.fmt.outFormat.toUpperCase().useful.bold + - ' resume: '.useful + PATH.relative(process.cwd(), f ).replace(/\\/g,'/').useful.bold); - theFormat = _fmts.filter( function(fmt) { return fmt.name === targInfo.fmt.outFormat; })[0]; MKDIRP.sync( PATH.dirname( f ) ); // Ensure dest folder exists; @@ -134,9 +113,6 @@ // Otherwise this is either a) a JSON Resume theme or b) an ad-hoc format // (JSON, YML, or PNG) that every theme gets "for free". else { - _log( 'Generating '.useful + - targInfo.fmt.outFormat.toUpperCase().useful.bold + - ' resume: '.useful + PATH.relative(process.cwd(), f ).replace(/\\/g,'/').useful.bold); theFormat = _fmts.filter( function(fmt) { return fmt.name === targInfo.fmt.outFormat; @@ -175,9 +151,12 @@ // Save the file FS.writeFileSync( f, rezHtml ); + + // Return markup to the client + return rezHtml; } else { - theFormat.gen.generate( rez, f, _opts ); + return theFormat.gen.generate( rez, f, _opts ); } } } @@ -186,4 +165,72 @@ } } + + /** + Expand output files. + */ + function expand( dst, theTheme ) { + var targets = []; + // (can't use map() here). + ( (dst && dst.length && dst) || ['resume.all'] ).forEach( function(t) { + + var to = PATH.resolve(t), + pa = parsePath(to), + fmat = pa.extname || '.all'; + + targets.push.apply( + targets, fmat === '.all' ? + + Object.keys( theTheme.formats ).map(function(k){ + var z = theTheme.formats[k]; + return { file: to.replace(/all$/g,z.outFormat), fmt: z }; + }) : + + [{ file: to, fmt: theTheme.getFormat( fmat.slice(1) ) }]); + + }); + return targets; + } + + + /** + Verify the specified theme name/path. + */ + function verify_theme( themeNameOrPath ) { + var tFolder = PATH.resolve( + __dirname, + '../../node_modules/fluent-themes/themes', + themeNameOrPath + ); + var exists = require('path-exists').sync; + if( !exists( tFolder ) ) { + tFolder = PATH.resolve( themeNameOrPath ); + if( !exists( tFolder ) ) { + throw { fluenterror: 1, data: _opts.theme }; + } + } + return tFolder; + } + + + + /** + Load the specified theme. + */ + function load_theme( tFolder ) { + var theTheme = _opts.theme.indexOf('jsonresume-theme-') > -1 ? + new JRSTheme().open(tFolder) : new FluentTheme().open( tFolder ); + _opts.themeObj = theTheme; + var numFormats = Object.keys(theTheme.formats).length; + _log( 'Applying '.info + theTheme.name.toUpperCase().infoBold + + (' theme (' + numFormats + ' formats)').info); + return theTheme; + } + + + + module.exports = build; + + + }()); From ccadb0416f8d82fbfe8e117acfd4a125494adf4c Mon Sep 17 00:00:00 2001 From: hacksalot Date: Wed, 30 Dec 2015 15:03:26 -0500 Subject: [PATCH 11/23] Move freebie formats out of theme class. --- src/core/fresh-theme.js | 4 ---- src/verbs/generate.js | 16 ++++++++++++++++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/core/fresh-theme.js b/src/core/fresh-theme.js index 1d8fdc2..bf2b414 100644 --- a/src/core/fresh-theme.js +++ b/src/core/fresh-theme.js @@ -67,10 +67,6 @@ Definition of the FRESHTheme class. formatsHash = loadImplicit.call( this ); } - // Add freebie formats every theme gets - formatsHash.json = { title: 'json', outFormat: 'json', pre: 'json', ext: 'json', path: null, data: null }; - formatsHash.yml = { title: 'yaml', outFormat: 'yml', pre: 'yml', ext: 'yml', path: null, data: null }; - // Cache this.formats = formatsHash; diff --git a/src/verbs/generate.js b/src/verbs/generate.js index 276fdf4..c0cd8da 100644 --- a/src/verbs/generate.js +++ b/src/verbs/generate.js @@ -218,9 +218,25 @@ Implementation of the 'generate' verb for HackMyResume. Load the specified theme. */ function load_theme( tFolder ) { + + // Create a FRESH or JRS theme object var theTheme = _opts.theme.indexOf('jsonresume-theme-') > -1 ? new JRSTheme().open(tFolder) : new FluentTheme().open( tFolder ); + + // Add freebie formats every theme gets + theTheme.formats.json = theTheme.formats.json || { + title: 'json', outFormat: 'json', pre: 'json', + ext: 'json', path: null, data: null + }; + theTheme.formats.yml = theTheme.formats.yml || { + title: 'yaml', outFormat: 'yml', pre: 'yml', + ext: 'yml', path: null, data: null + }; + + // Cache the theme object _opts.themeObj = theTheme; + + // Output a message TODO: core should not log var numFormats = Object.keys(theTheme.formats).length; _log( 'Applying '.info + theTheme.name.toUpperCase().infoBold + (' theme (' + numFormats + ' formats)').info); From 2ff912e687d7f446b1c506d72c677a000cebee25 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Wed, 30 Dec 2015 15:11:18 -0500 Subject: [PATCH 12/23] Scrub. --- src/core/jrs-theme.js | 43 +++++++++++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/src/core/jrs-theme.js b/src/core/jrs-theme.js index 26904c2..ccf3300 100644 --- a/src/core/jrs-theme.js +++ b/src/core/jrs-theme.js @@ -16,32 +16,37 @@ Definition of the JRSTheme class. /** - The JRSTheme class is a representation of a JSON Resume theme asset. + The JRSTheme class is a representation of a JSON Resume + theme asset. @class JRSTheme */ function JRSTheme() { } + + /** Open and parse the specified theme. + @method open */ - JRSTheme.prototype.open = function( themeFolder ) { + JRSTheme.prototype.open = function( thFolder ) { - this.folder = themeFolder; + this.folder = thFolder; - // Open the [theme-name].json file; should have the same name as folder - var pathInfo = parsePath( themeFolder ); + // Open the [theme-name].json file; should have the same + // name as folder + var pathInfo = parsePath( thFolder ); // Open and parse the theme's package.json file. - var packageJsonPath = PATH.join(themeFolder, 'package.json'); - if( pathExists( packageJsonPath ) ) { - var themePack = require( themeFolder ); - var themePkgJson = require( packageJsonPath ); - this.name = themePkgJson.name; - this.render = (themePack && themePack.render) || undefined; + var pkgJsonPath = PATH.join( thFolder, 'package.json' ); + if( pathExists( pkgJsonPath )) { + var thApi = require( thFolder ) + , thPkg = require( pkgJsonPath ); + this.name = thPkg.name; + this.render = (thApi && thApi.render) || undefined; this.formats = { - html: { title: 'html', outFormat: 'html', ext: 'html' } + html: { title:'html', outFormat:'html', ext:'html' } }; } else { @@ -51,20 +56,30 @@ Definition of the JRSTheme class. return this; }; + + /** - Determine if the theme supports the specified output format. + Determine if the theme supports the output format. + @method hasFormat */ JRSTheme.prototype.hasFormat = function( fmt ) { return _.has( this.formats, fmt ); }; + + /** - Determine if the theme supports the specified output format. + Return the requested output format. + @method getFormat */ JRSTheme.prototype.getFormat = function( fmt ) { return this.formats[ fmt ]; }; + + module.exports = JRSTheme; + + }()); From 72de1bbd335cb2176eb7f808bf22dc04af47a5f1 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Wed, 30 Dec 2015 15:21:58 -0500 Subject: [PATCH 13/23] Scrub. --- src/core/fresh-theme.js | 91 +++++++++++++++++++++++++++-------------- src/core/jrs-theme.js | 2 +- 2 files changed, 61 insertions(+), 32 deletions(-) diff --git a/src/core/fresh-theme.js b/src/core/fresh-theme.js index bf2b414..bbb816c 100644 --- a/src/core/fresh-theme.js +++ b/src/core/fresh-theme.js @@ -6,6 +6,8 @@ Definition of the FRESHTheme class. (function() { + + var FS = require('fs') , extend = require('../utils/extend') , validator = require('is-my-json-valid') @@ -15,16 +17,21 @@ Definition of the FRESHTheme class. , pathExists = require('path-exists').sync , EXTEND = require('../utils/extend') , moment = require('moment') - , RECURSIVE_READ_DIR = require('recursive-readdir-sync'); + , READFILES = require('recursive-readdir-sync'); + + /** - The FRESHTheme class is a representation of a HackMyResume theme asset. + The FRESHTheme class is a representation of a FRESH theme + asset. See also: JRSTheme. @class FRESHTheme */ function FRESHTheme() { } + + /** Open and parse the specified theme. */ @@ -38,18 +45,7 @@ Definition of the FRESHTheme class. // Set up a formats hash for the theme var formatsHash = { }; - // See if the theme has a package.json. If so, load it. - var packageJsonPath = PATH.join(themeFolder, 'package.json'); - if( pathExists( packageJsonPath ) ) { - var themePack = require( themeFolder ); - var themePkgJson = require( packageJsonPath ); - this.name = themePkgJson.name; - this.render = (themePack && themePack.render) || undefined; - this.formats = { html: { title: 'html', outFormat: 'html', ext: 'html', path: null, data: null } }; - return this; - } - - // Otherwise, do a full theme load + // Load the theme var themeFile = PATH.join( themeFolder, pathInfo.basename + '.json' ); var themeInfo = JSON.parse( FS.readFileSync( themeFile, 'utf8' ) ); var that = this; @@ -76,6 +72,8 @@ Definition of the FRESHTheme class. return this; }; + + /** Determine if the theme supports the specified output format. */ @@ -83,6 +81,8 @@ Definition of the FRESHTheme class. return _.has( this.formats, fmt ); }; + + /** Determine if the theme supports the specified output format. */ @@ -90,6 +90,11 @@ Definition of the FRESHTheme class. return this.formats[ fmt ]; }; + + /** + Load the theme implicitly, by scanning the theme folder for + files. TODO: Refactor duplicated code with loadExplicit. + */ function loadImplicit() { // Set up a hash of formats supported by this theme. @@ -103,7 +108,7 @@ Definition of the FRESHTheme class. // 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. - var fmts = RECURSIVE_READ_DIR( tplFolder ).map( function( absPath ) { + var fmts = READFILES(tplFolder).map( function(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 @@ -131,7 +136,7 @@ Definition of the FRESHTheme class. // compact-[outputformat].[extension], for ex, compact-pdf.html. if( !outFmt ) { var idx = pathInfo.name.lastIndexOf('-'); - outFmt = ( idx === -1 ) ? pathInfo.name : pathInfo.name.substr( idx + 1 ); + outFmt = (idx === -1) ? pathInfo.name : pathInfo.name.substr(idx + 1); isMajor = true; } @@ -161,9 +166,13 @@ Definition of the FRESHTheme class. }); // Now, get all the CSS files... - (this.cssFiles = fmts.filter(function( fmt ){ return fmt && (fmt.ext === 'css'); })) + (this.cssFiles = fmts.filter(function( fmt ){ + return fmt && (fmt.ext === 'css'); + })) + + // For each CSS file, get its corresponding HTML file .forEach(function( cssf ) { - // For each CSS file, get its corresponding HTML file + var idx = _.findIndex(fmts, function( fmt ) { return fmt && fmt.pre === cssf.pre && fmt.ext === 'html'; }); @@ -180,16 +189,19 @@ Definition of the FRESHTheme class. return formatsHash; } + + + /** + Load the theme explicitly, by following the 'formats' hash + in the theme's JSON settings file. + */ function loadExplicit() { - var that = this; - // Set up a hash of formats supported by this theme. + // Housekeeping var formatsHash = { }; - - // Establish the base theme folder var tplFolder = PATH.join( this.folder, 'src' ); - var act = null; + var 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 @@ -200,13 +212,16 @@ Definition of the FRESHTheme class. // If this file is mentioned in the theme's JSON file under "transforms" var pathInfo = parsePath(absPath); var absPathSafe = absPath.trim().toLowerCase(); - var outFmt = _.find( Object.keys( that.formats ), function( fmtKey ) { - var fmtVal = that.formats[ fmtKey ]; - return _.some( fmtVal.transform, function( fpath ) { - var absPathB = PATH.join( that.folder, fpath ).trim().toLowerCase(); - return absPathB === absPathSafe; + var outFmt = _.find( + Object.keys( that.formats ), + function( fmtKey ) { + var fmtVal = that.formats[ fmtKey ]; + return _.some( fmtVal.transform, function(fpath) { + var absPathB = PATH.join( that.folder, fpath ) + .trim().toLowerCase(); + return absPathB === absPathSafe; + }); }); - }); if( outFmt ) { act = 'transform'; } @@ -227,7 +242,7 @@ Definition of the FRESHTheme class. // compact-[outputformat].[extension], for ex, compact-pdf.html. if( !outFmt ) { var idx = pathInfo.name.lastIndexOf('-'); - outFmt = ( idx === -1 ) ? pathInfo.name : pathInfo.name.substr( idx + 1 ); + outFmt = (idx === -1) ? pathInfo.name : pathInfo.name.substr(idx + 1); } // We should have a valid output format now. @@ -257,7 +272,11 @@ Definition of the FRESHTheme class. }); // Now, get all the CSS files... - (this.cssFiles = fmts.filter(function( fmt ){ return fmt.ext === 'css'; })) + (this.cssFiles = fmts.filter(function( fmt ){ + return fmt.ext === 'css'; + })) + + // For each CSS file, get its corresponding HTML file .forEach(function( cssf ) { // For each CSS file, get its corresponding HTML file var idx = _.findIndex(fmts, function( fmt ) { @@ -275,12 +294,22 @@ Definition of the FRESHTheme class. return formatsHash; } + + + /** + Return a more friendly name for certain formats. + TODO: Refactor + */ function friendlyName( val ) { val = val.trim().toLowerCase(); var friendly = { yml: 'yaml', md: 'markdown', txt: 'text' }; return friendly[val] || val; } + + module.exports = FRESHTheme; + + }()); diff --git a/src/core/jrs-theme.js b/src/core/jrs-theme.js index ccf3300..4f2942a 100644 --- a/src/core/jrs-theme.js +++ b/src/core/jrs-theme.js @@ -17,7 +17,7 @@ Definition of the JRSTheme class. /** The JRSTheme class is a representation of a JSON Resume - theme asset. + theme asset. See also: FRESHTheme. @class JRSTheme */ function JRSTheme() { From 22554c61c5b749817acfd00b9dde9772e5b88f8d Mon Sep 17 00:00:00 2001 From: hacksalot Date: Wed, 30 Dec 2015 18:18:11 -0500 Subject: [PATCH 14/23] Rename and bump fluent-themes dependency. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8eb6ba2..b3719d6 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "dependencies": { "colors": "^1.1.2", "copy": "^0.1.3", - "fluent-themes": "~0.8.0-beta", + "fresh-themes": "~0.9.0-beta", "fresca": "~0.2.2", "fs-extra": "^0.24.0", "handlebars": "^4.0.5", From d4e0a0fa055c583e3a1f1b14582ddd23cce32df7 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Wed, 30 Dec 2015 18:19:00 -0500 Subject: [PATCH 15/23] Add {{styleSheet}} helper (placeholder). --- src/eng/generic-helpers.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/eng/generic-helpers.js b/src/eng/generic-helpers.js index ab9c1f6..26c03dc 100644 --- a/src/eng/generic-helpers.js +++ b/src/eng/generic-helpers.js @@ -1,6 +1,6 @@ /** -Generic template helper definitions for FluentCV. -@license MIT. Copyright (c) 2015 James Devlin / FluentDesk. +Generic template helper definitions for HackMyResume / FluentCV. +@license MIT. See LICENSE.md for details. @module generic-helpers.js */ @@ -114,6 +114,14 @@ Generic template helper definitions for FluentCV. if (lhs || rhs) return options.fn(this); }, + /** + Conditional stylesheet link. Either display the link or embed the stylesheet + via tag. + */ + styleSheet: function( file ) { + return ''; + }, + /** Perform a generic comparison. See: http://doginthehat.com.au/2012/02/comparison-block-helper-for-handlebars-templates From d9010470439108df66b89fe470198e89ef87746d Mon Sep 17 00:00:00 2001 From: hacksalot Date: Wed, 30 Dec 2015 18:50:58 -0500 Subject: [PATCH 16/23] Update fluent-themes --> fresh-themes. --- README.md | 2 +- src/gen/template-generator.js | 2 +- src/verbs/generate.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e58b005..4e0aa3b 100644 --- a/README.md +++ b/README.md @@ -327,5 +327,5 @@ MIT. Go crazy. See [LICENSE.md][1] for details. [travis-image]: https://img.shields.io/travis/palomajs/paloma.svg?style=flat-square [travis-url]: https://travis-ci.org/hacksalot/HackMyResume [contribute]: CONTRIBUTING.md -[fresh-themes]: https://github.com/fluentdesk/fluent-themes +[fresh-themes]: https://github.com/fluentdesk/fresh-themes [jrst]: https://www.npmjs.com/search?q=jsonresume-theme diff --git a/src/gen/template-generator.js b/src/gen/template-generator.js index 8d3629c..257f477 100644 --- a/src/gen/template-generator.js +++ b/src/gen/template-generator.js @@ -233,7 +233,7 @@ Definition of the TemplateGenerator class. // Verify the specified theme name/path var tFolder = PATH.join( - parsePath( require.resolve('fluent-themes') ).dirname, + parsePath( require.resolve('fresh-themes') ).dirname, this.opts.theme ); diff --git a/src/verbs/generate.js b/src/verbs/generate.js index c0cd8da..e4f06c9 100644 --- a/src/verbs/generate.js +++ b/src/verbs/generate.js @@ -199,7 +199,7 @@ Implementation of the 'generate' verb for HackMyResume. function verify_theme( themeNameOrPath ) { var tFolder = PATH.resolve( __dirname, - '../../node_modules/fluent-themes/themes', + '../../node_modules/fresh-themes/themes', themeNameOrPath ); var exists = require('path-exists').sync; From 558a321fe80bc39d02ce71653ef157c5a927220e Mon Sep 17 00:00:00 2001 From: hacksalot Date: Wed, 30 Dec 2015 18:52:41 -0500 Subject: [PATCH 17/23] Refactor generator logic. --- package.json | 2 +- src/gen/html-png-generator.js | 31 +++++++------ src/verbs/generate.js | 84 ++++++++++++++++++++++++----------- 3 files changed, 77 insertions(+), 40 deletions(-) diff --git a/package.json b/package.json index b3719d6..79c689c 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "dependencies": { "colors": "^1.1.2", "copy": "^0.1.3", - "fresh-themes": "~0.9.0-beta", + "fresh-themes": "~0.9.2-beta", "fresca": "~0.2.2", "fs-extra": "^0.24.0", "handlebars": "^4.0.5", diff --git a/src/gen/html-png-generator.js b/src/gen/html-png-generator.js index 4d9b5b9..2ec793e 100644 --- a/src/gen/html-png-generator.js +++ b/src/gen/html-png-generator.js @@ -1,6 +1,6 @@ /** Definition of the HtmlPngGenerator class. -@license MIT. Copyright (c) 2015 James Devlin / FluentDesk. +@license MIT. See LICENSE.MD for details. @module html-png-generator.js */ @@ -11,7 +11,7 @@ Definition of the HtmlPngGenerator class. , HTML = require( 'html' ); /** - An HTML-based PDF resume generator for HackMyResume. + An HTML-based PNG resume generator for HackMyResume. */ var HtmlPngGenerator = module.exports = TemplateGenerator.extend({ @@ -19,24 +19,29 @@ Definition of the HtmlPngGenerator class. this._super( 'png', 'html' ); }, - /** - Generate the binary PDF. - */ - onBeforeSave: function( info ) { - png( info.mk, info.outputFile ); - return null; // halt further processing + invoke: function( rez, themeMarkup, cssInfo, opts ) { + //return YAML.stringify( JSON.parse( rez.stringify() ), Infinity, 2 ); + }, + + generate: function( rez, f, opts ) { + var htmlResults = opts.targets.filter(function(t){ + return t.fmt.outFormat === 'html'; + }); + var htmlFile = htmlResults[0].final.files.filter(function(fl){ + return fl.info.ext === 'html'; + }); + png(htmlFile[0].data, f); } }); /** - Generate a PDF from HTML. + Generate a PNG from HTML. */ function png( markup, fOut ) { - - require('webshot')( markup , { encoding: 'binary', siteType: 'html' } ) - .pipe( FS.createWriteStream( fOut ) ); - + // require('webshot')( markup , { encoding: 'binary', siteType: 'html' } ) + // .pipe( FS.createWriteStream( fOut ) ); + require('webshot')( markup , fOut, { siteType: 'html' }, function(err) { } ); } }()); diff --git a/src/verbs/generate.js b/src/verbs/generate.js index e4f06c9..eecdc9a 100644 --- a/src/verbs/generate.js +++ b/src/verbs/generate.js @@ -6,6 +6,8 @@ Implementation of the 'generate' verb for HackMyResume. (function() { + + var PATH = require('path') , FS = require('fs') , MD = require('marked') @@ -70,22 +72,23 @@ Implementation of the 'generate' verb for HackMyResume. var targets = expand( dst, theTheme ); // Run the transformation! - var finished = targets.map( function(t) { - return EXTEND(true, t, { markup: single(t, theTheme) }); + targets.forEach( function(t) { + t.final = single( t, theTheme, targets ); }); // Don't send the client back empty-handed - return { sheet: rez, targets: targets, processed: finished }; + return { sheet: rez, targets: targets, processed: targets }; } /** - Generate a single resume of a specific format. TODO: Refactor. + Generate a single target resume such as "out/rez.html" or "out/rez.doc". @param targInfo Information for the target resume. @param theme A FRESHTheme or JRSTheme object. + @returns */ - function single( targInfo, theme ) { + function single( targInfo, theme, finished ) { function MDIN(txt) { // TODO: Move this return MD(txt || '' ).replace(/^\s*

|<\/p>\s*$/gi, ''); @@ -107,7 +110,8 @@ Implementation of the 'generate' verb for HackMyResume. theFormat = _fmts.filter( function(fmt) { return fmt.name === targInfo.fmt.outFormat; })[0]; MKDIRP.sync( PATH.dirname( f ) ); // Ensure dest folder exists; - theFormat.gen.generate( rez, f, _opts ); + _opts.targets = finished; + return theFormat.gen.generate( rez, f, _opts ); } // Otherwise this is either a) a JSON Resume theme or b) an ad-hoc format @@ -166,29 +170,67 @@ Implementation of the 'generate' verb for HackMyResume. } + /** - Expand output files. + Expand output files. For example, "foo.all" should be expanded to + ["foo.html", "foo.doc", "foo.pdf", "etc"]. + @param dst An array of output files as specified by the user. + @param theTheme A FRESHTheme or JRSTheme object. */ function expand( dst, theTheme ) { - var targets = []; - // (can't use map() here). - ( (dst && dst.length && dst) || ['resume.all'] ).forEach( function(t) { - var to = PATH.resolve(t), - pa = parsePath(to), - fmat = pa.extname || '.all'; + // Add freebie formats (JSON, YAML, PNG) every theme gets... + // Add HTML-driven PNG only if the theme has an HTML format. + theTheme.formats.json = theTheme.formats.json || { + freebie: true, title: 'json', outFormat: 'json', pre: 'json', + ext: 'json', path: null, data: null + }; + theTheme.formats.yml = theTheme.formats.yml || { + freebie: true, title: 'yaml', outFormat: 'yml', pre: 'yml', + ext: 'yml', path: null, data: null + }; + theTheme.formats.png = theTheme.formats.png || + ( theTheme.formats.html && { + freebie: true, title: 'png', outFormat: 'png', + ext: 'yml', path: null, data: null + }); + + // Set up the destination collection. It's either the array of files passed + // by the user or 'out/resume.all' if no targets were specified. + var destColl = (dst && dst.length && dst) || + [PATH.normalize('out/resume.all')] + + // Assemble an array of expanded target files... (can't use map() here) + var targets = []; + destColl.forEach( function(t) { + + var to = PATH.resolve(t), pa = parsePath(to),fmat = pa.extname || '.all'; + + var explicitFormats = _.omit( theTheme.formats, function(val) { + return !val.freebie; + }); + var implicitFormats = _.omit( theTheme.formats, function(val) { + return val.freebie; + }); targets.push.apply( targets, fmat === '.all' ? - - Object.keys( theTheme.formats ).map(function(k){ + Object.keys( implicitFormats ).map( function( k ) { var z = theTheme.formats[k]; - return { file: to.replace(/all$/g,z.outFormat), fmt: z }; + return { file: to.replace( /all$/g, z.outFormat ), fmt: z }; }) : + [{ file: to, fmt: theTheme.getFormat( fmat.slice(1) ) }]); + targets.push.apply( + targets, fmat === '.all' ? + Object.keys( explicitFormats ).map( function( k ) { + var z = theTheme.formats[k]; + return { file: to.replace( /all$/g, z.outFormat ), fmt: z }; + }) : [{ file: to, fmt: theTheme.getFormat( fmat.slice(1) ) }]); }); + return targets; } @@ -223,16 +265,6 @@ Implementation of the 'generate' verb for HackMyResume. var theTheme = _opts.theme.indexOf('jsonresume-theme-') > -1 ? new JRSTheme().open(tFolder) : new FluentTheme().open( tFolder ); - // Add freebie formats every theme gets - theTheme.formats.json = theTheme.formats.json || { - title: 'json', outFormat: 'json', pre: 'json', - ext: 'json', path: null, data: null - }; - theTheme.formats.yml = theTheme.formats.yml || { - title: 'yaml', outFormat: 'yml', pre: 'yml', - ext: 'yml', path: null, data: null - }; - // Cache the theme object _opts.themeObj = theTheme; From a280d8acb22b089b0afa708142178bb7ddd7d86f Mon Sep 17 00:00:00 2001 From: hacksalot Date: Wed, 30 Dec 2015 19:45:50 -0500 Subject: [PATCH 18/23] Support CSS embedding vs. linking. --- src/eng/generic-helpers.js | 6 ++++-- src/eng/handlebars-generator.js | 1 + src/index.js | 3 ++- src/verbs/generate.js | 1 + 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/eng/generic-helpers.js b/src/eng/generic-helpers.js index 26c03dc..a930933 100644 --- a/src/eng/generic-helpers.js +++ b/src/eng/generic-helpers.js @@ -118,8 +118,10 @@ Generic template helper definitions for HackMyResume / FluentCV. Conditional stylesheet link. Either display the link or embed the stylesheet via tag. */ - styleSheet: function( file ) { - return ''; + styleSheet: function( file, options ) { + return ( this.opts.css === 'link') ? + '' : + ''; }, /** diff --git a/src/eng/handlebars-generator.js b/src/eng/handlebars-generator.js index 71dbf58..27a8f7d 100644 --- a/src/eng/handlebars-generator.js +++ b/src/eng/handlebars-generator.js @@ -40,6 +40,7 @@ Definition of the HandlebarsGenerator class. RAW: json, filt: opts.filters, cssInfo: cssInfo, + opts: opts, headFragment: opts.headFragment || '' }); diff --git a/src/index.js b/src/index.js index d2f99cf..9352d49 100644 --- a/src/index.js +++ b/src/index.js @@ -91,6 +91,7 @@ function getOpts( args ) { theme: args.t || 'modern', format: args.f || 'FRESH', prettify: !noPretty, - silent: args.s || args.silent + silent: args.s || args.silent, + css: args.css || 'embed' }; } diff --git a/src/verbs/generate.js b/src/verbs/generate.js index eecdc9a..d3819a7 100644 --- a/src/verbs/generate.js +++ b/src/verbs/generate.js @@ -49,6 +49,7 @@ Implementation of the 'generate' verb for HackMyResume. //_opts = extend( true, _opts, opts ); _opts.theme = (opts.theme && opts.theme.toLowerCase().trim())|| 'modern'; _opts.prettify = opts.prettify === true ? _opts.prettify : false; + _opts.css = opts.css; // Load the theme... var tFolder = verify_theme( _opts.theme ); From 55943bf49a02317373475ac281e2c8f3cc92e1f9 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Wed, 30 Dec 2015 20:09:39 -0500 Subject: [PATCH 19/23] Fix missing semicolon. --- src/verbs/generate.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/verbs/generate.js b/src/verbs/generate.js index d3819a7..c8984d1 100644 --- a/src/verbs/generate.js +++ b/src/verbs/generate.js @@ -199,7 +199,7 @@ Implementation of the 'generate' verb for HackMyResume. // Set up the destination collection. It's either the array of files passed // by the user or 'out/resume.all' if no targets were specified. var destColl = (dst && dst.length && dst) || - [PATH.normalize('out/resume.all')] + [PATH.normalize('out/resume.all')]; // Assemble an array of expanded target files... (can't use map() here) var targets = []; From 76cafa42491b7157d982d4f37209aeecede1d0f0 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Wed, 30 Dec 2015 20:10:14 -0500 Subject: [PATCH 20/23] Fix reference error in explicit themes. --- src/core/fresh-theme.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/fresh-theme.js b/src/core/fresh-theme.js index bbb816c..dc01975 100644 --- a/src/core/fresh-theme.js +++ b/src/core/fresh-theme.js @@ -206,7 +206,7 @@ Definition of the FRESHTheme class. // 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. - var fmts = RECURSIVE_READ_DIR( tplFolder ).map( function( absPath ) { + var fmts = READFILES( tplFolder ).map( function( absPath ) { act = null; // If this file is mentioned in the theme's JSON file under "transforms" @@ -275,7 +275,7 @@ Definition of the FRESHTheme class. (this.cssFiles = fmts.filter(function( fmt ){ return fmt.ext === 'css'; })) - + // For each CSS file, get its corresponding HTML file .forEach(function( cssf ) { // For each CSS file, get its corresponding HTML file From 3acf648eb486218dd3fd8a46cdfa81c85363525e Mon Sep 17 00:00:00 2001 From: hacksalot Date: Wed, 30 Dec 2015 20:11:09 -0500 Subject: [PATCH 21/23] Expose helpers to Underscore engine. Get the same set of helpers working for Underscore and Handlebars engines. Needs refactoring. --- src/eng/underscore-generator.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/eng/underscore-generator.js b/src/eng/underscore-generator.js index 0a9be84..eaa8eed 100644 --- a/src/eng/underscore-generator.js +++ b/src/eng/underscore-generator.js @@ -11,7 +11,6 @@ Definition of the UnderscoreGenerator class. var _ = require('underscore'); - /** Perform template-based resume generation using Underscore.js. @class UnderscoreGenerator @@ -32,6 +31,10 @@ Definition of the UnderscoreGenerator class. // Strip {# comments #} jst = jst.replace( delims.comment, ''); + var helpers = require('./generic-helpers'); + helpers.opts = opts; + helpers.cssInfo = cssInfo; + // Compile and run the template. TODO: avoid unnecessary recompiles. var compiled = _.template(jst); var ret = compiled({ @@ -40,13 +43,15 @@ Definition of the UnderscoreGenerator class. XML: require('xml-escape'), RAW: json, cssInfo: cssInfo, - headFragment: opts.headFragment || '' + headFragment: opts.headFragment || '', + opts: opts, + h: helpers }); return ret; } }; - + }()); From 3cf24cfb402a2cbaf977e5ee0a762af944159890 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Wed, 30 Dec 2015 20:11:21 -0500 Subject: [PATCH 22/23] Fix PNG generation glitch. --- src/verbs/generate.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/verbs/generate.js b/src/verbs/generate.js index c8984d1..55011e3 100644 --- a/src/verbs/generate.js +++ b/src/verbs/generate.js @@ -190,11 +190,12 @@ Implementation of the 'generate' verb for HackMyResume. freebie: true, title: 'yaml', outFormat: 'yml', pre: 'yml', ext: 'yml', path: null, data: null }; - theTheme.formats.png = theTheme.formats.png || - ( theTheme.formats.html && { - freebie: true, title: 'png', outFormat: 'png', - ext: 'yml', path: null, data: null - }); + if( theTheme.formats.html && !theTheme.formats.png ) { + theTheme.formats.png = { + freebie: true, title: 'png', outFormat: 'png', + ext: 'yml', path: null, data: null + }; + } // Set up the destination collection. It's either the array of files passed // by the user or 'out/resume.all' if no targets were specified. @@ -207,7 +208,7 @@ Implementation of the 'generate' verb for HackMyResume. var to = PATH.resolve(t), pa = parsePath(to),fmat = pa.extname || '.all'; - var explicitFormats = _.omit( theTheme.formats, function(val) { + var explicitFormats = _.omit( theTheme.formats, function(val, key) { return !val.freebie; }); var implicitFormats = _.omit( theTheme.formats, function(val) { From df27924ac20d254307aed919408fbafde4397f74 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Wed, 30 Dec 2015 21:07:28 -0500 Subject: [PATCH 23/23] Add Johnny Trouble to tests. --- package.json | 3 +- test/test-fresh-sheet.js | 89 +++++++++++++++++++--------------------- test/test-themes.js | 47 ++++++++++----------- 3 files changed, 65 insertions(+), 74 deletions(-) diff --git a/package.json b/package.json index 79c689c..bef8d59 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "dependencies": { "colors": "^1.1.2", "copy": "^0.1.3", - "fresh-themes": "~0.9.2-beta", + "fresh-themes": "~0.9.3-beta", "fresca": "~0.2.2", "fs-extra": "^0.24.0", "handlebars": "^4.0.5", @@ -79,6 +79,7 @@ "grunt-contrib-yuidoc": "^0.10.0", "grunt-simple-mocha": "*", "jane-q-fullstacker": "fluentdesk/jane-q-fullstacker", + "johnny-trouble-resume": "fluentdesk/johnny-trouble-resume", "jsonresume-theme-boilerplate": "^0.1.2", "jsonresume-theme-classy": "^1.0.9", "jsonresume-theme-modern": "0.0.18", diff --git a/test/test-fresh-sheet.js b/test/test-fresh-sheet.js index 4d28559..eb2ec7c 100644 --- a/test/test-fresh-sheet.js +++ b/test/test-fresh-sheet.js @@ -9,59 +9,54 @@ var chai = require('chai') chai.config.includeStack = false; -describe('jane-doe.json (FRESH)', function () { +function testResume(opts) { - var _sheet; + describe( opts.title + ' (FRESH)', function () { - it('should open without throwing an exception', function () { - function tryOpen() { - _sheet = new FRESHResume().open( - 'node_modules/jane-q-fullstacker/resume/jane-resume.json' ); - } - tryOpen.should.not.Throw(); - }); + var _sheet; - it('should have one or more of each section', function() { - expect( - //(_sheet.basics) && - _sheet.name && _sheet.info && _sheet.location && _sheet.contact && - (_sheet.employment.history && _sheet.employment.history.length > 0) && - (_sheet.skills && _sheet.skills.list.length > 0) && - (_sheet.education.history && _sheet.education.history.length > 0) && - (_sheet.service.history && _sheet.service.history.length > 0) && - (_sheet.writing && _sheet.writing.length > 0) && - (_sheet.recognition && _sheet.recognition.length > 0) && - (_sheet.samples && _sheet.samples.length > 0) && - (_sheet.references && _sheet.references.length > 0) && - (_sheet.interests && _sheet.interests.length > 0) - ).to.equal( true ); - }); + it('should open without throwing an exception', function () { + function tryOpen() { + _sheet = new FRESHResume().open( opts.path ); + } + tryOpen.should.not.Throw(); + }); - it('should have a work duration of 7 years', function() { - _sheet.computed.numYears.should.equal( 7 ); - }); + it('should have one or more of each section', function() { + var newObj = _.pick( _sheet, opts.sections ); + expect( Object.keys(newObj).length ).to.equal( opts.sections.length ); + }); - it('should save without throwing an exception', function(){ - function trySave() { - _sheet.save( 'test/sandbox/jane-q-fullstacker.json' ); - } - trySave.should.not.Throw(); - }); + it('should have a work duration of ' + opts.duration + ' years', function() { + _sheet.computed.numYears.should.equal( opts.duration ); + }); - it('should not be modified after saving', function() { - var savedSheet = new FRESHResume().open('test/sandbox/jane-q-fullstacker.json'); - _sheet.stringify().should.equal( savedSheet.stringify() ); - }); + it('should save without throwing an exception', function(){ + function trySave() { + _sheet.save( 'test/sandbox/' + opts.title + '.json' ); + } + trySave.should.not.Throw(); + }); - it('should validate against the FRESH resume schema', function() { - var result = _sheet.isValid(); - // var schemaJson = require('fresca'); - // var validate = validator( schemaJson, { verbose: true } ); - // var result = validate( JSON.parse( _sheet.imp.raw ) ); - result || console.log("\n\nOops, resume didn't validate. " + - "Validation errors:\n\n", _sheet.imp.validationErrors, "\n\n"); - result.should.equal( true ); - }); + it('should not be modified after saving', function() { + var savedSheet = new FRESHResume().open('test/sandbox/' + opts.title + '.json'); + _sheet.stringify().should.equal( savedSheet.stringify() ); + }); + + it('should validate against the FRESH resume schema', function() { + var result = _sheet.isValid(); + // var schemaJson = require('fresca'); + // var validate = validator( schemaJson, { verbose: true } ); + // var result = validate( JSON.parse( _sheet.imp.raw ) ); + result || console.log("\n\nOops, resume didn't validate. " + + "Validation errors:\n\n", _sheet.imp.validationErrors, "\n\n"); + result.should.equal( true ); + }); -}); + }); +} + +var sects = [ 'info', 'employment', 'service', 'skills', 'education', 'writing', 'recognition', 'references' ]; +testResume({ title: 'jane-q-fullstacker', path: 'node_modules/jane-q-fullstacker/resume/jane-resume.json', duration: 7, sections: sects }); +testResume({ title: 'johnny-trouble-resume', path: 'node_modules/johnny-trouble-resume/src/johnny-trouble.fresh.json', duration: 3, sections: sects }); diff --git a/test/test-themes.js b/test/test-themes.js index 5a6ee1d..ba9496a 100644 --- a/test/test-themes.js +++ b/test/test-themes.js @@ -12,7 +12,9 @@ var SPAWNWATCHER = require('../src/core/spawn-watch') chai.config.includeStack = false; -describe('Testing themes', function () { +function genThemes( title, src, fmt ) { + + describe('Testing themes against ' + title.toUpperCase() + ' resume ' + '(' + fmt + ')' , function () { var _sheet; @@ -29,11 +31,11 @@ describe('Testing themes', function () { function genTheme( fmt, src, themeName, themeLoc, testTitle ) { themeLoc = themeLoc || themeName; - testTitle = themeName.toUpperCase() + ' theme should generate without throwing an exception'; + testTitle = themeName.toUpperCase() + ' theme (' + fmt + ') should generate without throwing an exception'; it( testTitle, function () { function tryOpen() { //var src = ['node_modules/jane-q-fullstacker/resume/jane-resume.json']; - var dst = ['test/sandbox/' + fmt + '/' + themeName + '/resume.all']; + var dst = ['test/sandbox/' + fmt + '/' + title + '/' + themeName + '/resume.all']; var opts = { theme: themeLoc, format: fmt, @@ -46,28 +48,21 @@ describe('Testing themes', function () { }); } - var src = ['node_modules/jane-q-fullstacker/resume/jane-resume.json']; - genTheme('FRESH', src, 'hello-world'); - genTheme('FRESH', src, 'compact'); - genTheme('FRESH', src, 'modern'); - genTheme('FRESH', src, 'minimist'); - genTheme('FRESH', src, 'awesome'); - genTheme('FRESH', src, 'positive'); - genTheme('FRESH', src, 'jsonresume-theme-boilerplate', 'node_modules/jsonresume-theme-boilerplate' ); - genTheme('FRESH', src, 'jsonresume-theme-sceptile', 'node_modules/jsonresume-theme-sceptile' ); - genTheme('FRESH', src, 'jsonresume-theme-modern', 'node_modules/jsonresume-theme-modern' ); - genTheme('FRESH', src, 'jsonresume-theme-classy', 'node_modules/jsonresume-theme-classy' ); + genTheme(fmt, src, 'hello-world'); + genTheme(fmt, src, 'compact'); + genTheme(fmt, src, 'modern'); + genTheme(fmt, src, 'minimist'); + genTheme(fmt, src, 'awesome'); + genTheme(fmt, src, 'positive'); + genTheme(fmt, src, 'jsonresume-theme-boilerplate', 'node_modules/jsonresume-theme-boilerplate' ); + genTheme(fmt, src, 'jsonresume-theme-sceptile', 'node_modules/jsonresume-theme-sceptile' ); + genTheme(fmt, src, 'jsonresume-theme-modern', 'node_modules/jsonresume-theme-modern' ); + genTheme(fmt, src, 'jsonresume-theme-classy', 'node_modules/jsonresume-theme-classy' ); - src = ['test/resumes/jrs-0.0.0/richard-hendriks.json']; - genTheme('JRS', src, 'hello-world'); - genTheme('JRS', src, 'compact'); - genTheme('JRS', src, 'modern'); - genTheme('JRS', src, 'minimist'); - genTheme('JRS', src, 'awesome'); - genTheme('JRS', src, 'positive'); - genTheme('JRS', src, 'jsonresume-theme-boilerplate', 'node_modules/jsonresume-theme-boilerplate' ); - genTheme('JRS', src, 'jsonresume-theme-sceptile', 'node_modules/jsonresume-theme-sceptile' ); - genTheme('JRS', src, 'jsonresume-theme-modern', 'node_modules/jsonresume-theme-modern' ); - genTheme('JRS', src, 'jsonresume-theme-classy', 'node_modules/jsonresume-theme-classy' ); + }); -}); +} + +genThemes( 'jane-q-fullstacker', ['node_modules/jane-q-fullstacker/resume/jane-resume.json'], 'FRESH' ); +genThemes( 'johnny-trouble', ['node_modules/johnny-trouble-resume/src/johnny-trouble.fresh.json'], 'FRESH' ); +genThemes( 'richard-hendriks', ['test/resumes/jrs-0.0.0/richard-hendriks.json'], 'JRS' );