diff --git a/package.json b/package.json index ef9583a..c234aee 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "homepage": "https://github.com/hacksalot/HackMyResume", "dependencies": { "chalk": "^1.1.1", + "commander": "^2.9.0", "copy": "^0.1.3", "fresca": "~0.2.4", "fresh-themes": "~0.9.3-beta", diff --git a/src/core/error-handler.js b/src/core/error-handler.js index 358e67e..839a4af 100644 --- a/src/core/error-handler.js +++ b/src/core/error-handler.js @@ -67,8 +67,7 @@ Error-handling routines for HackMyResume. break; case HACKMYSTATUS.invalidCommand: - msg = chalk.yellow('Please ') + chalk.yellow.bold('specify the output resume file') + - chalk.yellow(' that should be created.'); + msg = chalk.yellow('Invalid command: "') + chalk.yellow.bold(ex.attempted) + chalk.yellow('"'); break; case HACKMYSTATUS.resumeNotFoundAlt: @@ -116,7 +115,7 @@ Error-handling routines for HackMyResume. } // Let the error code be the process's return code. - if( shouldExit ) + if( shouldExit || ex.shouldExit ) process.exit( exitCode ); } diff --git a/src/index.js b/src/index.js index 9d8ac12..9cdbc08 100644 --- a/src/index.js +++ b/src/index.js @@ -18,8 +18,9 @@ var SPAWNW = require('./core/spawn-watch') , HACKMYSTATUS = require('./core/status-codes') , opts = { } , title = chalk.white.bold('\n*** HackMyResume v' + PKG.version + ' ***') - , _ = require('underscore'); - + , StringUtils = require('./utils/string.js') + , _ = require('underscore') + , Command = require('commander').Command; @@ -31,46 +32,175 @@ catch( ex ) { } - +/** +Kick off the HackMyResume application. +*/ function main() { - // Setup - var a = ARGS( process.argv.slice(2) ); - if( a._.length === 0 ) { throw { fluenterror: 4 }; } - opts = getOpts( a ); - logMsg( title ); + var args = initialize(); - // Get the action to be performed - var params = a._.map( function(p){ return p.toLowerCase().trim(); }); - var verb = opts.help ? 'help' : params[0]; - if( !FCMD.verbs[ verb ] && !FCMD.alias[ verb ] ) { - logMsg(chalk.yellow('Invalid command: "') + chalk.yellow.bold(verb) + chalk.yellow('"')); - return; + function execCommand() { + var argsArray = Array.prototype.slice.call(arguments); + //console.log(argsArray); + return FCMD.verbs[ this.name() ].apply( null, argsArray ); } + // Create the top-level (application) command... + var program = new Command('hackmyresume') + .version(PKG.version) + .description(chalk.yellow.bold('*** HackMyResume ***')) + .option('-s, --silent', 'Run in silent mode.') + .usage('COMMAND [TO ]'); + + // Create the NEW command + program + .command('new') + .arguments('') + .option('-f --format ', 'FRESH or JRS format', 'FRESH') + .alias('create') + .description('Create resume(s) in FRESH or JSON RESUME format.') + .action(function( sources ) { + execCommand.call( this, sources, [], this.opts(), logMsg ); + }); + + // Create the VALIDATE command + program + .command('validate') + .arguments('') + .description('Validate a resume in FRESH or JSON RESUME format.') + .action(function(sources) { + execCommand.call(this, sources, [], this.opts(), logMsg); + }); + + // Create the CONVERT command + program + .command('convert') + //.arguments('') + .description('Convert a resume to/from FRESH or JSON RESUME format.') + .action(function() { + var x = splitSrcDest.call( this ); + execCommand.call( this, x.src, x.dst, this.opts(), logMsg ); + }); + + // Create the ANALYZE command + program + .command('analyze') + .arguments('') + .description('Analyze one or more resumes.') + .action(function() { + execCommand.call(this, sources, [], this.opts(), logMsg); + }); + + // Create the BUILD command + program + .command('build') + .alias('generate') + //.arguments(' TO [targets]') + //.usage('...') + .option('-t --theme ', 'Theme name or path') + .option('-p --prettify', 'Preffity HTML output.') + .description('Generate resume to multiple formats') + .action(function( sources, targets, options ) { + var x = splitSrcDest.call( this ); + execCommand.call( this, x.src, x.dst, opts, logMsg ); + }); + + // program.on('--help', function(){ + // console.log(' Examples:'); + // console.log(''); + // console.log(' $ custom-help --help'); + // console.log(' $ custom-help -h'); + // console.log(''); + // }); + + program.parse( args ); + + if (!program.args.length) { throw { fluenterror: 4 }; } + +} + + + +/** +Split multiple command-line filenames by the 'TO' keyword +*/ +function splitSrcDest() { + + var params = this.parent.args.filter(function(j) { return String.is(j); }); + if( params.length === 0 ) + throw { fluenterror: HACKMYSTATUS.resumeNotFound }; + // Find the TO keyword, if any - var splitAt = _.indexOf( params, 'to' ); - if( splitAt === a._.length - 1 && splitAt !== -1 ) { - // 'TO' cannot be the last argument - logMsg(chalk.yellow('Please ') + chalk.yellow.bold('specify an output file') + - chalk.yellow(' for this operation or ') + chalk.yellow.bold('omit the TO keyword') + + var splitAt = _.findIndex( params, function(p) { + return p.toLowerCase() === 'to'; + }); + + // TO can't be the last keyword + if( splitAt === params.length - 1 && splitAt !== -1 ) { + logMsg(chalk.yellow('Please ') + + chalk.yellow.bold('specify an output file') + + chalk.yellow(' for this operation or ') + + chalk.yellow.bold('omit the TO keyword') + chalk.yellow('.') ); return; } - // Massage inputs and outputs - var src = a._.slice(1, splitAt === -1 ? undefined : splitAt ); - var dst = splitAt === -1 ? [] : a._.slice( splitAt + 1 ); - - // Invoke the action - (FCMD.verbs[verb] || FCMD.alias[verb]).apply(null, [src, dst, opts, logMsg]); - + return { + src: params.slice(0, splitAt === -1 ? undefined : splitAt ), + dst: splitAt === -1 ? [] : params.slice( splitAt + 1 ) + }; } + +/** +Massage command-line args and setup Commander.js. +*/ +function initialize() { + + // Support case-insensitive sub-commands (build, generate, validate, etc.).. + var oVerb, verb = '', args = process.argv.slice(), cleanArgs = args.slice(2); + if( cleanArgs.length ) { + var verbIdx = _.findIndex( cleanArgs, function(v){ return v[0] !== '-'; }); + if( verbIdx !== -1 ) { + oVerb = cleanArgs[ verbIdx ]; + verb = args[ verbIdx + 2 ] = oVerb.trim().toLowerCase(); + } + } + + // Handle invalid verbs here (a bit easier here than in commander.js)... + if( !FCMD.verbs[ verb ] && !FCMD.alias[ verb ] ) { + throw { fluenterror: HACKMYSTATUS.invalidCommand, shouldExit: true, + attempted: oVerb }; + } + + // Override the .missingArgument behavior + Command.prototype.missingArgument = function(name) { + throw { fluenterror: HACKMYSTATUS.resumeNotFound }; + }; + + // Override the .helpInformation behavior + Command.prototype.helpInformation = function() { + var manPage = FS.readFileSync( PATH.join(__dirname, 'use.txt'), 'utf8' ); + return chalk.green.bold(manPage); + }; + + return args; +} + + + +/** +Simple logging placeholder. +*/ function logMsg( msg ) { opts.silent || console.log( msg ); } + + +/** +Fetch options from command line arguments. +*/ function getOpts( args ) { var noPretty = args.nopretty || args.n; noPretty = noPretty && (noPretty === true || noPretty === 'true'); diff --git a/src/verbs/convert.js b/src/verbs/convert.js index e5e483a..d050ec0 100644 --- a/src/verbs/convert.js +++ b/src/verbs/convert.js @@ -11,7 +11,8 @@ Implementation of the 'convert' verb for HackMyResume. var ResumeFactory = require('../core/resume-factory') - , chalk = require('chalk'); + , chalk = require('chalk') + , HACKMYSTATUS = require('../core/status-codes'); @@ -24,12 +25,19 @@ Implementation of the 'convert' verb for HackMyResume. var _log = logger || console.log; if( !srcs || !srcs.length ) { throw { fluenterror: 6 }; } if( !dst || !dst.length ) { - if( srcs.length === 1 ) { throw { fluenterror: 5 }; } - else if( srcs.length === 2 ) { dst = dst || []; dst.push( srcs.pop() ); } - else { throw { fluenterror: 5 }; } + if( srcs.length === 1 ) { + throw { fluenterror: HACKMYSTATUS.inputOutputParity }; + } + else if( srcs.length === 2 ) { + dst = dst || []; dst.push( srcs.pop() ); + } + else { + throw { fluenterror: HACKMYSTATUS.inputOutputParity }; + } + } + if(srcs && dst && srcs.length && dst.length && srcs.length !== dst.length){ + throw { fluenterror: HACKMYSTATUS.inputOutputParity }; } - if( srcs && dst && srcs.length && dst.length && - srcs.length !== dst.length ) { throw { fluenterror: 7 }; } // Load source resumes srcs.forEach( function( src, idx ) { @@ -46,7 +54,7 @@ Implementation of the 'convert' verb for HackMyResume. // TODO: Core should not log _log( chalk.green('Converting ') + chalk.green.bold(rinfo.file) + - chalk.green(' (' + srcFmt + ') to ') + chalk.green.bold(dst[0]) + + chalk.green(' (' + srcFmt + ') to ') + chalk.green.bold(dst[idx]) + chalk.green(' (' + targetFormat + ').')); // Save it to the destination format diff --git a/src/verbs/create.js b/src/verbs/create.js index c2d1c8f..1df9004 100644 --- a/src/verbs/create.js +++ b/src/verbs/create.js @@ -15,6 +15,7 @@ Implementation of the 'create' verb for HackMyResume. Create a new empty resume in either FRESH or JRS format. */ module.exports = function create( src, dst, opts, logger ) { + //console.log(src); var _log = logger || console.log; if( !src || !src.length ) throw { fluenterror: 8 }; src.forEach( function( t ) {