diff --git a/Gruntfile.js b/Gruntfile.js index 53e15ab..36bf717 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -6,6 +6,25 @@ module.exports = function (grunt) { pkg: grunt.file.readJSON('package.json'), + copy: { + main: { + expand: true, + cwd: 'src', + src: ['**/*','!**/*.coffee'], + dest: 'dist/', + }, + }, + + coffee: { + main: { + expand: true, + cwd: 'src', + src: ['**/*.coffee'], + dest: 'dist/', + ext: '.js' + } + }, + simplemocha: { options: { globals: ['expect', 'should'], @@ -27,7 +46,7 @@ module.exports = function (grunt) { } }, - clean: ['test/sandbox'], + clean: ['dist','test/sandbox'], yuidoc: { compile: { @@ -37,7 +56,6 @@ module.exports = function (grunt) { url: '<%= pkg.homepage %>', options: { paths: 'src/', - //themedir: 'path/to/custom/theme/', outdir: 'docs/' } } @@ -54,17 +72,29 @@ module.exports = function (grunt) { }; grunt.initConfig( opts ); - + grunt.loadNpmTasks('grunt-contrib-coffee'); + grunt.loadNpmTasks('grunt-contrib-copy'); grunt.loadNpmTasks('grunt-simple-mocha'); grunt.loadNpmTasks('grunt-contrib-yuidoc'); grunt.loadNpmTasks('grunt-jsdoc'); grunt.loadNpmTasks('grunt-contrib-jshint'); grunt.loadNpmTasks('grunt-contrib-clean'); - grunt.registerTask('test', 'Test the HackMyResume library.', - function( config ) { grunt.task.run(['clean','jshint','simplemocha:all']); }); - grunt.registerTask('document', 'Generate HackMyResume library documentation.', - function( config ) { grunt.task.run( ['jsdoc'] ); }); + grunt.registerTask('test', 'Test the HackMyResume application.', + function( config ) { + grunt.task.run(['build','jshint','simplemocha:all']); + }); + + grunt.registerTask('document', 'Generate HackMyResume documentation.', + function( config ) { + grunt.task.run( ['jsdoc'] ); + }); + + grunt.registerTask('build', 'Build the HackMyResume application.', + function( config ) { + grunt.task.run( ['clean','copy','coffee'] ); + }); + grunt.registerTask('default', [ 'test', 'jsdoc' ]); }; diff --git a/dist/cli/analyze.hbs b/dist/cli/analyze.hbs new file mode 100644 index 0000000..124aa5c --- /dev/null +++ b/dist/cli/analyze.hbs @@ -0,0 +1,30 @@ + +{{style "SECTIONS (" "bold"}}{{style totals.numSections "white" }}{{style ")" "bold"}} + + employment: {{v totals.totals.employment "-" 2 "bold" }} + projects: {{v totals.totals.projects "-" 2 "bold" }} + education: {{v totals.totals.education "-" 2 "bold" }} + service: {{v totals.totals.service "-" 2 "bold" }} + skills: {{v totals.totals.skills "-" 2 "bold" }} + writing: {{v totals.totals.writing "-" 2 "bold" }} + speaking: {{v totals.totals.speaking "-" 2 "bold" }} + reading: {{v totals.totals.reading "-" 2 "bold" }} + social: {{v totals.totals.social "-" 2 "bold" }} + references: {{v totals.totals.references "-" 2 "bold" }} + testimonials: {{v totals.totals.testimonials "-" 2 "bold" }} + languages: {{v totals.totals.languages "-" 2 "bold" }} + interests: {{v totals.totals.interests "-" 2 "bold" }} + +{{style "COVERAGE (" "bold"}}{{style coverage.pct "white"}}{{style ")" "bold"}} + + Total Days: {{v coverage.duration.total "-" 5 "bold" }} + Employed: {{v coverage.duration.work "-" 5 "bold" }} + Gaps: {{v coverage.gaps.length "-" 5 "bold" }} [{{#if coverage.gaps.length }}{{#each coverage.gaps }}{{#unless @first}} {{/unless}}{{gapLength duration }}{{/each}}{{/if}}] + Overlaps: {{v coverage.overlaps.length "-" 5 "bold" }} [{{#if coverage.overlaps.length }}{{#each coverage.overlaps }}{{#unless @first}} {{/unless}}{{gapLength duration }}{{/each}}{{/if}}] + +{{style "KEYWORDS (" "bold"}}{{style keywords.length "white" }}{{style ")" "bold"}} + +{{#each keywords }}{{{pad name 18}}}: {{v count "-" 5 "bold"}} mention{{#isPlural count}}s{{/isPlural}} +{{/each}} + ------------------------------- +{{v keywords.length "0" 9 "bold"}} {{style "KEYWORDS" "bold"}} {{v keywords.totalKeywords "0" 5 "bold"}} {{style "mentions" "bold"}} diff --git a/dist/cli/error.js b/dist/cli/error.js new file mode 100644 index 0000000..28b93c9 --- /dev/null +++ b/dist/cli/error.js @@ -0,0 +1,280 @@ +/** +Error-handling routines for HackMyResume. +@module cli/error +@license MIT. See LICENSE.md for details. +*/ + + + +(function() { + + + + var HMSTATUS = require('hackmycore/src/core/status-codes') + , PKG = require('../../package.json') + , FS = require('fs') + , FCMD = require('hackmycore') + , PATH = require('path') + , WRAP = require('word-wrap') + , M2C = require('hackmycore/src/utils/md2chalk.js') + , chalk = require('chalk') + , extend = require('extend') + , YAML = require('yamljs') + , printf = require('printf') + , SyntaxErrorEx = require('hackmycore/src/utils/syntax-error-ex'); + require('string.prototype.startswith'); + + + + /** + Error handler for HackMyResume. All errors are handled here. + @class ErrorHandler + */ + var ErrorHandler = module.exports = { + + + + init: function( debug, assert, silent ) { + this.debug = debug; + this.assert = assert; + this.silent = silent; + this.msgs = require('./msg.js').errors; + return this; + }, + + + + err: function( ex, shouldExit ) { + + // Short-circuit logging output if --silent is on + var o = this.silent ? function() { } : _defaultLog; + + // Special case; can probably be removed. + if( ex.pass ) throw ex; + + // Load error messages + this.msgs = this.msgs || require('./msg.js').errors; + + // Handle packaged HMR exceptions + if( ex.fluenterror ) { + + // Output the error message + var objError = assembleError.call( this, ex ); + o( this[ 'format_' + objError.etype ]( objError.msg )); + + // Output the stack (sometimes) + if( objError.withStack ) { + var stack = ex.stack || (ex.inner && ex.inner.stack); + stack && o( chalk.gray( stack ) ); + } + + // Quit if necessary + if( ex.quit || objError.quit ) { + this.debug && o( + chalk.cyan('Exiting with error code ' + ex.fluenterror.toString())); + if( this.assert ) { ex.pass = true; throw ex; } + process.exit( ex.fluenterror ); + } + + } + + // Handle raw exceptions + else { + o( ex ); + var stackTrace = ex.stack || (ex.inner && ex.inner.stack); + if( stackTrace && this.debug ) + o( M2C(ex.stack || ex.inner.stack, 'gray') ); + } + + }, + + + + format_error: function( msg ) { + msg = msg || ''; + return chalk.red.bold( + msg.toUpperCase().startsWith('ERROR:') ? msg : 'Error: ' + msg ); + }, + + + + format_warning: function( brief, msg ) { + return chalk.yellow(brief) + chalk.yellow(msg || ''); + }, + + + format_custom: function( msg ) { + return msg; + } + + + + }; + + + + function _defaultLog() { + console.log.apply( console.log, arguments ); + } + + + + function assembleError( ex ) { + + var msg = '', withStack = false, quit = false, etype = 'warning'; + if( this.debug ) withStack = true; + + switch( ex.fluenterror ) { + + case HMSTATUS.themeNotFound: + msg = printf( M2C( this.msgs.themeNotFound.msg, 'yellow' ), ex.data); + break; + + case HMSTATUS.copyCSS: + msg = M2C( this.msgs.copyCSS.msg, 'red' ); + quit = false; + break; + + case HMSTATUS.resumeNotFound: + msg = M2C( this.msgs.resumeNotFound.msg, 'yellow' ); + break; + + case HMSTATUS.missingCommand: + msg = M2C( this.msgs.missingCommand.msg + " (", 'yellow'); + msg += Object.keys( FCMD.verbs ).map( function(v, idx, ar) { + return (idx === ar.length - 1 ? chalk.yellow('or ') : '') + + chalk.yellow.bold(v.toUpperCase()); + }).join( chalk.yellow(', ')) + chalk.yellow(").\n\n"); + + msg += chalk.gray(FS.readFileSync( + PATH.resolve(__dirname, '../cli/use.txt'), 'utf8' )); + break; + + case HMSTATUS.invalidCommand: + msg = printf( M2C( this.msgs.invalidCommand.msg, 'yellow'), ex.attempted ); + break; + + case HMSTATUS.resumeNotFoundAlt: + msg = M2C( this.msgs.resumeNotFoundAlt.msg, 'yellow' ); + break; + + case HMSTATUS.inputOutputParity: + msg = M2C( this.msgs.inputOutputParity.msg ); + break; + + case HMSTATUS.createNameMissing: + msg = M2C( this.msgs.createNameMissing.msg ); + break; + + case HMSTATUS.pdfGeneration: + msg = M2C( this.msgs.pdfGeneration.msg, 'bold' ); + if( ex.inner ) msg += chalk.red('\n' + ex.inner); + withStack = true; quit = false; etype = 'error'; + break; + + case HMSTATUS.invalid: + msg = M2C( this.msgs.invalid.msg, 'red' ); + etype = 'error'; + break; + + case HMSTATUS.generateError: + msg = (ex.inner && ex.inner.toString()) || ex; + quit = false; + etype = 'error'; + break; + + case HMSTATUS.fileSaveError: + msg = printf( M2C( this.msgs.fileSaveError.msg ), (ex.inner || ex).toString() ); + etype = 'error'; + quit = false; + break; + + case HMSTATUS.invalidFormat: + ex.data.forEach(function(d){ + msg += printf( M2C( this.msgs.invalidFormat.msg, 'bold' ), + ex.theme.name.toUpperCase(), d.format.toUpperCase()); + }, this); + break; + + case HMSTATUS.missingParam: + msg = printf( M2C( this.msgs.missingParam.msg ), ex.expected, ex.helper ); + break; + + case HMSTATUS.invalidHelperUse: + msg = printf( M2C( this.msgs.invalidHelperUse.msg ), ex.helper ); + if( ex.error ) { + msg += '\n--> ' + assembleError.call( this, extend( true, {}, ex, {fluenterror: ex.error} )).msg; + //msg += printf( '\n--> ' + M2C( this.msgs.invalidParamCount.msg ), ex.expected ); + } + quit = false; + etype = 'warning'; + break; + + case HMSTATUS.notOnPath: + msg = printf( M2C(this.msgs.notOnPath.msg, 'bold'), ex.engine); + quit = false; + etype = 'error'; + break; + + case HMSTATUS.readError: + if( !ex.quiet ) + console.error(printf( M2C(this.msgs.readError.msg, 'red'), ex.file)); + msg = ex.inner.toString(); + etype = 'error'; + break; + + case HMSTATUS.mixedMerge: + msg = M2C( this.msgs.mixedMerge.msg ); + quit = false; + break; + + case HMSTATUS.invokeTemplate: + msg = M2C( this.msgs.invokeTemplate.msg, 'red' ); + msg += M2C( '\n' + WRAP(ex.inner.toString(), { width: 60, indent: ' ' }), 'gray' ); + etype = 'custom'; + break; + + case HMSTATUS.compileTemplate: + etype = 'error'; + break; + + case HMSTATUS.themeLoad: + msg = M2C( printf( this.msgs.themeLoad.msg, ex.attempted.toUpperCase() ), 'red'); + if( ex.inner && ex.inner.fluenterror ) { + msg += M2C('\nError: ', 'red') + assembleError.call( this, ex.inner ).msg; + } + quit = true; + etype = 'custom'; + break; + + case HMSTATUS.parseError: + if( SyntaxErrorEx.is( ex.inner )) { + console.error( printf( M2C(this.msgs.readError.msg, 'red'), ex.file ) ); + var se = new SyntaxErrorEx( ex, ex.raw ); + msg = printf( M2C( this.msgs.parseError.msg, 'red' ), + se.line, se.col); + } + else if( ex.inner && ex.inner.line !== undefined && ex.inner.col !== undefined ) { + msg = printf( M2C( this.msgs.parseError.msg, 'red' ), + ex.inner.line, ex.inner.col); + } + else { + msg = ex; + } + etype = 'error'; + break; + + } + + return { + msg: msg, // The error message to display + withStack: withStack, // Whether to include the stack + quit: quit, + etype: etype + }; + } + + + + +}()); diff --git a/dist/cli/main.js b/dist/cli/main.js new file mode 100644 index 0000000..cb83991 --- /dev/null +++ b/dist/cli/main.js @@ -0,0 +1,279 @@ + +/** +Definition of the `main` function. +@module cli/main +@license MIT. See LICENSE.md for details. + */ + +(function() { + var Command, EXTEND, FS, HME, HMR, HMSTATUS, OUTPUT, PAD, PATH, PKG, StringUtils, _, _opts, _out, _title, chalk, execute, initOptions, initialize, loadOptions, logMsg, main, safeLoadJSON, splitSrcDest; + + HMR = require('hackmycore'); + + PKG = require('../../package.json'); + + FS = require('fs'); + + EXTEND = require('extend'); + + chalk = require('chalk'); + + PATH = require('path'); + + HMSTATUS = require('hackmycore/src/core/status-codes'); + + HME = require('hackmycore/src/core/event-codes'); + + safeLoadJSON = require('hackmycore/src/utils/safe-json-loader'); + + StringUtils = require('hackmycore/src/utils/string.js'); + + _ = require('underscore'); + + OUTPUT = require('./out'); + + PAD = require('string-padding'); + + Command = require('commander').Command; + + _opts = {}; + + _title = chalk.white.bold('\n*** HackMyResume v' + PKG.version + ' ***'); + + _out = new OUTPUT(_opts); + + + /* + A callable implementation of the HackMyResume CLI. Encapsulates the command + line interface as a single method accepting a parameter array. + @alias module:cli/main.main + @param rawArgs {Array} An array of command-line parameters. Will either be + process.argv (in production) or custom parameters (in test). + */ + + main = module.exports = function(rawArgs) { + var args, initInfo, program; + initInfo = initialize(rawArgs); + args = initInfo.args; + program = new Command('hackmyresume').version(PKG.version).description(chalk.yellow.bold('*** HackMyResume ***')).option('-s --silent', 'Run in silent mode').option('--no-color', 'Disable colors').option('--color', 'Enable colors').option('-d --debug', 'Enable diagnostics', false).option('-a --assert', 'Treat warnings as errors', false).option('-v --version', 'Show the version').allowUnknownOption(); + program.jsonArgs = initInfo.options; + 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) { + execute.call(this, sources, [], this.opts(), logMsg); + })); + program.command('validate')["arguments"]('').description('Validate a resume in FRESH or JSON RESUME format.').action(function(sources) { + execute.call(this, sources, [], this.opts(), logMsg); + }); + program.command('convert').description('Convert a resume to/from FRESH or JSON RESUME format.').action(function() { + var x; + x = splitSrcDest.call(this); + execute.call(this, x.src, x.dst, this.opts(), logMsg); + }); + program.command('analyze')["arguments"]('').description('Analyze one or more resumes.').action(function(sources) { + execute.call(this, sources, [], this.opts(), logMsg); + }); + program.command('peek')["arguments"]('').description('Peek at a resume field or section').action(function(sources, sectionOrField) { + var dst; + dst = sources && sources.length > 1 ? [sources.pop()] : []; + execute.call(this, sources, dst, this.opts(), logMsg); + }); + program.command('build').alias('generate').option('-t --theme ', 'Theme name or path').option('-n --no-prettify', 'Disable HTML prettification', true).option('-c --css