From 5735ddc49528ca202e62f46b16ec4b00e587c6f8 Mon Sep 17 00:00:00 2001 From: devlinjd Date: Sat, 21 Nov 2015 16:12:22 -0500 Subject: [PATCH] Multiple enhancements. A set of rough enhancements supporting FRESH: - Added ability to process multiple sources for all commands (BUILD, VALIDATE, CONVERT). - Added new HELP command to show usage. - Improved error-handling and color-coding. --- src/fluentcmd.js | 99 +++++++++++++++++++++++++++++++----------------- src/index.js | 44 ++++++++++++++++----- src/use.txt | 22 +++++++++++ 3 files changed, 121 insertions(+), 44 deletions(-) create mode 100644 src/use.txt diff --git a/src/fluentcmd.js b/src/fluentcmd.js index f5ffe7f..295d12d 100644 --- a/src/fluentcmd.js +++ b/src/fluentcmd.js @@ -15,7 +15,7 @@ module.exports = function () { , FLUENT = require('./fluentlib') , PATH = require('path') , MKDIRP = require('mkdirp') - , COLORS = require('colors') + //, COLORS = require('colors') , rez, _log, _err; /** @@ -62,8 +62,8 @@ module.exports = function () { // Load the theme var theTheme = new FLUENT.Theme().open( tFolder ); _opts.themeObj = theTheme; - _log( 'Applying '.yellow + theTheme.name.toUpperCase().yellow.bold + (' theme (' + - Object.keys(theTheme.formats).length + ' formats)').yellow ); + _log( 'Applying '.status + theTheme.name.toUpperCase().infoBold + (' theme (' + + Object.keys(theTheme.formats).length + ' formats)').status ); // Expand output resumes... (can't use map() here) var targets = [], that = this; @@ -99,8 +99,8 @@ module.exports = function () { var fObj = _.property( fi.fmt.pre )( theme.formats ); var fOut = path.join( f.substring( 0, f.lastIndexOf('.')+1 ) + fObj.pre); - _log( 'Generating '.green + fi.fmt.title.toUpperCase().green.bold + ' resume: '.green + - path.relative(process.cwd(), f ).green.bold ); + _log( 'Generating '.useful + fi.fmt.title.toUpperCase().useful.bold + ' resume: '.useful + + path.relative(process.cwd(), f ).useful.bold ); var theFormat = _fmts.filter( function( fmt ) { return fmt.name === fi.fmt.pre; })[0]; @@ -124,7 +124,7 @@ module.exports = function () { */ function validate( src, unused, opts, logger ) { _log = logger || console.log; - if( !src || !src.length ) { throw { fluenterror: 3 }; } + if( !src || !src.length ) { throw { fluenterror: 6 }; } var isValid = true; var validator = require('is-my-json-valid'); @@ -152,7 +152,7 @@ module.exports = function () { var rez = JSON.parse( rep.raw ); } catch( ex ) { - _log('Validating '.gray + rep.file.cyan.bold + ' against FRESH/JRS schema: '.gray + 'ERROR!'.red.bold); + _log('Validating '.info + rep.file.infoBold + ' against FRESH/JRS schema: '.info + 'ERROR!'.error.bold); if (ex instanceof SyntaxError) { // Invalid JSON @@ -166,27 +166,39 @@ module.exports = function () { return; } - var fmt = rez.meta && rez.meta.format === 'FRESH@0.1.0' ? 'fresh':'jars'; - process.stdout.write( 'Validating '.gray + rep.file + ' against '.gray + - fmt.replace('jars','JSON Resume').toUpperCase() + ' schema: '.gray ); + var isValid = false; + var style = 'useful'; + var errors = []; - var validate = validator( schemas[ fmt ], { // Note [1] - formats: { date: /^\d{4}(?:-(?:0[0-9]{1}|1[0-2]{1})(?:-[0-9]{2})?)?$/ } + 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 ); }); - var ret = validate( rez ); - if( !ret ) { - rez.imp = rez.imp || { }; - rez.imp.validationErrors = validate.errors; - _log('INVALID'.bold.yellow); - rez.imp.validationErrors.forEach(function(err,idx){ - _log( '--> '.bold.yellow + ( err.field.replace('data.','resume.').toUpperCase() - + ' ' + err.message).yellow ); - }); - } - else { - _log('VALID!'.bold.green); - } + }); } @@ -196,19 +208,35 @@ module.exports = function () { */ function convert( src, dst, opts, logger ) { _log = logger || console.log; - if( !src || !src.length ) { throw { fluenterror: 3 }; } - if( !dst || !dst.length ) { throw { fluenterror: 5 }; } - var sheet = loadSourceResumes( src )[ 0 ]; - var sourceFormat = sheet.imp.orgFormat === 'JRS' ? 'JRS' : 'FRESH'; - var targetFormat = sourceFormat === 'JRS' ? 'FRESH' : 'JRS'; - _log( 'Converting '.gray + src[0] + (' (' + sourceFormat + ') to ').gray + dst[0] + - (' (' + targetFormat + ').').gray ); - sheet.saveAs( dst[0], targetFormat ); + 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 ); + }); + } + + /** + 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 '.gray + 'SOURCE' + ' resume: '.gray + res.cyan.bold ); + _log( 'Reading '.info + 'SOURCE'.infoBold + ' resume: '.status + res.cyan.bold ); return (fn && fn(res)) || (new FLUENT.FRESHResume()).open( res ); }); } @@ -246,7 +274,8 @@ module.exports = function () { verbs: { build: generate, validate: validate, - convert: convert + convert: convert, + help: help }, lib: require('./fluentlib'), options: _opts, diff --git a/src/index.js b/src/index.js index 82f6102..bb4b5f9 100644 --- a/src/index.js +++ b/src/index.js @@ -8,8 +8,11 @@ Command-line interface (CLI) for FluentCV:CLI. var ARGS = require( 'minimist' ) , FCMD = require( './fluentcmd') , PKG = require('../package.json') + , COLORS = require('colors') + , FS = require('fs') + , PATH = require('path') , opts = { } - , title = ('*** FluentCV v' + PKG.version + ' ***').white.bold + , title = ('*** FluentCV v' + PKG.version + ' ***').bold.white , _ = require('underscore'); @@ -25,16 +28,30 @@ catch( ex ) { function main() { + // Colorize + COLORS.setTheme({ + title: ['white','bold'], + info: process.platform === 'win32' ? 'gray' : ['white','dim'], + infoBold: ['white','dim'], + warn: 'yellow', + error: 'red', + guide: 'yellow', + status: 'gray',//['white','dim'], + useful: 'green', + }); + + // Setup if( process.argv.length <= 2 ) { throw { fluenterror: 4 }; } var a = ARGS( process.argv.slice(2) ); opts = getOpts( a ); logMsg( title ); + // Get the action to be performed var params = a._.map( function(p){ return p.toLowerCase().trim(); }); var verb = params[0]; if( !FCMD.verbs[ verb ] ) { - logMsg('Invalid command: "'.yellow + verb.yellow.bold + '"'.yellow); + logMsg('Invalid command: "'.warn + verb.warn.bold + '"'.warn); return; } @@ -42,7 +59,8 @@ function main() { var splitAt = _.indexOf( params, 'to' ); if( splitAt === a._.length - 1 ) { // 'TO' cannot be the last argument - logMsg('Please '.gray + 'specify an output file' + ' for this operation or '.gray + 'omit the TO keyword' + '.'.gray); + logMsg('Please '.warn + 'specify an output file'.warnBold + + ' for this operation or '.warn + 'omit the TO keyword'.warnBold + '.'.warn ); return; } @@ -75,20 +93,27 @@ function getOpts( args ) { function handleError( ex ) { var msg = '', exitCode; + + + if( ex.fluenterror ){ switch( ex.fluenterror ) { // TODO: Remove magic numbers case 1: msg = "The specified theme couldn't be found: " + ex.data; break; case 2: msg = "Couldn't copy CSS file to destination folder"; break; - case 3: msg = 'Please '.gray + 'specify a valid input resume' + ' in '.gray + 'FRESH' + ' or '.gray + 'JSON Resume' + ' format.'.gray; break; - case 4: msg = title + "\nPlease specify a command (".gray + + case 3: msg = 'Please '.guide + 'specify a valid input resume'.guide.bold + ' in FRESH or JSON Resume format.'.guide; break; + case 4: msg = title + "\nPlease ".guide + "specify a command".guide.bold + " (".guide + Object.keys( FCMD.verbs ).map( function(v, idx, ar) { - return (idx === ar.length - 1 ? 'or '.gray : '') - + v.toUpperCase(); - }).join(', ') + ")"; + return (idx === ar.length - 1 ? 'or '.guide : '') + + v.toUpperCase().guide; + }).join(', '.guide) + ") to get started.\n\n".guide + FS.readFileSync( PATH.join(__dirname, 'use.txt'), 'utf8' ).info.bold; break; - case 5: msg = "Please specify the name of the TARGET file to convert to.".gray; + //case 4: msg = title + '\n' + ; break; + case 5: msg = 'Please '.guide + 'specify the output resume file'.guide.bold + ' that should be created in the new format.'.guide; break; + case 6: msg = 'Please '.guide + 'specify a valid input resume'.guide.bold + ' in either FRESH or JSON Resume format.'.guide; break; + case 7: msg = 'Please '.guide + 'specify an output file name'.guide.bold + ' for every input file you wish to convert.'.guide; break; }; exitCode = ex.fluenterror; + } else { msg = ex.toString(); @@ -101,6 +126,7 @@ function handleError( ex ) { console.log( ('ERROR: ' + trimmed.toString()).red.bold ); else console.log( trimmed.toString() ); + process.exit( exitCode ); } diff --git a/src/use.txt b/src/use.txt new file mode 100644 index 0000000..1072347 --- /dev/null +++ b/src/use.txt @@ -0,0 +1,22 @@ +Usage: + + fluentcv [TO ] [-t ] + + should be BUILD, CONVERT, VALIDATE, or HELP. should +be the path to one or more FRESH or JSON Resume format resumes. +should be the name of the destination resume to be created, if any. The + parameter should be the name of a predefined theme (for example: +COMPACT, MINIMIST, MODERN, or HELLO-WORLD) or the relative path to a +custom theme. + + fluentcv BUILD resume.json TO out/resume.all + fluentcv CONVERT resume.json TO resume-jrs.json + fluentcv VALIDATE resume.json + +Both SOURCES and TARGETS can accept multiple files: + + fluentCV BUILD r1.json r2.json TO out/resume.all out2/resume.html + fluentCV VALIDATE resume.json resume2.json resume3.json + +See https://github.com/fluentdesk/fluentCV/blob/master/README.md +for more information.