From fcaeb381fe1796d3caaa52cad90e2600a4a456bd Mon Sep 17 00:00:00 2001 From: devlinjd Date: Mon, 7 Dec 2015 21:24:14 -0500 Subject: [PATCH] Gather. --- src/core/fresh-resume.js | 21 ++ src/core/theme.js | 102 ++++-- src/eng/underscore-generator.js | 5 +- src/fluentcmd.js | 601 +++++++++++++++++--------------- src/gen/template-generator.js | 27 +- 5 files changed, 438 insertions(+), 318 deletions(-) diff --git a/src/core/fresh-resume.js b/src/core/fresh-resume.js index fb647ef..b2fb8d3 100644 --- a/src/core/fresh-resume.js +++ b/src/core/fresh-resume.js @@ -179,6 +179,27 @@ Definition of the FRESHResume class. }); }; + /** + Return the specified network profile. + */ + FreshResume.prototype.getProfile = function( socialNetwork ) { + socialNetwork = socialNetwork.trim().toLowerCase(); + return this.social && _.find( this.social, function(sn) { + return sn.network.trim().toLowerCase() === socialNetwork + }); + } + + /** + Return an array of profiles for the specified network, for when the user + has multiple eg. GitHub accounts. + */ + FreshResume.prototype.getProfiles = function( socialNetwork ) { + socialNetwork = socialNetwork.trim().toLowerCase(); + return this.social && _.filter( this.social, function(sn){ + return sn.network.trim().toLowerCase() === socialNetwork + }); + } + /** Determine if the sheet includes a specific skill. */ diff --git a/src/core/theme.js b/src/core/theme.js index 57565b3..ed79ea4 100644 --- a/src/core/theme.js +++ b/src/core/theme.js @@ -149,36 +149,98 @@ Abstract theme representation. function loadExplicit() { - var formatsHash = { }; var that = this; + // Set up a hash of formats supported by this theme. + var formatsHash = { }; // Establish the base theme folder - var tplFolder = this.folder;//PATH.join( this.folder, 'src' ); + var tplFolder = PATH.join( this.folder, 'src' ); - // Iterate over all keys in the "formats" section of the theme JSON file. - // Each key will be a format (html, latex, pdf, etc) with some data. - Object.keys( this.formats ).forEach( function( k ) { + var act = null; - formatsHash[ k ] = { - outFormat: k, - files: that.formats[ k ].files.map(function(fi){ + // 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 absPath = PATH.join( tplFolder, fi ); - var pathInfo = PATH.parse( absPath ); + act = null; + // If this file is mentioned in the theme's JSON file under "transforms" + var pathInfo = PATH.parse(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; + }); + }); + if( outFmt ) { + act = 'transform'; + } + // 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 ) { + var portion = pathInfo.dir.replace(tplFolder,''); + if( portion && portion.trim() ) { + var reg = /^(?:\/|\\)(html|latex|doc|pdf)(?:\/|\\)?/ig; + var res = reg.exec( portion ); + res && (outFmt = res[1]); + } + } - return { - path: absPath, - ext: pathInfo.ext.slice(1), - title: friendlyName( k ), - pre: k, - outFormat: k, - data: FS.readFileSync( absPath, 'utf8' ), - css: null - }; - }) + // Otherwise, the output format is inferred from the filename, as in + // 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 ) + } + + // We should have a valid output format now. + formatsHash[ outFmt ] = + formatsHash[outFmt] || { outFormat: outFmt, files: [] }; + + // Create the file representation object. + var obj = { + action: act, + orgPath: PATH.relative(that.folder, absPath), + path: absPath, + ext: pathInfo.ext.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 ); + return obj; }); + + // Now, get all the CSS files... + (this.cssFiles = fmts.filter(function( fmt ){ return fmt.ext === 'css'; })) + .forEach(function( cssf ) { + // For each CSS file, get its corresponding HTML file + var idx = _.findIndex(fmts, function( fmt ) { + return 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( function( fmt) { + return fmt.ext !== 'css'; + }); + + // Object.keys( formatsHash ).forEach(function(k){ + // formatsHash[ k ].files.forEach(function(xhs){ + // console.log(xhs.orgPath); + // }); + // }); + return formatsHash; } diff --git a/src/eng/underscore-generator.js b/src/eng/underscore-generator.js index a8102db..d25089d 100644 --- a/src/eng/underscore-generator.js +++ b/src/eng/underscore-generator.js @@ -23,10 +23,10 @@ Underscore template generate for FluentCV. jst = jst.replace( delims.interpolate, function replace(m, p1) { if( p1.indexOf('|') > -1 ) { var terms = p1.split('|'); - return '[~ print( filt.' + terms[1] + '( ' + terms[0] + ' )) ]]'; + return '[~ print( filt.' + terms[1] + '( ' + terms[0] + ' )) ~]'; } else { - return '[~ print( filt.out(' + p1 + ') ) ]]'; + return '[~ print( filt.out(' + p1 + ') ) ~]'; } }); @@ -34,6 +34,7 @@ Underscore template generate for FluentCV. jst = jst.replace( delims.comment, ''); // Compile and run the template. TODO: avoid unnecessary recompiles. var compiled = _.template(jst); + var ret = compiled({ r: json, filt: opts.filters, diff --git a/src/fluentcmd.js b/src/fluentcmd.js index 5063911..cb8b6c9 100644 --- a/src/fluentcmd.js +++ b/src/fluentcmd.js @@ -4,318 +4,343 @@ Internal resume generation logic for FluentCV. @module fluentcmd.js */ -module.exports = function () { +(function() { + module.exports = function () { - // We don't mind pseudo-globals here - var path = require( 'path' ) - , extend = require( './utils/extend' ) - , unused = require('./utils/string') - , FS = require('fs') - , _ = require('underscore') - , FLUENT = require('./fluentlib') - , PATH = require('path') - , MKDIRP = require('mkdirp') - //, COLORS = require('colors') - , rez, _log, _err; + var path = require( 'path' ) + , extend = require( './utils/extend' ) + , unused = require('./utils/string') + , FS = require('fs') + , _ = require('underscore') + , FLUENT = require('./fluentlib') + , PATH = require('path') + , MKDIRP = require('mkdirp') + //, COLORS = require('colors') + , rez, _log, _err; - /** - Given a source JSON resume, 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". - @param logger Optional logging override. - */ - function generate( src, dst, opts, logger, errHandler ) { + /** + Given a source JSON resume, 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". + @param logger Optional logging override. + */ + function generate( src, dst, opts, logger, errHandler ) { - _log = logger || console.log; - _err = errHandler || error; + _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; + //_opts = extend( true, _opts, opts ); + _opts.theme = (opts.theme && opts.theme.toLowerCase().trim())|| 'modern'; + _opts.prettify = opts.prettify === true ? _opts.prettify : false; - // Load input resumes... - if(!src || !src.length) { throw { fluenterror: 3 }; } - var sheets = loadSourceResumes( src ); + // Load input resumes... + if(!src || !src.length) { throw { fluenterror: 3 }; } + var sheets = loadSourceResumes( src ); - // Merge input resumes... - var msg = ''; - rez = _.reduceRight( sheets, function( a, b, idx ) { - msg += ((idx == sheets.length - 2) ? 'Merging '.gray + a.imp.fileName : '') - + ' onto '.gray + b.imp.fileName; - return extend( true, b, a ); - }); - msg && _log(msg); + // Merge input resumes... + var msg = ''; + rez = _.reduceRight( sheets, function( a, b, idx ) { + msg += ((idx == sheets.length - 2) ? + 'Merging '.gray+ a.imp.fileName : '') + ' onto '.gray + b.imp.fileName; + return extend( true, b, a ); + }); + msg && _log(msg); - // Verify the specified theme name/path - var relativeThemeFolder = '../node_modules/fluent-themes/themes'; - var tFolder = PATH.resolve( __dirname, relativeThemeFolder, _opts.theme ); - var exists = require('./utils/file-exists'); - if (!exists( tFolder )) { - tFolder = PATH.resolve( _opts.theme ); + // Verify the specified theme name/path + var relativeThemeFolder = '../node_modules/fluent-themes/themes'; + var tFolder = PATH.resolve( __dirname, relativeThemeFolder, _opts.theme); + var exists = require('./utils/file-exists'); if (!exists( tFolder )) { - throw { fluenterror: 1, data: _opts.theme }; - } - } - - // Load the theme - var theTheme = new FLUENT.Theme().open( tFolder ); - _opts.themeObj = theTheme; - _log( 'Applying '.info + theTheme.name.toUpperCase().infoBold + (' theme (' + - Object.keys(theTheme.formats).length + ' formats)').info ); - - // 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 = path.parse(to), - fmat = pa.ext || '.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) ) }]); - - }); - - // Run the transformation! - var finished = targets.map( function(t) { return 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". - */ - function single( targetInfo, theme ) { - try { - var f = targetInfo.file, fType = targetInfo.fmt.outFormat, fName = path.basename(f,'.'+fType); - - if( targetInfo.fmt.files && targetInfo.fmt.files.length ) { - targetInfo.fmt.files.forEach( function( form ) { - - if( form.ext === 'css' ) - return; - - _log( 'Generating '.useful + targetInfo.fmt.outFormat.toUpperCase().useful.bold + ' resume: '.useful + - path.relative(process.cwd(), f ).useful.bold ); - - var theFormat = _fmts.filter( - function( fmt ) { return fmt.name === targetInfo.fmt.outFormat; })[0]; - - MKDIRP.sync( path.dirname( f ) ); // Ensure dest folder exists; - theFormat.gen.generate( rez, f, _opts ); - - }); - } - else { - _log( 'Generating '.useful + targetInfo.fmt.outFormat.toUpperCase().useful.bold + ' resume: '.useful + - path.relative(process.cwd(), f ).useful.bold ); - - var theFormat = _fmts.filter( - function( fmt ) { return fmt.name === targetInfo.fmt.outFormat; })[0]; - MKDIRP.sync( path.dirname( f ) ); // Ensure dest folder exists; - theFormat.gen.generate( rez, f, _opts ); - } - } - catch( ex ) { - _err( ex ); - } - } - - /** - Handle an exception. - */ - function error( ex ) { - throw ex; - } - - /** - Validate 1 to N resumes in either FRESH or JSON Resume format. - */ - function validate( src, unused, opts, logger ) { - _log = logger || console.log; - if( !src || !src.length ) { throw { fluenterror: 6 }; } - var isValid = true; - - var validator = require('is-my-json-valid'); - var schemas = { - fresh: require('FRESCA'), - jars: require('./core/resume.json') - }; - - // Load input resumes... - var sheets = loadSourceResumes(src, function( res ) { - try { - return { - file: res, - raw: FS.readFileSync( res, 'utf8' ) - }; - } - catch( ex ) { - throw ex; - } - }); - - sheets.forEach( function( rep ) { - - try { - var rez = JSON.parse( rep.raw ); - } - catch( ex ) { - _log('Validating '.info + rep.file.infoBold + ' against FRESH/JRS schema: '.info + 'ERROR!'.error.bold); - - if (ex instanceof SyntaxError) { - // Invalid JSON - _log( '--> '.bold.red + rep.file.toUpperCase().red + ' contains invalid JSON. Unable to validate.'.red ); - _log( (' INTERNAL: ' + ex).red ); + tFolder = PATH.resolve( _opts.theme ); + if (!exists( tFolder )) { + throw { fluenterror: 1, data: _opts.theme }; } - else { - - _log(('ERROR: ' + ex.toString()).red.bold); - } - return; } - var isValid = false; - var style = 'useful'; - var errors = []; + // Load the theme + var theTheme = new FLUENT.Theme().open( tFolder ); + _opts.themeObj = theTheme; + _log( 'Applying '.info + theTheme.name.toUpperCase().infoBold + + (' theme (' +Object.keys(theTheme.formats).length + ' formats)').info); - try { + // 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 = path.parse(to), + fmat = pa.ext || '.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) ) }]); - var fmt = rez.meta && rez.meta.format === 'FRESH@0.1.0' ? 'fresh':'jars'; - var validate = validator( schemas[ fmt ], { // Note [1] - formats: { date: /^\d{4}(?:-(?:0[0-9]{1}|1[0-2]{1})(?:-[0-9]{2})?)?$/ } - }); - - isValid = validate( rez ); - if( !isValid ) { - style = 'warn'; - errors = validate.errors; - } - - } - catch(ex) { - - } - - _log( 'Validating '.info + rep.file.infoBold + ' against '.info + - fmt.replace('jars','JSON Resume').toUpperCase().infoBold + ' schema: '.info + (isValid ? 'VALID!' : 'INVALID')[style].bold ); - - errors.forEach(function(err,idx){ - _log( '--> '.bold.yellow + ( err.field.replace('data.','resume.').toUpperCase() - + ' ' + err.message).yellow ); }); + // Run the transformation! + var finished = targets.map( function(t) { return single(t, theTheme); }); - - }); - } - - /** - Convert between FRESH and JRS formats. - */ - function convert( src, dst, opts, logger ) { - _log = logger || console.log; - if( !src || !src.length ) { throw { fluenterror: 6 }; } - if( !dst || !dst.length ) { - if( src.length === 1 ) { throw { fluenterror: 5 }; } - else if( src.length === 2 ) { dst = [ src[1] ]; src = [ src[0] ]; } - else { throw { fluenterror: 5 }; } + // Don't send the client back empty-handed + return { sheet: rez, targets: targets, processed: finished }; } - if( src && dst && src.length && dst.length && src.length !== dst.length ) { - throw { fluenterror: 7 }; + + /** + 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". + */ + function single( targInfo, theme ) { + try { + var f = targInfo.file + , fType = targInfo.fmt.outFormat + , fName = path.basename(f, '.' + fType); + + // If targInfo.fmt.files exists, this theme has an explicit "files" + // section in its theme.json file. + if( targInfo.fmt.files && targInfo.fmt.files.length ) { + + _log( 'Generating '.useful + + targInfo.fmt.outFormat.toUpperCase().useful.bold + + ' resume: '.useful + path.relative(process.cwd(), f ).useful.bold); + + var 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 ); + + // targInfo.fmt.files.forEach( function( form ) { + // + // if( form.action === 'transform' ) { + // var 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 ); + // } + // else if( form.action === null ) { + // // Copy the file + // } + // + // }); + + } + // Otherwise the theme has no files section + else { + _log( 'Generating '.useful + + targInfo.fmt.outFormat.toUpperCase().useful.bold + + ' resume: '.useful + path.relative(process.cwd(), f ).useful.bold); + + var 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 ); + } + } + catch( ex ) { + _err( ex ); + } } - var sheets = loadSourceResumes( src ); - sheets.forEach(function(sheet, idx){ - var sourceFormat = sheet.imp.orgFormat === 'JRS' ? 'JRS' : 'FRESH'; - var targetFormat = sourceFormat === 'JRS' ? 'FRESH' : 'JRS'; - _log( 'Converting '.useful + sheet.imp.fileName.useful.bold + (' (' + sourceFormat + ') to ').useful + dst[0].useful.bold + - (' (' + targetFormat + ').').useful ); - sheet.saveAs( dst[idx], targetFormat ); - }); - } - /** - Create a new empty resume in either FRESH or JRS format. - */ - function create( src, dst, opts, logger ) { - _log = logger || console.log; - dst = src || ['resume.json']; - dst.forEach( function( t ) { - var safeFormat = opts.format.toUpperCase(); - _log('Creating new '.useful +safeFormat.useful.bold+ ' resume: '.useful + t.useful.bold); - MKDIRP.sync( path.dirname( t ) ); // Ensure dest folder exists; - FLUENT[ safeFormat + 'Resume' ].default().save( t ); - }); - } - - /** - Display help documentation. - */ - function help() { - console.log( FS.readFileSync( PATH.join(__dirname, 'use.txt'), 'utf8' ).useful.bold ); - } - - function loadSourceResumes( src, fn ) { - return src.map( function( res ) { - _log( 'Reading '.info + 'SOURCE'.infoBold + ' resume: '.info + res.cyan.bold ); - return (fn && fn(res)) || (new FLUENT.FRESHResume()).open( res ); - }); - } - - /** - Supported resume formats. - */ - var _fmts = [ - { name: 'html', ext: 'html', gen: new FLUENT.HtmlGenerator() }, - { name: 'txt', ext: 'txt', gen: new FLUENT.TextGenerator() }, - { name: 'doc', ext: 'doc', fmt: 'xml', gen: new FLUENT.WordGenerator() }, - { name: 'pdf', ext: 'pdf', fmt: 'html', is: false, gen: new FLUENT.HtmlPdfGenerator() }, - { name: 'md', ext: 'md', fmt: 'txt', gen: new FLUENT.MarkdownGenerator() }, - { name: 'json', ext: 'json', gen: new FLUENT.JsonGenerator() }, - { name: 'yml', ext: 'yml', fmt: 'yml', gen: new FLUENT.JsonYamlGenerator() }, - { name: 'latex', ext: 'tex', fmt: 'latex', gen: new FLUENT.LaTeXGenerator() } - ]; - - /** - Default FluentCV options. - */ - var _opts = { - theme: 'modern', - prettify: { // ← See https://github.com/beautify-web/js-beautify#options - indent_size: 2, - unformatted: ['em','strong'], - max_char: 80, // ← See lib/html.js in above-linked repo - //wrap_line_length: 120, ← Don't use this + /** + Handle an exception. + */ + function error( ex ) { + throw ex; } - }; - /** - Internal module interface. Used by FCV Desktop and HMR. - */ - return { - verbs: { - build: generate, - validate: validate, - convert: convert, - new: create, - help: help - }, - lib: require('./fluentlib'), - options: _opts, - formats: _fmts - }; + /** + Validate 1 to N resumes in either FRESH or JSON Resume format. + */ + function validate( src, unused, opts, logger ) { + _log = logger || console.log; + if( !src || !src.length ) { throw { fluenterror: 6 }; } + var isValid = true; -}(); + var validator = require('is-my-json-valid'); + var schemas = { + fresh: require('FRESCA'), + jars: require('./core/resume.json') + }; + + // Load input resumes... + var sheets = loadSourceResumes(src, function( res ) { + try { + return { + file: res, + raw: FS.readFileSync( res, 'utf8' ) + }; + } + catch( ex ) { + throw ex; + } + }); + + sheets.forEach( function( rep ) { + + try { + var rez = JSON.parse( rep.raw ); + } + catch( ex ) { + _log('Validating '.info + rep.file.infoBold + + ' against FRESH/JRS schema: '.info + 'ERROR!'.error.bold); + + if (ex instanceof SyntaxError) { + // Invalid JSON + _log( '--> '.bold.red + rep.file.toUpperCase().red + + ' contains invalid JSON. Unable to validate.'.red ); + _log( (' INTERNAL: ' + ex).red ); + } + else { + + _log(('ERROR: ' + ex.toString()).red.bold); + } + return; + } + + var isValid = false; + var style = 'useful'; + var errors = []; + + try { + + var fmt = rez.meta && + (rez.meta.format === 'FRESH@0.1.0') ? 'fresh':'jars'; + var validate = validator( schemas[ fmt ], { // Note [1] + formats: { + date: /^\d{4}(?:-(?:0[0-9]{1}|1[0-2]{1})(?:-[0-9]{2})?)?$/ + } + }); + + isValid = validate( rez ); + if( !isValid ) { + style = 'warn'; + errors = validate.errors; + } + + } + catch(ex) { + + } + + _log( 'Validating '.info + rep.file.infoBold + ' against '.info + + fmt.replace('jars','JSON Resume').toUpperCase().infoBold + + ' schema: '.info + (isValid ? 'VALID!' : 'INVALID')[style].bold ); + + errors.forEach(function(err,idx) { + _log( '--> '.bold.yellow + + (err.field.replace('data.','resume.').toUpperCase() + ' ' + + err.message).yellow ); + }); + + }); + } + + /** + Convert between FRESH and JRS formats. + */ + function convert( src, dst, opts, logger ) { + _log = logger || console.log; + if( !src || !src.length ) { throw { fluenterror: 6 }; } + if( !dst || !dst.length ) { + if( src.length === 1 ) { throw { fluenterror: 5 }; } + else if( src.length === 2 ) { dst = [ src[1] ]; src = [ src[0] ]; } + else { throw { fluenterror: 5 }; } + } + if( src && dst && src.length && dst.length && src.length !== dst.length ) { + throw { fluenterror: 7 }; + } + var sheets = loadSourceResumes( src ); + sheets.forEach(function(sheet, idx){ + var sourceFormat = sheet.imp.orgFormat === 'JRS' ? 'JRS' : 'FRESH'; + var targetFormat = sourceFormat === 'JRS' ? 'FRESH' : 'JRS'; + _log( 'Converting '.useful + sheet.imp.fileName.useful.bold + (' (' + + sourceFormat + ') to ').useful + dst[0].useful.bold + + (' (' + targetFormat + ').').useful ); + sheet.saveAs( dst[idx], targetFormat ); + }); + } + + /** + Create a new empty resume in either FRESH or JRS format. + */ + function create( src, dst, opts, logger ) { + _log = logger || console.log; + dst = src || ['resume.json']; + dst.forEach( function( t ) { + var safeFormat = opts.format.toUpperCase(); + _log('Creating new '.useful +safeFormat.useful.bold+ ' resume: '.useful + + t.useful.bold); + MKDIRP.sync( path.dirname( t ) ); // Ensure dest folder exists; + FLUENT[ safeFormat + 'Resume' ].default().save( t ); + }); + } + + /** + Display help documentation. + */ + function help() { + console.log( FS.readFileSync( PATH.join(__dirname, 'use.txt'), 'utf8' ) + .useful.bold ); + } + + function loadSourceResumes( src, fn ) { + return src.map( function( res ) { + _log( 'Reading '.info + 'SOURCE'.infoBold + ' resume: '.info + + res.cyan.bold ); + return (fn && fn(res)) || (new FLUENT.FRESHResume()).open( res ); + }); + } + + /** + Supported resume formats. + */ + var _fmts = [ + { name: 'html', ext: 'html', gen: new FLUENT.HtmlGenerator() }, + { name: 'txt', ext: 'txt', gen: new FLUENT.TextGenerator() }, + { name: 'doc', ext: 'doc', fmt: 'xml', gen: new FLUENT.WordGenerator() }, + { name: 'pdf', ext: 'pdf', fmt: 'html', is: false, gen: new FLUENT.HtmlPdfGenerator() }, + { name: 'md', ext: 'md', fmt: 'txt', gen: new FLUENT.MarkdownGenerator() }, + { name: 'json', ext: 'json', gen: new FLUENT.JsonGenerator() }, + { name: 'yml', ext: 'yml', fmt: 'yml', gen: new FLUENT.JsonYamlGenerator() }, + { name: 'latex', ext: 'tex', fmt: 'latex', gen: new FLUENT.LaTeXGenerator() } + ]; + + /** + Default FluentCV options. + */ + var _opts = { + theme: 'modern', + prettify: { // ← See https://github.com/beautify-web/js-beautify#options + indent_size: 2, + unformatted: ['em','strong'], + max_char: 80, // ← See lib/html.js in above-linked repo + //wrap_line_length: 120, ← Don't use this + } + }; + + /** + Internal module interface. Used by FCV Desktop and HMR. + */ + return { + verbs: { + build: generate, + validate: validate, + convert: convert, + new: create, + help: help + }, + lib: require('./fluentlib'), + options: _opts, + formats: _fmts + }; + + }(); + +}()); // [1]: JSON.parse throws SyntaxError on invalid JSON. See: // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse diff --git a/src/gen/template-generator.js b/src/gen/template-generator.js index c25c956..6ab7016 100644 --- a/src/gen/template-generator.js +++ b/src/gen/template-generator.js @@ -10,6 +10,7 @@ Template-based resume generator base for FluentCV. , MD = require( 'marked' ) , XML = require( 'xml-escape' ) , PATH = require('path') + , MKDIRP = require('mkdirp') , BaseGenerator = require( './base-generator' ) , EXTEND = require('../utils/extend') , Theme = require('../core/theme'); @@ -90,6 +91,8 @@ Template-based resume generator base for FluentCV. } } + var outFolder = PATH.parse(f).dir; + // Load the theme var theme = opts.themeObj || new Theme().open( tFolder ); @@ -99,20 +102,28 @@ Template-based resume generator base for FluentCV. var that = this; curFmt.files.forEach(function(tplInfo){ + if( tplInfo.action === 'transform' ) { + var cssInfo = { file: tplInfo.css ? tplInfo.cssPath : null, data: tplInfo.css || null }; + var mk = that.single( rez, tplInfo.data, that.format, cssInfo, that.opts ); + that.onBeforeSave && (mk = that.onBeforeSave( { mk: mk, theme: theme, outputFile: f } )); - var cssInfo = { file: tplInfo.css ? tplInfo.cssPath : null, data: tplInfo.css || null }; - // Compile and invoke the template! - var mk = that.single( rez, tplInfo.data, that.format, cssInfo, that.opts ); - that.onBeforeSave && (mk = that.onBeforeSave( { mk: mk, theme: theme, outputFile: f } )); - FS.writeFileSync( f, mk, { encoding: 'utf8', flags: 'w' } ); + var thisFilePath = PATH.join(outFolder, tplInfo.orgPath); + MKDIRP.sync( PATH.dirname(thisFilePath) ); + console.log('Would save to ' + thisFilePath); + + FS.writeFileSync( thisFilePath, mk, { encoding: 'utf8', flags: 'w' } ); + } }); }, /** - 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.). + Perform a single resume JSON-to-DEST resume transformation. + @param json A FRESH or JRS resume object. + @param jst The stringified template data + @param format The format name, such as "html" or "latex" + @param cssInfo Needs to be refactored. + @param opts Options and passthrough data. */ single: function( json, jst, format, cssInfo, opts ) {