diff --git a/src/gen/base-generator.js b/src/gen/base-generator.js new file mode 100644 index 0000000..dcb319b --- /dev/null +++ b/src/gen/base-generator.js @@ -0,0 +1,13 @@ +/** +Base resume generator for FluentCV. +@license Copyright (c) 2015 by James M. Devlin. All rights reserved. +*/ + +(function() { + var Class = require( './class' ); + var BaseGenerator = module.exports = Class.extend({ + init: function( outputFormat ) { + this.format = outputFormat; + } + }); +}()); diff --git a/src/gen/class.js b/src/gen/class.js new file mode 100644 index 0000000..2640865 --- /dev/null +++ b/src/gen/class.js @@ -0,0 +1,67 @@ +/* Simple JavaScript Inheritance + * By John Resig http://ejohn.org/ + * MIT Licensed. + * http://ejohn.org/blog/simple-javascript-inheritance/ + */ +// Inspired by base2 and Prototype +(function(){ + var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/; + + // The base Class implementation (does nothing) + this.Class = function(){}; + module.exports = Class; + + // Create a new Class that inherits from this class + Class.extend = function(prop) { + var _super = this.prototype; + + // Instantiate a base class (but only create the instance, + // don't run the init constructor) + initializing = true; + var prototype = new this(); + initializing = false; + + // Copy the properties over onto the new prototype + for (var name in prop) { + // Check if we're overwriting an existing function + prototype[name] = typeof prop[name] == "function" && + typeof _super[name] == "function" && fnTest.test(prop[name]) ? + (function(name, fn){ + return function() { + var tmp = this._super; + + // Add a new ._super() method that is the same method + // but on the super-class + this._super = _super[name]; + + // The method only need to be bound temporarily, so we + // remove it when we're done executing + var ret = fn.apply(this, arguments); + this._super = tmp; + + return ret; + }; + })(name, prop[name]) : + prop[name]; + } + + // The dummy class constructor + function Class() { + // All construction is actually done in the init method + if ( !initializing && this.init ) + this.init.apply(this, arguments); + } + + // Populate our constructed prototype object + Class.prototype = prototype; + + // Enforce the constructor to be what we expect + Class.prototype.constructor = Class; + + // And make this class extendable + Class.extend = arguments.callee; + + return Class; + }; + +})(); diff --git a/src/gen/html-generator.js b/src/gen/html-generator.js new file mode 100644 index 0000000..0531913 --- /dev/null +++ b/src/gen/html-generator.js @@ -0,0 +1,31 @@ +/** +HTML resume generator for FluentCV. +@license Copyright (c) 2015 by James M. Devlin. All rights reserved. +*/ + +var TemplateGenerator = require('./template-generator'); +var FS = require('fs-extra'); +var HTML = require( 'html' ); + +var HtmlGenerator = TemplateGenerator.extend({ + + init: function() { + this._super( 'html' ); + }, + + /** + Generate an HTML resume with optional pretty printing. + */ + onBeforeSave: function( mk, themeFile, outputFile ) { + var cssSrc = themeFile.replace( /.html$/g, '.css' ); + var cssDst = outputFile.replace( /.html$/g, '.css' ); + FS.copy( cssSrc, cssDst, function( e ) { + if( e ) err( "Couldn't copy CSS file to destination: " + err); + }); + return true ? + HTML.prettyPrint( mk, { indent_size: 2 } ) : mk; + } + +}); + +module.exports = HtmlGenerator; diff --git a/src/gen/html-pdf-generator.js b/src/gen/html-pdf-generator.js new file mode 100644 index 0000000..a657204 --- /dev/null +++ b/src/gen/html-pdf-generator.js @@ -0,0 +1,31 @@ +/** +HTML-based PDF resume generator for FluentCV. +@license Copyright (c) 2015 by James M. Devlin. All rights reserved. +*/ + +var TemplateGenerator = require('./template-generator'); +var FS = require('fs-extra'); +var HTML = require( 'html' ); + +var HtmlPdfGenerator = TemplateGenerator.extend({ + + init: function() { + this._super( 'pdf', 'html' ); + }, + + /** + Generate an HTML resume with optional pretty printing. + */ + onBeforeSave: function( mk, themeFile, outputFile ) { + var cssSrc = themeFile.replace( /.html$/g, '.css' ); + var cssDst = outputFile.replace( /.html$/g, '.css' ); + FS.copy( cssSrc, cssDst, function( e ) { + if( e ) err( "Couldn't copy CSS file to destination: " + err); + }); + return true ? + HTML.prettyPrint( mk, { indent_size: 2 } ) : mk; + } + +}); + +module.exports = HtmlPdfGenerator; diff --git a/src/gen/template-generator.js b/src/gen/template-generator.js new file mode 100644 index 0000000..b093413 --- /dev/null +++ b/src/gen/template-generator.js @@ -0,0 +1,148 @@ +/** +Template-based resume generator base for FluentCV. +@license Copyright (c) 2015 by James M. Devlin. All rights reserved. +*/ + +(function() { + + var FS = require( 'fs' ); + var BaseGenerator = require( './base-generator' ); + var _ = require( 'underscore' ); + var MD = require( 'marked' ); + var XML = require( 'xml-escape' ); + var path = require('path'); + + var _opts = { + keepBreaks: true, + nSym: '&newl;', + rSym: '&retn;', + template: { + interpolate: /\{\{(.+?)\}\}/g, + escape: /\{\{\=(.+?)\}\}/g, + evaluate: /\{\%(.+?)\%\}/g, + comment: /\{\#(.+?)\#\}/g + }, + filters: { + out: function( txt ) { return txt; }, + raw: function( txt ) { return txt; }, + xml: function( txt ) { return XML(txt); }, + md: function( txt ) { return MD(txt); }, + mdin: function( txt ) { return MD(txt).replace(/^\s*\|\<\/p\>\s*$/gi, ''); }, + lower: function( txt ) { return txt.toLowerCase(); } + }, + prettyPrint: true, + prettyIndent: 2 + }; + + var TemplateGenerator = BaseGenerator.extend({ + + /** outputFormat: html, txt, pdf, doc + templateFormat: html or txt + **/ + init: function( outputFormat, templateFormat, cssFile ){ + this._super( outputFormat ); + this.tplFormat = templateFormat || outputFormat; + }, + + /** Default generation method for template-based generators. */ + generate: function( rez, f, themeName ) { + try { + + // Get the output file type (pdf, html, txt, etc) + var fName = path.basename( f, '.' + this.format ); + + // Load the active theme file, including CSS data if req'd + var themeFile = path.join( __dirname, '../../../watermark/', themeName, this.format + '.' + this.tplFormat ); + var cssData = this.tplFormat === 'html' ? FS.readFileSync( path.join( __dirname, '../../../watermark/', themeName, 'html.css' ), 'utf8' ) : null; + var mk = FS.readFileSync( themeFile, 'utf8' ); + + // Compile and invoke the template! + mk = this.single( rez, mk, this.format, cssData, fName ); + this.onBeforeSave && (mk = this.onBeforeSave( mk, themeFile, f )); + + // Post-process and save the file + FS.writeFileSync( f, mk, 'utf8' ); + return mk; + } + catch( ex ) { + err( ex ); + } + }, + + /** + Perform a single resume JSON-to-DEST resume transformation. Exists as a + separate function in order to expose string-based transformations to clients + who don't have access to filesystem resources (in-browser, etc.). + */ + single: function( json, jst, format, styles, fName ) { + + // Freeze whitespace in the template + _opts.keepBreaks && ( jst = freeze(jst) ); + + // Tweak underscore's default template delimeters + _.templateSettings = _opts.template; + + // Convert {{ someVar }} to {% print(filt.out(someVar) %} + // Convert {{ someVar|someFilter }} to {% print(filt.someFilter(someVar) %} + jst = jst.replace( _.templateSettings.interpolate, function replace(m, p1) { + if( p1.indexOf('|') > -1 ) { + var terms = p1.split('|'); + return '{% print( filt.' + terms[1] + '( ' + terms[0] + ' )) %}'; + } + else { + return '{% print( filt.out(' + p1 + ') ) %}'; + } + }); + + // Strip {# comments #} + jst = jst.replace( _.templateSettings.comment, ''); + json.display_progress_bar = true; + + // Compile and run the template. TODO: avoid unnecessary recompiles. + jst = _.template( jst )({ r: json, css: styles, embedCss: false, cssFile: fName, filt: _opts.filters }); + + // Unfreeze whitespace + _opts.keepBreaks && ( jst = unfreeze(jst) ); + + return jst; + } + + + }); + + /** + Export the TemplateGenerator function/ctor. + */ + module.exports = TemplateGenerator; + + /** + Freeze newlines for protection against errant JST parsers. + */ + function freeze( markup ) { + return markup + .replace( _reg.regN, _opts.nSym ) + .replace( _reg.regR, _opts.rSym ); + } + + /** + Unfreeze newlines when the coast is clear. + */ + function unfreeze( markup ) { + return markup + .replace( _reg.regSymR, '\r' ) + .replace( _reg.regSymN, '\n' ); + } + + /** + Regexes for linebreak preservation. + */ + var _reg = { + regN: new RegExp( '\n', 'g' ), + regR: new RegExp( '\r', 'g' ), + regSymN: new RegExp( _opts.nSym, 'g' ), + regSymR: new RegExp( _opts.rSym, 'g' ) + }; + + + +}()); diff --git a/src/gen/text-generator.js b/src/gen/text-generator.js new file mode 100644 index 0000000..46b1420 --- /dev/null +++ b/src/gen/text-generator.js @@ -0,0 +1,15 @@ +/** +Plain text resume generator for FluentCV. +@license Copyright (c) 2015 by James M. Devlin. All rights reserved. +*/ + +var TemplateGenerator = require('./template-generator'); +var TextGenerator = TemplateGenerator.extend({ + + init: function(){ + this._super( 'txt' ); + }, + +}); + +module.exports = TextGenerator; diff --git a/src/gen/word-generator.js b/src/gen/word-generator.js new file mode 100644 index 0000000..db6cbd8 --- /dev/null +++ b/src/gen/word-generator.js @@ -0,0 +1,13 @@ +/** +MS Word resume generator for FluentCV. +@license Copyright (c) 2015 by James M. Devlin. All rights reserved. +*/ + +var TemplateGenerator = require('./template-generator'); +var WordGenerator = module.exports = TemplateGenerator.extend({ + + init: function(){ + this._super( 'doc', 'xml' ); + }, + +}); diff --git a/src/scrappy.js b/src/scrappy.js index a520bbe..b34e06e 100644 --- a/src/scrappy.js +++ b/src/scrappy.js @@ -13,7 +13,11 @@ module.exports = function () { , path = require( 'path' ) , extend = require( './extend' ) , _ = require('underscore') - , Sheet = require('./sheet'); + , Sheet = require('./sheet') + , HtmlGenerator = require('./gen/html-generator') + , TextGenerator = require('./gen/text-generator') + , HtmlPdfGenerator = require('./gen/html-pdf-generator') + , WordGenerator = require('./gen/word-generator'); String.prototype.endsWith = function(suffix) { return this.indexOf(suffix, this.length - suffix.length) !== -1; @@ -281,7 +285,12 @@ module.exports = function () { generate: hmr, transform: single, options: _opts, - formats: _fmts + formats: _fmts, + Sheet: Sheet, + HtmlGenerator: HtmlGenerator, + TextGenerator: TextGenerator, + HtmlPdfGenerator: HtmlPdfGenerator, + WordGenerator: WordGenerator }; }();