From 00067d012ab73bc0bc8290d08f1e9c924a110e68 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Sat, 3 Feb 2018 16:15:17 -0500 Subject: [PATCH 01/30] fix: correctly replace frozen fields in JRS-themed resumes --- dist/renderers/jrs-generator.js | 4 ++-- src/renderers/jrs-generator.coffee | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dist/renderers/jrs-generator.js b/dist/renderers/jrs-generator.js index 23b1bc8..99baab5 100644 --- a/dist/renderers/jrs-generator.js +++ b/dist/renderers/jrs-generator.js @@ -46,8 +46,8 @@ Definition of the JRSGenerator class. turnoff.forEach(function(c, idx) { return console[c] = org[idx]; }); - return rezHtml = rezHtml.replace(/@@@@~.*?~@@@@/gm, function(val) { - return MDIN(val.replace(/~@@@@/gm, '').replace(/@@@@~/gm, '')); + return rezHtml = rezHtml.replace(/@@@@~[\s\S]*?~@@@@/g, function(val) { + return MDIN(val.replace(/~@@@@/g, '').replace(/@@@@~/g, '')); }); } }; diff --git a/src/renderers/jrs-generator.coffee b/src/renderers/jrs-generator.coffee index 6a39612..6279a3e 100644 --- a/src/renderers/jrs-generator.coffee +++ b/src/renderers/jrs-generator.coffee @@ -37,8 +37,8 @@ JRSGenerator = module.exports = turnoff.forEach (c, idx) -> console[c] = org[idx] # Unfreeze and apply Markdown - rezHtml = rezHtml.replace /@@@@~.*?~@@@@/gm, (val) -> - MDIN( val.replace( /~@@@@/gm,'' ).replace( /@@@@~/gm,'' ) ) + rezHtml = rezHtml.replace /@@@@~[\s\S]*?~@@@@/g, (val) -> + MDIN( val.replace( /~@@@@/g,'' ).replace( /@@@@~/g,'' ) ) MDIN = (txt) -> # TODO: Move this From 55196c2766858fc54c8470b7c7cb1a055ae48155 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Sun, 4 Feb 2018 01:13:02 -0500 Subject: [PATCH 02/30] fix: prevent weird characters in date fields --- dist/helpers/handlebars-helpers.js | 2 +- src/helpers/handlebars-helpers.coffee | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dist/helpers/handlebars-helpers.js b/dist/helpers/handlebars-helpers.js index 982c231..65c1532 100644 --- a/dist/helpers/handlebars-helpers.js +++ b/dist/helpers/handlebars-helpers.js @@ -33,7 +33,7 @@ Template helper definitions for Handlebars. helpers.type = 'handlebars'; wrappedHelpers = _.mapObject(helpers, function(hVal, hKey) { if (_.isFunction(hVal)) { - _.wrap(hVal, function(func) { + return _.wrap(hVal, function(func) { var args; args = Array.prototype.slice.call(arguments); args.shift(); diff --git a/src/helpers/handlebars-helpers.coffee b/src/helpers/handlebars-helpers.coffee index e4ea69d..871e356 100644 --- a/src/helpers/handlebars-helpers.coffee +++ b/src/helpers/handlebars-helpers.coffee @@ -25,7 +25,7 @@ module.exports = ( theme, opts ) -> wrappedHelpers = _.mapObject helpers, ( hVal, hKey ) -> if _.isFunction hVal - _.wrap hVal, (func) -> + return _.wrap hVal, (func) -> args = Array.prototype.slice.call arguments args.shift() # lose the 1st element (func) args.pop() # lose the last element (the Handlebars options hash) From caca653666f24271a02f7db095998f34be6dd5da Mon Sep 17 00:00:00 2001 From: hacksalot Date: Sun, 4 Feb 2018 01:43:51 -0500 Subject: [PATCH 03/30] style: remove unnecessary expression --- dist/renderers/handlebars-generator.js | 2 +- src/renderers/handlebars-generator.coffee | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dist/renderers/handlebars-generator.js b/dist/renderers/handlebars-generator.js index 8955215..53df326 100644 --- a/dist/renderers/handlebars-generator.js +++ b/dist/renderers/handlebars-generator.js @@ -39,7 +39,7 @@ Definition of the HandlebarsGenerator class. template = HANDLEBARS.compile(tpl, { strict: false, assumeObjects: false, - noEscape: data.opts.noescape || false + noEscape: data.opts.noescape }); return template(data); } catch (_error) { diff --git a/src/renderers/handlebars-generator.coffee b/src/renderers/handlebars-generator.coffee index fa590fe..0041bb1 100644 --- a/src/renderers/handlebars-generator.coffee +++ b/src/renderers/handlebars-generator.coffee @@ -30,7 +30,7 @@ HandlebarsGenerator = module.exports = try # Compile and run the Handlebars template. template = HANDLEBARS.compile tpl, - strict: false, assumeObjects: false, noEscape: data.opts.noescape || false + strict: false, assumeObjects: false, noEscape: data.opts.noescape return template data catch throw From 81d9d5f157bbf1fbb5979919d2b70710b9878a3c Mon Sep 17 00:00:00 2001 From: hacksalot Date: Sun, 4 Feb 2018 04:06:49 -0500 Subject: [PATCH 04/30] test: add fresh-theme-underscore to suite --- package.json | 2 +- test/scripts/test-themes.js | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 4bf94af..1ca9b36 100644 --- a/package.json +++ b/package.json @@ -90,7 +90,7 @@ "chai-as-promised": "^7.1.1", "dir-compare": "0.0.2", "fresh-test-resumes": "^0.9.1", - "fresh-test-themes": "^0.1.0", + "fresh-test-themes": "^0.2.0", "grunt": "*", "grunt-cli": "^0.1.13", "grunt-contrib-clean": "^0.7.0", diff --git a/test/scripts/test-themes.js b/test/scripts/test-themes.js index 755350e..95375d0 100644 --- a/test/scripts/test-themes.js +++ b/test/scripts/test-themes.js @@ -56,8 +56,9 @@ function genThemes( title, src, fmt ) { // Run the command! var v = new HackMyResume.verbs.build(); v.on('hmr:error', function(ex) { - console.log('Error thrown'); - assert(false); + console.error('Error thrown: %o', ex); + throw ex; + //assert(false); }); var p = v.invoke( src, dst, opts ); @@ -74,7 +75,7 @@ function genThemes( title, src, fmt ) { //genTheme(fmt, src, 'hello-world'); genTheme(fmt, src, 'compact'); genTheme(fmt, src, 'modern'); - //genTheme(fmt, src, 'underscore'); + genTheme(fmt, src, 'underscore', 'node_modules/fresh-test-themes/node_modules/fresh-theme-underscore'); //genTheme(fmt, src, 'awesome'); genTheme(fmt, src, 'positive'); genTheme(fmt, src, 'jsonresume-theme-boilerplate', From f1343add71a697ff08fd61117da5db1d23978d8c Mon Sep 17 00:00:00 2001 From: hacksalot Date: Sun, 4 Feb 2018 04:44:47 -0500 Subject: [PATCH 05/30] test: remove hard-coded submodule path --- test/scripts/test-themes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/scripts/test-themes.js b/test/scripts/test-themes.js index 95375d0..266db28 100644 --- a/test/scripts/test-themes.js +++ b/test/scripts/test-themes.js @@ -75,7 +75,7 @@ function genThemes( title, src, fmt ) { //genTheme(fmt, src, 'hello-world'); genTheme(fmt, src, 'compact'); genTheme(fmt, src, 'modern'); - genTheme(fmt, src, 'underscore', 'node_modules/fresh-test-themes/node_modules/fresh-theme-underscore'); + genTheme(fmt, src, 'underscore', path.parse( require.resolve('fresh-theme-underscore') ).dir ); //genTheme(fmt, src, 'awesome'); genTheme(fmt, src, 'positive'); genTheme(fmt, src, 'jsonresume-theme-boilerplate', From 2767b16b47585164d2b43c2cc03e31381a09f930 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Sun, 4 Feb 2018 05:34:47 -0500 Subject: [PATCH 06/30] test: use direct dependency for fresh-resume-underscore --- package.json | 1 + test/scripts/test-themes.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 1ca9b36..be2cfc1 100644 --- a/package.json +++ b/package.json @@ -91,6 +91,7 @@ "dir-compare": "0.0.2", "fresh-test-resumes": "^0.9.1", "fresh-test-themes": "^0.2.0", + "fresh-theme-underscore": "^0.1.1", "grunt": "*", "grunt-cli": "^0.1.13", "grunt-contrib-clean": "^0.7.0", diff --git a/test/scripts/test-themes.js b/test/scripts/test-themes.js index 266db28..2dbf208 100644 --- a/test/scripts/test-themes.js +++ b/test/scripts/test-themes.js @@ -75,7 +75,7 @@ function genThemes( title, src, fmt ) { //genTheme(fmt, src, 'hello-world'); genTheme(fmt, src, 'compact'); genTheme(fmt, src, 'modern'); - genTheme(fmt, src, 'underscore', path.parse( require.resolve('fresh-theme-underscore') ).dir ); + genTheme(fmt, src, 'underscore', 'node_modules/fresh-theme-underscore' ); //genTheme(fmt, src, 'awesome'); genTheme(fmt, src, 'positive'); genTheme(fmt, src, 'jsonresume-theme-boilerplate', From 8dca5b76e7fe6df1b36d0d37f10465f7d7b521b5 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Sun, 4 Feb 2018 22:49:58 -0500 Subject: [PATCH 07/30] refactor: remove AbstractResume base class (1) AbstractResume adds complexity without contributing utility. There's not really a clean "class" abstraction in JavaScript to begin with; CoffeeScript classes, as nice as they are syntactically, occlude the issue even further. (2) AbstractResume currently functions as a container for exactly two functions which arguably should live outside the resume class anyway. --- dist/core/abstract-resume.js | 113 ----------------------- dist/core/fresh-resume.js | 25 ++--- dist/core/jrs-resume.js | 25 ++--- dist/inspectors/duration-inspector.js | 56 +++++++++++ dist/utils/resume-scrubber.js | 45 +++++++++ src/core/abstract-resume.coffee | 98 -------------------- src/core/fresh-resume.coffee | 10 +- src/core/jrs-resume.coffee | 9 +- src/inspectors/duration-inspector.coffee | 44 +++++++++ src/utils/resume-scrubber.coffee | 47 ++++++++++ 10 files changed, 223 insertions(+), 249 deletions(-) delete mode 100644 dist/core/abstract-resume.js create mode 100644 dist/inspectors/duration-inspector.js create mode 100644 dist/utils/resume-scrubber.js delete mode 100644 src/core/abstract-resume.coffee create mode 100644 src/inspectors/duration-inspector.coffee create mode 100644 src/utils/resume-scrubber.coffee diff --git a/dist/core/abstract-resume.js b/dist/core/abstract-resume.js deleted file mode 100644 index ce0e81e..0000000 --- a/dist/core/abstract-resume.js +++ /dev/null @@ -1,113 +0,0 @@ - -/** -Definition of the AbstractResume class. -@license MIT. See LICENSE.md for details. -@module core/abstract-resume - */ - -(function() { - var AbstractResume, FluentDate, _, __; - - _ = require('underscore'); - - __ = require('lodash'); - - FluentDate = require('./fluent-date'); - - AbstractResume = (function() { - function AbstractResume() {} - - - /** - Compute the total duration of the work history. - @returns The total duration of the sheet's work history, that is, the number - of years between the start date of the earliest job on the resume and the - *latest end date of all jobs in the work history*. This last condition is for - sheets that have overlapping jobs. - */ - - AbstractResume.prototype.duration = function(collKey, startKey, endKey, unit) { - var firstDate, hist, lastDate, new_e; - unit = unit || 'years'; - hist = __.get(this, collKey); - if (!hist || !hist.length) { - return 0; - } - new_e = hist.map(function(job) { - var obj; - obj = _.pick(job, [startKey, endKey]); - if (!_.has(obj, endKey)) { - obj[endKey] = 'current'; - } - if (obj && (obj[startKey] || obj[endKey])) { - obj = _.pairs(obj); - obj[0][1] = FluentDate.fmt(obj[0][1]); - if (obj.length > 1) { - obj[1][1] = FluentDate.fmt(obj[1][1]); - } - } - return obj; - }); - new_e = _.filter(_.flatten(new_e, true), function(v) { - return v && v.length && v[0] && v[0].length; - }); - if (!new_e || !new_e.length) { - return 0; - } - new_e = _.sortBy(new_e, function(elem) { - return elem[1].unix(); - }); - firstDate = _.first(new_e)[1]; - lastDate = _.last(new_e)[1]; - return lastDate.diff(firstDate, unit); - }; - - - /** - Removes ignored or private fields from a resume object - @returns an object with the following structure: - { - scrubbed: the processed resume object - ignoreList: an array of ignored nodes that were removed - privateList: an array of private nodes that were removed - } - */ - - AbstractResume.prototype.scrubResume = function(rep, opts) { - var ignoreList, includePrivates, privateList, scrubbed, traverse; - traverse = require('traverse'); - ignoreList = []; - privateList = []; - includePrivates = opts && opts["private"]; - scrubbed = traverse(rep).map(function() { - if (!this.isLeaf) { - if (this.node.ignore === true || this.node.ignore === 'true') { - ignoreList.push(this.node); - this["delete"](); - } else if ((this.node["private"] === true || this.node["private"] === 'true') && !includePrivates) { - privateList.push(this.node); - this["delete"](); - } - } - if (_.isArray(this.node)) { - this.after(function() { - this.update(_.compact(this.node)); - }); - } - }); - return { - scrubbed: scrubbed, - ingoreList: ignoreList, - privateList: privateList - }; - }; - - return AbstractResume; - - })(); - - module.exports = AbstractResume; - -}).call(this); - -//# sourceMappingURL=abstract-resume.js.map diff --git a/dist/core/fresh-resume.js b/dist/core/fresh-resume.js index c256e63..344c421 100644 --- a/dist/core/fresh-resume.js +++ b/dist/core/fresh-resume.js @@ -6,9 +6,7 @@ Definition of the FRESHResume class. */ (function() { - var AbstractResume, CONVERTER, FS, FluentDate, FreshResume, JRSResume, MD, PATH, XML, _, __, _parseDates, extend, moment, validator, - extend1 = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, - hasProp = {}.hasOwnProperty; + var CONVERTER, FS, FluentDate, FreshResume, JRSResume, MD, PATH, XML, _, __, _parseDates, extend, moment, validator; FS = require('fs'); @@ -34,8 +32,6 @@ Definition of the FRESHResume class. FluentDate = require('./fluent-date'); - AbstractResume = require('./abstract-resume'); - /** A FRESH resume or CV. FRESH resumes are backed by JSON, and each FreshResume @@ -43,12 +39,8 @@ Definition of the FRESHResume class. @constructor */ - FreshResume = (function(superClass) { - extend1(FreshResume, superClass); - - function FreshResume() { - return FreshResume.__super__.constructor.apply(this, arguments); - } + FreshResume = (function() { + function FreshResume() {} /** Initialize the the FreshResume from JSON string data. */ @@ -77,9 +69,10 @@ Definition of the FRESHResume class. */ FreshResume.prototype.parseJSON = function(rep, opts) { - var ignoreList, privateList, ref, ref1, scrubbed; + var ignoreList, privateList, ref, ref1, scrubbed, scrubber; if (opts && opts.privatize) { - ref = this.scrubResume(rep, opts), scrubbed = ref.scrubbed, ignoreList = ref.ignoreList, privateList = ref.privateList; + scrubber = require('../utils/resume-scrubber'); + ref = scrubber.scrubResume(rep, opts), scrubbed = ref.scrubbed, ignoreList = ref.ignoreList, privateList = ref.privateList; } extend(true, this, opts && opts.privatize ? scrubbed : rep); if (!((ref1 = this.imp) != null ? ref1.processed : void 0)) { @@ -368,7 +361,9 @@ Definition of the FRESHResume class. }; FreshResume.prototype.duration = function(unit) { - return FreshResume.__super__.duration.call(this, 'employment.history', 'start', 'end', unit); + var inspector; + inspector = require('../inspectors/duration-inspector'); + return inspector.run(this, 'employment.history', 'start', 'end', unit); }; @@ -415,7 +410,7 @@ Definition of the FRESHResume class. return FreshResume; - })(AbstractResume); + })(); /** diff --git a/dist/core/jrs-resume.js b/dist/core/jrs-resume.js index be078ad..69f7396 100644 --- a/dist/core/jrs-resume.js +++ b/dist/core/jrs-resume.js @@ -6,9 +6,7 @@ Definition of the JRSResume class. */ (function() { - var AbstractResume, CONVERTER, FS, JRSResume, MD, PATH, _, _parseDates, extend, moment, validator, - extend1 = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, - hasProp = {}.hasOwnProperty; + var CONVERTER, FS, JRSResume, MD, PATH, _, _parseDates, extend, moment, validator; FS = require('fs'); @@ -26,8 +24,6 @@ Definition of the JRSResume class. moment = require('moment'); - AbstractResume = require('./abstract-resume'); - /** A JRS resume or CV. JRS resumes are backed by JSON, and each JRSResume object @@ -35,14 +31,10 @@ Definition of the JRSResume class. @class JRSResume */ - JRSResume = (function(superClass) { + JRSResume = (function() { var clear; - extend1(JRSResume, superClass); - - function JRSResume() { - return JRSResume.__super__.constructor.apply(this, arguments); - } + function JRSResume() {} /** Initialize the the JSResume from string. */ @@ -71,10 +63,11 @@ Definition of the JRSResume class. */ JRSResume.prototype.parseJSON = function(rep, opts) { - var ignoreList, privateList, ref, ref1, scrubbed; + var ignoreList, privateList, ref, ref1, scrubbed, scrubber; opts = opts || {}; if (opts.privatize) { - ref = this.scrubResume(rep, opts), scrubbed = ref.scrubbed, ignoreList = ref.ignoreList, privateList = ref.privateList; + scrubber = require('../utils/resume-scrubber'); + ref = scrubber.scrubResume(rep, opts), scrubbed = ref.scrubbed, ignoreList = ref.ignoreList, privateList = ref.privateList; } extend(true, this, opts.privatize ? scrubbed : rep); if (!((ref1 = this.imp) != null ? ref1.processed : void 0)) { @@ -239,7 +232,9 @@ Definition of the JRSResume class. }; JRSResume.prototype.duration = function(unit) { - return JRSResume.__super__.duration.call(this, 'work', 'startDate', 'endDate', unit); + var inspector; + inspector = require('../inspectors/duration-inspector'); + return inspector.run(this, 'work', 'startDate', 'endDate', unit); }; @@ -339,7 +334,7 @@ Definition of the JRSResume class. return JRSResume; - })(AbstractResume); + })(); /** Get the default (empty) sheet. */ diff --git a/dist/inspectors/duration-inspector.js b/dist/inspectors/duration-inspector.js new file mode 100644 index 0000000..5b048a2 --- /dev/null +++ b/dist/inspectors/duration-inspector.js @@ -0,0 +1,56 @@ +(function() { + var FluentDate, _; + + FluentDate = require('../core/fluent-date'); + + _ = require('underscore'); + + module.exports = { + + /** + Compute the total duration of the work history. + @returns The total duration of the sheet's work history, that is, the number + of years between the start date of the earliest job on the resume and the + *latest end date of all jobs in the work history*. This last condition is for + sheets that have overlapping jobs. + */ + run: function(rez, collKey, startKey, endKey, unit) { + var firstDate, hist, lastDate, new_e; + unit = unit || 'years'; + hist = __.get(rez, collKey); + if (!hist || !hist.length) { + return 0; + } + new_e = hist.map(function(job) { + var obj; + obj = _.pick(job, [startKey, endKey]); + if (!_.has(obj, endKey)) { + obj[endKey] = 'current'; + } + if (obj && (obj[startKey] || obj[endKey])) { + obj = _.pairs(obj); + obj[0][1] = FluentDate.fmt(obj[0][1]); + if (obj.length > 1) { + obj[1][1] = FluentDate.fmt(obj[1][1]); + } + } + return obj; + }); + new_e = _.filter(_.flatten(new_e, true), function(v) { + return v && v.length && v[0] && v[0].length; + }); + if (!new_e || !new_e.length) { + return 0; + } + new_e = _.sortBy(new_e, function(elem) { + return elem[1].unix(); + }); + firstDate = _.first(new_e)[1]; + lastDate = _.last(new_e)[1]; + return lastDate.diff(firstDate, unit); + } + }; + +}).call(this); + +//# sourceMappingURL=duration-inspector.js.map diff --git a/dist/utils/resume-scrubber.js b/dist/utils/resume-scrubber.js new file mode 100644 index 0000000..93bc636 --- /dev/null +++ b/dist/utils/resume-scrubber.js @@ -0,0 +1,45 @@ +(function() { + module.exports = { + + /** + Removes ignored or private fields from a resume object + @returns an object with the following structure: + { + scrubbed: the processed resume object + ignoreList: an array of ignored nodes that were removed + privateList: an array of private nodes that were removed + } + */ + scrubResume: function(rep, opts) { + var ignoreList, includePrivates, privateList, scrubbed, traverse; + traverse = require('traverse'); + ignoreList = []; + privateList = []; + includePrivates = opts && opts["private"]; + scrubbed = traverse(rep).map(function() { + if (!this.isLeaf) { + if (this.node.ignore === true || this.node.ignore === 'true') { + ignoreList.push(this.node); + this["delete"](); + } else if ((this.node["private"] === true || this.node["private"] === 'true') && !includePrivates) { + privateList.push(this.node); + this["delete"](); + } + } + if (_.isArray(this.node)) { + this.after(function() { + this.update(_.compact(this.node)); + }); + } + }); + return { + scrubbed: scrubbed, + ingoreList: ignoreList, + privateList: privateList + }; + } + }; + +}).call(this); + +//# sourceMappingURL=resume-scrubber.js.map diff --git a/src/core/abstract-resume.coffee b/src/core/abstract-resume.coffee deleted file mode 100644 index 13ce00c..0000000 --- a/src/core/abstract-resume.coffee +++ /dev/null @@ -1,98 +0,0 @@ -###* -Definition of the AbstractResume class. -@license MIT. See LICENSE.md for details. -@module core/abstract-resume -### - -_ = require 'underscore' -__ = require 'lodash' -FluentDate = require('./fluent-date') - -class AbstractResume - - ###* - Compute the total duration of the work history. - @returns The total duration of the sheet's work history, that is, the number - of years between the start date of the earliest job on the resume and the - *latest end date of all jobs in the work history*. This last condition is for - sheets that have overlapping jobs. - ### - duration: (collKey, startKey, endKey, unit) -> - unit = unit || 'years' - hist = __.get @, collKey - return 0 if !hist or !hist.length - - # BEGIN CODE DUPLICATION --> src/inspectors/gap-inspector.coffee (TODO) - - # Convert the candidate's employment history to an array of dates, - # where each element in the array is a start date or an end date of a - # job -- it doesn't matter which. - new_e = hist.map ( job ) -> - obj = _.pick( job, [startKey, endKey] ) - # Synthesize an end date if this is a "current" gig - obj[endKey] = 'current' if !_.has obj, endKey - if obj && (obj[startKey] || obj[endKey]) - obj = _.pairs obj - obj[0][1] = FluentDate.fmt( obj[0][1] ) - if obj.length > 1 - obj[1][1] = FluentDate.fmt( obj[1][1] ) - obj - - # Flatten the array, remove empties, and sort - new_e = _.filter _.flatten( new_e, true ), (v) -> - return v && v.length && v[0] && v[0].length - return 0 if !new_e or !new_e.length - new_e = _.sortBy new_e, ( elem ) -> return elem[1].unix() - - # END CODE DUPLICATION - - firstDate = _.first( new_e )[1]; - lastDate = _.last( new_e )[1]; - lastDate.diff firstDate, unit - - ###* - Removes ignored or private fields from a resume object - @returns an object with the following structure: - { - scrubbed: the processed resume object - ignoreList: an array of ignored nodes that were removed - privateList: an array of private nodes that were removed - } - ### - scrubResume: (rep, opts) -> - traverse = require 'traverse' - ignoreList = [] - privateList = [] - includePrivates = opts && opts.private - - scrubbed = traverse( rep ).map () -> # [^1] - if !@isLeaf - if @node.ignore == true || @node.ignore == 'true' - ignoreList.push @node - @delete() - else if (@node.private == true || @node.private == 'true') && !includePrivates - privateList.push @node - @delete() - if _.isArray(@node) # [^2] - @after () -> - @update _.compact this.node - return - return - - scrubbed: scrubbed - ingoreList: ignoreList - privateList: privateList - -module.exports = AbstractResume - - -# [^1]: As of v0.6.6, the NPM traverse library has a quirk when attempting -# to remove array elements directly using traverse's `this.remove`. See: -# -# https://github.com/substack/js-traverse/issues/48 -# -# [^2]: The workaround is to use traverse's 'this.delete' to nullify the value -# first, followed by removal with something like _.compact. -# -# https://github.com/substack/js-traverse/issues/48#issuecomment-142607200 -# diff --git a/src/core/fresh-resume.coffee b/src/core/fresh-resume.coffee index 8a7e825..f24a073 100644 --- a/src/core/fresh-resume.coffee +++ b/src/core/fresh-resume.coffee @@ -18,7 +18,6 @@ MD = require 'marked' CONVERTER = require 'fresh-jrs-converter' JRSResume = require './jrs-resume' FluentDate = require './fluent-date' -AbstractResume = require './abstract-resume' @@ -27,7 +26,7 @@ A FRESH resume or CV. FRESH resumes are backed by JSON, and each FreshResume object is an instantiation of that JSON decorated with utility methods. @constructor ### -class FreshResume extends AbstractResume +class FreshResume# extends AbstractResume @@ -55,7 +54,8 @@ class FreshResume extends AbstractResume if opts and opts.privatize # Ignore any element with the 'ignore: true' or 'private: true' designator. - { scrubbed, ignoreList, privateList } = @scrubResume rep, opts + scrubber = require '../utils/resume-scrubber' + { scrubbed, ignoreList, privateList } = scrubber.scrubResume rep, opts # Now apply the resume representation onto this object extend true, @, if opts and opts.privatize then scrubbed else rep @@ -299,7 +299,9 @@ class FreshResume extends AbstractResume duration: (unit) -> - super('employment.history', 'start', 'end', unit) + inspector = require '../inspectors/duration-inspector' + inspector.run @, 'employment.history', 'start', 'end', unit + diff --git a/src/core/jrs-resume.coffee b/src/core/jrs-resume.coffee index e7b2835..2834625 100644 --- a/src/core/jrs-resume.coffee +++ b/src/core/jrs-resume.coffee @@ -14,7 +14,6 @@ PATH = require('path') MD = require('marked') CONVERTER = require('fresh-jrs-converter') moment = require('moment') -AbstractResume = require('./abstract-resume') ###* @@ -22,7 +21,7 @@ A JRS resume or CV. JRS resumes are backed by JSON, and each JRSResume object is an instantiation of that JSON decorated with utility methods. @class JRSResume ### -class JRSResume extends AbstractResume +class JRSResume# extends AbstractResume @@ -49,8 +48,9 @@ class JRSResume extends AbstractResume parseJSON: ( rep, opts ) -> opts = opts || { }; if opts.privatize + scrubber = require '../utils/resume-scrubber' # Ignore any element with the 'ignore: true' or 'private: true' designator. - { scrubbed, ignoreList, privateList } = @scrubResume rep, opts + { scrubbed, ignoreList, privateList } = scrubber.scrubResume rep, opts # Extend resume properties onto ourself. extend true, this, if opts.privatize then scrubbed else rep @@ -188,7 +188,8 @@ class JRSResume extends AbstractResume duration: (unit) -> - super('work', 'startDate', 'endDate', unit) + inspector = require '../inspectors/duration-inspector'; + inspector.run @, 'work', 'startDate', 'endDate', unit diff --git a/src/inspectors/duration-inspector.coffee b/src/inspectors/duration-inspector.coffee new file mode 100644 index 0000000..a93e78b --- /dev/null +++ b/src/inspectors/duration-inspector.coffee @@ -0,0 +1,44 @@ +FluentDate = require '../core/fluent-date' +_ = require 'underscore' + +module.exports = + + ###* + Compute the total duration of the work history. + @returns The total duration of the sheet's work history, that is, the number + of years between the start date of the earliest job on the resume and the + *latest end date of all jobs in the work history*. This last condition is for + sheets that have overlapping jobs. + ### + run: (rez, collKey, startKey, endKey, unit) -> + unit = unit || 'years' + hist = __.get rez, collKey + return 0 if !hist or !hist.length + + # BEGIN CODE DUPLICATION --> src/inspectors/gap-inspector.coffee (TODO) + + # Convert the candidate's employment history to an array of dates, + # where each element in the array is a start date or an end date of a + # job -- it doesn't matter which. + new_e = hist.map ( job ) -> + obj = _.pick( job, [startKey, endKey] ) + # Synthesize an end date if this is a "current" gig + obj[endKey] = 'current' if !_.has obj, endKey + if obj && (obj[startKey] || obj[endKey]) + obj = _.pairs obj + obj[0][1] = FluentDate.fmt( obj[0][1] ) + if obj.length > 1 + obj[1][1] = FluentDate.fmt( obj[1][1] ) + obj + + # Flatten the array, remove empties, and sort + new_e = _.filter _.flatten( new_e, true ), (v) -> + return v && v.length && v[0] && v[0].length + return 0 if !new_e or !new_e.length + new_e = _.sortBy new_e, ( elem ) -> return elem[1].unix() + + # END CODE DUPLICATION + + firstDate = _.first( new_e )[1]; + lastDate = _.last( new_e )[1]; + lastDate.diff firstDate, unit diff --git a/src/utils/resume-scrubber.coffee b/src/utils/resume-scrubber.coffee new file mode 100644 index 0000000..ff54411 --- /dev/null +++ b/src/utils/resume-scrubber.coffee @@ -0,0 +1,47 @@ +module.exports = + + ###* + Removes ignored or private fields from a resume object + @returns an object with the following structure: + { + scrubbed: the processed resume object + ignoreList: an array of ignored nodes that were removed + privateList: an array of private nodes that were removed + } + ### + scrubResume: (rep, opts) -> + traverse = require 'traverse' + ignoreList = [] + privateList = [] + includePrivates = opts && opts.private + + scrubbed = traverse( rep ).map () -> # [^1] + if !@isLeaf + if @node.ignore == true || @node.ignore == 'true' + ignoreList.push @node + @delete() + else if (@node.private == true || @node.private == 'true') && !includePrivates + privateList.push @node + @delete() + if _.isArray(@node) # [^2] + @after () -> + @update _.compact this.node + return + return + + scrubbed: scrubbed + ingoreList: ignoreList + privateList: privateList + + + +# [^1]: As of v0.6.6, the NPM traverse library has a quirk when attempting +# to remove array elements directly using traverse's `this.remove`. See: +# +# https://github.com/substack/js-traverse/issues/48 +# +# [^2]: The workaround is to use traverse's 'this.delete' to nullify the value +# first, followed by removal with something like _.compact. +# +# https://github.com/substack/js-traverse/issues/48#issuecomment-142607200 +# From 8c81a5456509cc472439bbdc4e9575d8cb210d82 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Sun, 4 Feb 2018 23:06:34 -0500 Subject: [PATCH 08/30] fix: resolve issues around @@@@ characters in dates Simplify resume freezing; avoid transformations on foreign fields. Fixes #198 but needs followup to allow users to specify how and when freezing, encoding, and transformations occur. --- dist/core/jrs-resume.js | 40 ++++---------------------------------- src/core/jrs-resume.coffee | 38 ++++++------------------------------ 2 files changed, 10 insertions(+), 68 deletions(-) diff --git a/dist/core/jrs-resume.js b/dist/core/jrs-resume.js index 69f7396..57368e0 100644 --- a/dist/core/jrs-resume.js +++ b/dist/core/jrs-resume.js @@ -285,7 +285,7 @@ Definition of the JRSResume class. */ JRSResume.prototype.harden = function() { - var HD, HDIN, hardenStringsInObject, ret, that; + var HD, HDIN, ret, that, transformer; that = this; ret = this.dupe(); HD = function(txt) { @@ -294,42 +294,10 @@ Definition of the JRSResume class. HDIN = function(txt) { return HD(txt); }; - hardenStringsInObject = function(obj, inline) { - if (!obj) { - return; - } - inline = inline === void 0 || inline; - if (Object.prototype.toString.call(obj) === '[object Array]') { - return obj.forEach(function(elem, idx, ar) { - if (typeof elem === 'string' || elem instanceof String) { - return ar[idx] = inline ? HDIN(elem) : HD(elem); - } else { - return hardenStringsInObject(elem); - } - }); - } else if (typeof obj === 'object') { - return Object.keys(obj).forEach(function(key) { - var sub; - sub = obj[key]; - if (typeof sub === 'string' || sub instanceof String) { - if (_.contains(['skills', 'url', 'website', 'startDate', 'endDate', 'releaseDate', 'date', 'phone', 'email', 'address', 'postalCode', 'city', 'country', 'region'], key)) { - return; - } - if (key === 'summary') { - return obj[key] = HD(obj[key]); - } else { - return obj[key] = inline ? HDIN(obj[key]) : HD(obj[key]); - } - } else { - return hardenStringsInObject(sub); - } - }); - } - }; - Object.keys(ret).forEach(function(member) { - return hardenStringsInObject(ret[member]); + transformer = require('../utils/string-transformer'); + return transformer(ret, ['skills', 'url', 'website', 'startDate', 'endDate', 'releaseDate', 'date', 'phone', 'email', 'address', 'postalCode', 'city', 'country', 'region', 'safeStartDate', 'safeEndDate'], function(key, val) { + return HD(val); }); - return ret; }; return JRSResume; diff --git a/src/core/jrs-resume.coffee b/src/core/jrs-resume.coffee index 2834625..cae1df3 100644 --- a/src/core/jrs-resume.coffee +++ b/src/core/jrs-resume.coffee @@ -242,38 +242,12 @@ class JRSResume# extends AbstractResume #return MD(txt || '' ).replace(/^\s*

|<\/p>\s*$/gi, ''); return HD txt - # TODO: refactor recursion - hardenStringsInObject = ( obj, inline ) -> - - return if !obj - inline = inline == undefined || inline - - if Object.prototype.toString.call( obj ) == '[object Array]' - obj.forEach (elem, idx, ar) -> - if typeof elem == 'string' || elem instanceof String - ar[idx] = if inline then HDIN(elem) else HD( elem ) - else - hardenStringsInObject elem - else if typeof obj == 'object' - Object.keys( obj ).forEach (key) -> - sub = obj[key] - if typeof sub == 'string' || sub instanceof String - if _.contains(['skills','url','website','startDate','endDate', - 'releaseDate','date','phone','email','address','postalCode', - 'city','country','region'], key) - return - if key == 'summary' - obj[key] = HD( obj[key] ) - else - obj[key] = if inline then HDIN( obj[key] ) else HD( obj[key] ) - else - hardenStringsInObject sub - - - Object.keys( ret ).forEach (member) -> - hardenStringsInObject ret[ member ] - - ret + transformer = require '../utils/string-transformer' + transformer ret, + [ 'skills','url','website','startDate','endDate', 'releaseDate', 'date', + 'phone','email','address','postalCode','city','country','region', + 'safeStartDate','safeEndDate' ], + (key, val) -> HD val From dc88073bcc50b724ef43cd0aaeb3737dc75ab795 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Sun, 4 Feb 2018 23:35:42 -0500 Subject: [PATCH 09/30] test: reactivate Awesome theme and ice check --- test/scripts/test-themes.js | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/test/scripts/test-themes.js b/test/scripts/test-themes.js index 2dbf208..6d42f23 100644 --- a/test/scripts/test-themes.js +++ b/test/scripts/test-themes.js @@ -76,10 +76,11 @@ function genThemes( title, src, fmt ) { genTheme(fmt, src, 'compact'); genTheme(fmt, src, 'modern'); genTheme(fmt, src, 'underscore', 'node_modules/fresh-theme-underscore' ); - //genTheme(fmt, src, 'awesome'); + genTheme(fmt, src, 'awesome'); genTheme(fmt, src, 'positive'); + genTheme(fmt, src, 'jsonresume-theme-boilerplate', - 'node_modules/jsonresume-theme-boilerplate' ); + 'node_modules/jsonresume-theme-boilerplate' ); genTheme(fmt, src, 'jsonresume-theme-sceptile', 'node_modules/jsonresume-theme-sceptile' ); genTheme(fmt, src, 'jsonresume-theme-modern', @@ -129,17 +130,17 @@ genThemes( 'JRS' ); -// describe('Verifying generated theme files...', function() { -// -// it('Generated files should not contain ICE.', function() { -// var q = folderContains('@@@@', '../sandbox'); -// q.should.equal(false); -// }); -// -// it('Generated files should match exemplars...', function() { -// var q = foldersMatch( 'test/sandbox/FRESH/jane-q-fullstacker/modern', -// 'test/expected/modern' ); -// q.should.equal(true); -// }); -// -// }); +describe('Verifying generated theme files...', function() { + + it('Generated files should not contain ICE.', function() { + var q = folderContains('@@@@', '../sandbox'); + q.should.equal(false); + }); + + // it('Generated files should match exemplars...', function() { + // var q = foldersMatch( 'test/sandbox/FRESH/jane-q-fullstacker/modern', + // 'test/expected/modern' ); + // q.should.equal(true); + // }); + +}); From 6f07141b0d3b83f0601061ee90c62c8bab6eaff9 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Mon, 5 Feb 2018 00:06:14 -0500 Subject: [PATCH 10/30] test: remove image files from freeze test --- test/scripts/test-themes.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/scripts/test-themes.js b/test/scripts/test-themes.js index 6d42f23..3c68ada 100644 --- a/test/scripts/test-themes.js +++ b/test/scripts/test-themes.js @@ -93,13 +93,17 @@ function genThemes( title, src, fmt ) { } function folderContains( needle, haystack ) { + var ignoreExts = ['.png','.jpg','.jpeg','.bmp','.pdf', '.gif']; var safePath = path.normalize( path.join(__dirname, haystack)); return _.some( readFolder( safePath ), function( absPath ) { if( require('fs').lstatSync( absPath ).isFile() ) { - if( fileContains( absPath, needle ) ) { + var pathInfo = path.parse( absPath ); + if( !_.contains(ignoreExts, pathInfo.ext) && + fileContains(absPath, needle) ) { console.error('Found invalid metadata in ' + absPath); return true; } + return false; } }); } From 66f3cb15c9010d13e8e055c0948382f81f40e8bb Mon Sep 17 00:00:00 2001 From: hacksalot Date: Mon, 5 Feb 2018 23:41:40 -0500 Subject: [PATCH 11/30] style: remove unused var --- dist/core/jrs-resume.js | 3 +-- src/core/jrs-resume.coffee | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/dist/core/jrs-resume.js b/dist/core/jrs-resume.js index 57368e0..067cc32 100644 --- a/dist/core/jrs-resume.js +++ b/dist/core/jrs-resume.js @@ -285,8 +285,7 @@ Definition of the JRSResume class. */ JRSResume.prototype.harden = function() { - var HD, HDIN, ret, that, transformer; - that = this; + var HD, HDIN, ret, transformer; ret = this.dupe(); HD = function(txt) { return '@@@@~' + txt + '~@@@@'; diff --git a/src/core/jrs-resume.coffee b/src/core/jrs-resume.coffee index cae1df3..23c6b02 100644 --- a/src/core/jrs-resume.coffee +++ b/src/core/jrs-resume.coffee @@ -233,7 +233,6 @@ class JRSResume# extends AbstractResume ### harden: () -> - that = @ ret = @dupe() HD = (txt) -> '@@@@~' + txt + '~@@@@' From 7262578c819497d36692df52831350b0f850428a Mon Sep 17 00:00:00 2001 From: hacksalot Date: Mon, 5 Feb 2018 23:43:38 -0500 Subject: [PATCH 12/30] feat: allow standalone FRESH themes to inherit --- dist/core/fresh-theme.js | 12 ++++++++---- src/core/fresh-theme.coffee | 20 +++++++++++++++++--- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/dist/core/fresh-theme.js b/dist/core/fresh-theme.js index 7570333..8d15622 100644 --- a/dist/core/fresh-theme.js +++ b/dist/core/fresh-theme.js @@ -62,10 +62,14 @@ Definition of the FRESHTheme class. if (this.inherits) { cached = {}; _.each(this.inherits, function(th, key) { - var d, themePath, themesFolder; - themesFolder = require.resolve('fresh-themes'); - d = parsePath(themeFolder).dirname; - themePath = PATH.join(d, th); + var d, themePath, themesObj; + themesObj = require('fresh-themes'); + if (_.has(themesObj.themes, th)) { + themePath = PATH.join(parsePath(require.resolve('fresh-themes')).dirname, '/themes/', th); + } else { + d = parsePath(th).dirname; + themePath = PATH.join(d, th); + } cached[th] = cached[th] || new FRESHTheme().open(themePath); return formatsHash[key] = cached[th].getFormat(key); }); diff --git a/src/core/fresh-theme.coffee b/src/core/fresh-theme.coffee index 48e0b78..6e4432b 100644 --- a/src/core/fresh-theme.coffee +++ b/src/core/fresh-theme.coffee @@ -59,9 +59,23 @@ class FRESHTheme if @inherits cached = { } _.each @inherits, (th, key) -> - themesFolder = require.resolve 'fresh-themes' - d = parsePath( themeFolder ).dirname - themePath = PATH.join d, th + # First, see if this is one of the predefined FRESH themes. There are + # only a handful of these, but they may change over time, so we need to + # query the official source of truth: the fresh-themes repository, which + # mounts the themes conveniently by name to the module object, and which + # is embedded locally inside the HackMyResume installation. + # TODO: merge this code with + themesObj = require 'fresh-themes' + if _.has themesObj.themes, th + themePath = PATH.join( + parsePath( require.resolve('fresh-themes') ).dirname, + '/themes/', + th + ) + else + d = parsePath( th ).dirname + themePath = PATH.join d, th + cached[ th ] = cached[th] || new FRESHTheme().open( themePath ) formatsHash[ key ] = cached[ th ].getFormat( key ) From 2bf5bb72cff1c9e16df292270b1e19aff51a03d1 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Tue, 6 Feb 2018 08:22:31 -0500 Subject: [PATCH 13/30] fix: prevent broken XML in Word docs --- dist/utils/html-to-wpml.js | 6 ++++-- src/utils/html-to-wpml.coffee | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/dist/utils/html-to-wpml.js b/dist/utils/html-to-wpml.js index bef0dd6..6feb07f 100644 --- a/dist/utils/html-to-wpml.js +++ b/dist/utils/html-to-wpml.js @@ -6,7 +6,9 @@ Definition of the Markdown to WordProcessingML conversion routine. */ (function() { - var HTML5Tokenizer, _; + var HTML5Tokenizer, XML, _; + + XML = require('xml-escape'); _ = require('underscore'); @@ -51,7 +53,7 @@ Definition of the Markdown to WordProcessingML conversion routine. style = is_bold ? '' : ''; style += is_italic ? '' : ''; style += is_link ? '' : ''; - return final += (is_link ? '' : '') + '' + style + '' + tok.chars + '' + (is_link ? '' : ''); + return final += (is_link ? '' : '') + '' + style + '' + XML(tok.chars) + '' + (is_link ? '' : ''); } } }); diff --git a/src/utils/html-to-wpml.coffee b/src/utils/html-to-wpml.coffee index 98cb1b5..f8e5b7e 100644 --- a/src/utils/html-to-wpml.coffee +++ b/src/utils/html-to-wpml.coffee @@ -5,6 +5,7 @@ Definition of the Markdown to WordProcessingML conversion routine. ### +XML = require 'xml-escape' _ = require 'underscore' HTML5Tokenizer = require 'simple-html-tokenizer' @@ -44,6 +45,6 @@ module.exports = ( html ) -> style += if is_link then '' else '' final += (if is_link then ('') else '') + - '' + style + '' + tok.chars + + '' + style + '' + XML(tok.chars) + '' + (if is_link then '' else '') final From 23465624287920ca3a79c2897bd832dac0a3e4fb Mon Sep 17 00:00:00 2001 From: hacksalot Date: Tue, 6 Feb 2018 08:24:18 -0500 Subject: [PATCH 14/30] fix: remove extraneous log statement --- dist/cli/main.js | 1 - src/cli/main.coffee | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/dist/cli/main.js b/dist/cli/main.js index 391f2a2..e9ff5d2 100644 --- a/dist/cli/main.js +++ b/dist/cli/main.js @@ -257,7 +257,6 @@ Definition of the `main` function. executeFail = function(err) { var finalErrorCode, msgs; - console.dir(err); finalErrorCode = -1; if (err) { if (err.fluenterror) { diff --git a/src/cli/main.coffee b/src/cli/main.coffee index dbbf1f0..bbcb67d 100644 --- a/src/cli/main.coffee +++ b/src/cli/main.coffee @@ -288,7 +288,7 @@ executeSuccess = (obj) -> ### Failure handler for verb invocations. Calls process.exit by default ### executeFail = (err) -> - console.dir err + #console.dir err finalErrorCode = -1 if err if err.fluenterror From 38a073b09a3aca95929c6266fc6cf3eaf450c1ce Mon Sep 17 00:00:00 2001 From: hacksalot Date: Wed, 7 Feb 2018 05:49:02 -0500 Subject: [PATCH 15/30] feat: improve template helper wiring --- dist/helpers/handlebars-helpers.js | 4 ++-- dist/renderers/handlebars-generator.js | 4 ++-- src/helpers/handlebars-helpers.coffee | 21 ++++++++++++++++----- src/renderers/handlebars-generator.coffee | 12 +++++++----- 4 files changed, 27 insertions(+), 14 deletions(-) diff --git a/dist/helpers/handlebars-helpers.js b/dist/helpers/handlebars-helpers.js index 65c1532..a5891fb 100644 --- a/dist/helpers/handlebars-helpers.js +++ b/dist/helpers/handlebars-helpers.js @@ -26,7 +26,7 @@ Template helper definitions for Handlebars. @method registerHelpers */ - module.exports = function(theme, opts) { + module.exports = function(theme, rez, opts) { var curGlob, ex, glob, slash, wrappedHelpers; helpers.theme = theme; helpers.opts = opts; @@ -37,7 +37,7 @@ Template helper definitions for Handlebars. var args; args = Array.prototype.slice.call(arguments); args.shift(); - args.pop(); + args[args.length - 1] = rez; return func.apply(this, args); }); } diff --git a/dist/renderers/handlebars-generator.js b/dist/renderers/handlebars-generator.js index 53df326..9a75b7e 100644 --- a/dist/renderers/handlebars-generator.js +++ b/dist/renderers/handlebars-generator.js @@ -51,8 +51,6 @@ Definition of the HandlebarsGenerator class. }, generate: function(json, jst, format, curFmt, opts, theme) { var ctx, encData; - registerPartials(format, theme); - registerHelpers(theme, opts); encData = json; if (format === 'html' || format === 'pdf') { encData = json.markdownify(); @@ -60,6 +58,8 @@ Definition of the HandlebarsGenerator class. if (format === 'doc') { encData = json.xmlify(); } + registerPartials(format, theme); + registerHelpers(theme, encData, opts); ctx = { r: encData, RAW: json, diff --git a/src/helpers/handlebars-helpers.coffee b/src/helpers/handlebars-helpers.coffee index 871e356..3960e5e 100644 --- a/src/helpers/handlebars-helpers.coffee +++ b/src/helpers/handlebars-helpers.coffee @@ -17,23 +17,28 @@ Register useful Handlebars helpers. @method registerHelpers ### -module.exports = ( theme, opts ) -> +module.exports = ( theme, rez, opts ) -> helpers.theme = theme helpers.opts = opts helpers.type = 'handlebars' + # Prepare generic helpers for use with Handlebars. We do this by wrapping them + # in a Handlebars-aware wrapper which calls the helper internally. wrappedHelpers = _.mapObject helpers, ( hVal, hKey ) -> if _.isFunction hVal return _.wrap hVal, (func) -> args = Array.prototype.slice.call arguments - args.shift() # lose the 1st element (func) - args.pop() # lose the last element (the Handlebars options hash) - func.apply @, args + args.shift() # lose the 1st element (func) [^1] + #args.pop() # lose the last element (HB options hash) + args[ args.length - 1 ] = rez # replace w/ resume object + func.apply @, args # call the generic helper hVal , @ - HANDLEBARS.registerHelper wrappedHelpers + + # Prepare Handlebars-specific helpers - "blockHelpers" is really a misnomer + # since any kind of Handlebars-specific helper can live here HANDLEBARS.registerHelper blockHelpers # Register any theme-provided custom helpers... @@ -64,3 +69,9 @@ module.exports = ( theme, opts ) -> inner: ex glob: curGlob, exit: true return + +# [^1]: This little bit of acrobatics ensures that our generic helpers are +# called as generic helpers, not as Handlebars-specific helpers. This allows +# them to be used in other templating engines, like Underscore. If you need a +# Handlebars-specific helper with normal Handlebars context and options, put it +# in block-helpers.coffee. diff --git a/src/renderers/handlebars-generator.coffee b/src/renderers/handlebars-generator.coffee index 0041bb1..ab1cb6b 100644 --- a/src/renderers/handlebars-generator.coffee +++ b/src/renderers/handlebars-generator.coffee @@ -30,7 +30,9 @@ HandlebarsGenerator = module.exports = try # Compile and run the Handlebars template. template = HANDLEBARS.compile tpl, - strict: false, assumeObjects: false, noEscape: data.opts.noescape + strict: false + assumeObjects: false + noEscape: data.opts.noescape return template data catch throw @@ -42,10 +44,6 @@ HandlebarsGenerator = module.exports = generate: ( json, jst, format, curFmt, opts, theme ) -> - # Set up partials and helpers - registerPartials format, theme - registerHelpers theme, opts - # Preprocess text encData = json if format == 'html' || format == 'pdf' @@ -53,6 +51,10 @@ HandlebarsGenerator = module.exports = if( format == 'doc' ) encData = json.xmlify() + # Set up partials and helpers + registerPartials format, theme + registerHelpers theme, encData, opts + # Set up the context ctx = r: encData From c08c5f0fa3ff13adbd9abf7bf3c9933f07f0b976 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Wed, 7 Feb 2018 05:55:27 -0500 Subject: [PATCH 16/30] feat: introduce two skill-related helpers --- dist/helpers/block-helpers.js | 10 ++++++++++ dist/helpers/generic-helpers.js | 19 +++++++++++++++---- src/helpers/block-helpers.coffee | 10 +++++++++- src/helpers/generic-helpers.coffee | 14 ++++++++++---- 4 files changed, 44 insertions(+), 9 deletions(-) diff --git a/dist/helpers/block-helpers.js b/dist/helpers/block-helpers.js index f06a063..adae083 100644 --- a/dist/helpers/block-helpers.js +++ b/dist/helpers/block-helpers.js @@ -43,6 +43,16 @@ Block helper definitions for HackMyResume / FluentCV. } return ret; }, + ifHasSkill: function(rez, skill, options) { + var ret, skUp; + skUp = skill.toUpperCase(); + ret = _.some(rez.skills.list, function(sk) { + return (skUp.toUpperCase() === sk.name.toUpperCase()) && sk.years; + }, this); + if (ret) { + return options.fn(this); + } + }, /** Emit the enclosed content if the resume has the named diff --git a/dist/helpers/generic-helpers.js b/dist/helpers/generic-helpers.js index a9da25e..5fd4944 100644 --- a/dist/helpers/generic-helpers.js +++ b/dist/helpers/generic-helpers.js @@ -6,7 +6,7 @@ Generic template helper definitions for HackMyResume / FluentCV. */ (function() { - var FS, FluentDate, GenericHelpers, H2W, HMSTATUS, LO, MD, PATH, XML, _, _fromTo, _reportError, moment, printf, skillLevelToIndex, unused; + var FS, FluentDate, GenericHelpers, H2W, HMSTATUS, LO, MD, PATH, XML, _, _fromTo, _reportError, _skillLevelToIndex, moment, printf, unused; MD = require('marked'); @@ -391,7 +391,7 @@ Generic template helper definitions for HackMyResume / FluentCV. */ skillColor: function(lvl) { var idx, skillColors; - idx = skillLevelToIndex(lvl); + idx = _skillLevelToIndex(lvl); skillColors = (this.theme && this.theme.palette && this.theme.palette.skillLevels) || ['#FFFFFF', '#5CB85C', '#F1C40F', '#428BCA', '#C00000']; return skillColors[idx]; }, @@ -402,7 +402,7 @@ Generic template helper definitions for HackMyResume / FluentCV. */ skillHeight: function(lvl) { var idx; - idx = skillLevelToIndex(lvl); + idx = _skillLevelToIndex(lvl); return ['38.25', '30', '16', '8', '0'][idx]; }, @@ -541,6 +541,17 @@ Generic template helper definitions for HackMyResume / FluentCV. ret = PAD(stringOrArray, stringOrArray.length + Math.abs(padAmount), null, padAmount < 0 ? PAD.LEFT : PAD.RIGHT); } return ret; + }, + skillYears: function(skill, rez) { + var sk; + sk = _.find(rez.skills.list, function(sk) { + return sk.name.toUpperCase() === skill.toUpperCase(); + }); + if (sk) { + return sk.years; + } else { + return '?'; + } } }; @@ -599,7 +610,7 @@ Generic template helper definitions for HackMyResume / FluentCV. return ''; }; - skillLevelToIndex = function(lvl) { + _skillLevelToIndex = function(lvl) { var idx, intVal; idx = 0; if (String.is(lvl)) { diff --git a/src/helpers/block-helpers.coffee b/src/helpers/block-helpers.coffee index e3d3045..d01fcb6 100644 --- a/src/helpers/block-helpers.coffee +++ b/src/helpers/block-helpers.coffee @@ -38,6 +38,14 @@ BlockHelpers = module.exports = + ifHasSkill: ( rez, skill, options ) -> + skUp = skill.toUpperCase() + ret = _.some rez.skills.list, (sk) -> + (skUp.toUpperCase() == sk.name.toUpperCase()) and sk.years + , @ + options.fn @ if ret + + ###* Emit the enclosed content if the resume has the named property or subproperty. @@ -55,4 +63,4 @@ BlockHelpers = module.exports = Return true if either value is truthy. @method either ### - either: ( lhs, rhs, options ) -> options.fn @ if lhs || rhs + either: ( lhs, rhs, options ) -> options.fn @ if lhs || rhs diff --git a/src/helpers/generic-helpers.coffee b/src/helpers/generic-helpers.coffee index dddef4d..79724fe 100644 --- a/src/helpers/generic-helpers.coffee +++ b/src/helpers/generic-helpers.coffee @@ -329,6 +329,7 @@ GenericHelpers = module.exports = # If not provided by the user, stitle should default to sname. ps. # Handlebars silently passes in the options object to the last param, # where in Underscore stitle will be null/undefined, so we check both. + # TODO: not actually sure that's true, given that we _.wrap these functions stitle = (stitle && String.is(stitle) && stitle) || sname # If there's a section title override, use it. @@ -342,7 +343,7 @@ GenericHelpers = module.exports = wpml: ( txt, inline ) -> return '' if !txt inline = (inline && !inline.hash) || false - txt = XML(txt.trim()) + txt = XML txt.trim() txt = if inline then MD(txt).replace(/^\s*

|<\/p>\s*$/gi, '') else MD(txt) txt = H2W( txt ) return txt @@ -376,7 +377,7 @@ GenericHelpers = module.exports = '#FFFFAA'). ### skillColor: ( lvl ) -> - idx = skillLevelToIndex lvl + idx = _skillLevelToIndex lvl skillColors = (this.theme && this.theme.palette && this.theme.palette.skillLevels) || [ '#FFFFFF', '#5CB85C', '#F1C40F', '#428BCA', '#C00000' ] @@ -389,7 +390,7 @@ GenericHelpers = module.exports = @method lastWord ### skillHeight: ( lvl ) -> - idx = skillLevelToIndex lvl + idx = _skillLevelToIndex lvl ['38.25', '30', '16', '8', '0'][idx] @@ -505,6 +506,11 @@ GenericHelpers = module.exports = ret + skillYears: ( skill, rez ) -> + sk = _.find rez.skills.list, (sk) -> sk.name.toUpperCase() == skill.toUpperCase() + if sk then sk.years else '?' + + ###* Report an error to the outside world without throwing an exception. Currently @@ -562,7 +568,7 @@ _fromTo = ( dateA, dateB, fmt, sep, fallback ) -> -skillLevelToIndex = ( lvl ) -> +_skillLevelToIndex = ( lvl ) -> idx = 0 if String.is( lvl ) lvl = lvl.trim().toLowerCase() From 8648befcdd89e6ef91d4c1b05c95291a90eb77ec Mon Sep 17 00:00:00 2001 From: hacksalot Date: Wed, 7 Feb 2018 23:35:05 -0500 Subject: [PATCH 17/30] feat: introduce stringOrObject & linkMD helpers --- dist/helpers/generic-helpers.js | 33 ++++++++++++++++++++++++++++++ src/helpers/generic-helpers.coffee | 26 +++++++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/dist/helpers/generic-helpers.js b/dist/helpers/generic-helpers.js index 5fd4944..d9804ee 100644 --- a/dist/helpers/generic-helpers.js +++ b/dist/helpers/generic-helpers.js @@ -369,6 +369,18 @@ Generic template helper definitions for HackMyResume / FluentCV. } }, + /** + Emit a conditional Markdown link. + @method link + */ + linkMD: function(text, url) { + if (url && url.trim()) { + return '[' + text + '](' + url + ')'; + } else { + return text; + } + }, + /** Return the last word of the specified text. @method lastWord @@ -527,6 +539,10 @@ Generic template helper definitions for HackMyResume / FluentCV. return options.inverse(this); } }, + + /** + Emit padded text. + */ pad: function(stringOrArray, padAmount, unused) { var PAD, ret; stringOrArray = stringOrArray || ''; @@ -542,6 +558,11 @@ Generic template helper definitions for HackMyResume / FluentCV. } return ret; }, + + /** + Given the name of a skill ("JavaScript" or "HVAC repair"), return the number + of years assigned to that skill in the resume.skills.list collection. + */ skillYears: function(skill, rez) { var sk; sk = _.find(rez.skills.list, function(sk) { @@ -552,6 +573,18 @@ Generic template helper definitions for HackMyResume / FluentCV. } else { return '?'; } + }, + + /** + Given an object that may be a string or an object, return it as-is if it's a + string, otherwise return the value at obj[objPath]. + */ + stringOrObject: function(obj, objPath, rez) { + if (_.isString(obj)) { + return obj; + } else { + return LO.get(obj, objPath); + } } }; diff --git a/src/helpers/generic-helpers.coffee b/src/helpers/generic-helpers.coffee index 79724fe..a8116f9 100644 --- a/src/helpers/generic-helpers.coffee +++ b/src/helpers/generic-helpers.coffee @@ -359,6 +359,15 @@ GenericHelpers = module.exports = + ###* + Emit a conditional Markdown link. + @method link + ### + linkMD: ( text, url ) -> + return if url && url.trim() then ('[' + text + '](' + url + ')') else text + + + ###* Return the last word of the specified text. @method lastWord @@ -492,6 +501,9 @@ GenericHelpers = module.exports = + ###* + Emit padded text. + ### pad: (stringOrArray, padAmount, unused ) -> stringOrArray = stringOrArray || '' padAmount = padAmount || 0 @@ -506,12 +518,26 @@ GenericHelpers = module.exports = ret + + ###* + Given the name of a skill ("JavaScript" or "HVAC repair"), return the number + of years assigned to that skill in the resume.skills.list collection. + ### skillYears: ( skill, rez ) -> sk = _.find rez.skills.list, (sk) -> sk.name.toUpperCase() == skill.toUpperCase() if sk then sk.years else '?' + ###* + Given an object that may be a string or an object, return it as-is if it's a + string, otherwise return the value at obj[objPath]. + ### + stringOrObject: ( obj, objPath, rez ) -> + if _.isString obj then obj else LO.get obj, objPath + + + ###* Report an error to the outside world without throwing an exception. Currently relies on kludging the running verb into. opts. From 20815d7effde544bc791425e4c66d33f635b2dbc Mon Sep 17 00:00:00 2001 From: hacksalot Date: Fri, 9 Feb 2018 00:06:07 -0500 Subject: [PATCH 18/30] fix: add missing require() --- dist/inspectors/duration-inspector.js | 6 ++++-- src/inspectors/duration-inspector.coffee | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/dist/inspectors/duration-inspector.js b/dist/inspectors/duration-inspector.js index 5b048a2..d5837ea 100644 --- a/dist/inspectors/duration-inspector.js +++ b/dist/inspectors/duration-inspector.js @@ -1,10 +1,12 @@ (function() { - var FluentDate, _; + var FluentDate, _, lo; FluentDate = require('../core/fluent-date'); _ = require('underscore'); + lo = require('lodash'); + module.exports = { /** @@ -17,7 +19,7 @@ run: function(rez, collKey, startKey, endKey, unit) { var firstDate, hist, lastDate, new_e; unit = unit || 'years'; - hist = __.get(rez, collKey); + hist = lo.get(rez, collKey); if (!hist || !hist.length) { return 0; } diff --git a/src/inspectors/duration-inspector.coffee b/src/inspectors/duration-inspector.coffee index a93e78b..d6afc9a 100644 --- a/src/inspectors/duration-inspector.coffee +++ b/src/inspectors/duration-inspector.coffee @@ -1,5 +1,6 @@ FluentDate = require '../core/fluent-date' _ = require 'underscore' +lo = require 'lodash' module.exports = @@ -12,7 +13,7 @@ module.exports = ### run: (rez, collKey, startKey, endKey, unit) -> unit = unit || 'years' - hist = __.get rez, collKey + hist = lo.get rez, collKey return 0 if !hist or !hist.length # BEGIN CODE DUPLICATION --> src/inspectors/gap-inspector.coffee (TODO) From 06805578a2a7d4aabed5a13371eb9c0cc9a76963 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Fri, 9 Feb 2018 00:07:37 -0500 Subject: [PATCH 19/30] chore: introduce fresh-resume-validator dependency --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index be2cfc1..3026b32 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,7 @@ "fresh-jrs-converter": "^0.2.3", "fresh-resume-schema": "^1.0.0-beta", "fresh-resume-starter": "^0.3.1", + "fresh-resume-validator": "^0.1.0", "fresh-themes": "^0.16.0-beta", "fs-extra": "^0.26.4", "glob": "^7.1.2", From ea3c72845ea1d65eeb22e90657c2fd671d88aaa3 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Fri, 9 Feb 2018 00:08:41 -0500 Subject: [PATCH 20/30] chore: bump fresh-test-resumes to 0.9.2 explicit --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3026b32..ce4a5b4 100644 --- a/package.json +++ b/package.json @@ -90,7 +90,7 @@ "chai": "*", "chai-as-promised": "^7.1.1", "dir-compare": "0.0.2", - "fresh-test-resumes": "^0.9.1", + "fresh-test-resumes": "^0.9.2", "fresh-test-themes": "^0.2.0", "fresh-theme-underscore": "^0.1.1", "grunt": "*", From 17e5c6c1724c0fec63a8ded49d56e3edba0a2c28 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Fri, 9 Feb 2018 00:17:10 -0500 Subject: [PATCH 21/30] style: notate an issue in skills coalescing func --- dist/core/fresh-resume.js | 4 +++- src/core/fresh-resume.coffee | 19 ++++++++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/dist/core/fresh-resume.js b/dist/core/fresh-resume.js index 344c421..5546a41 100644 --- a/dist/core/fresh-resume.js +++ b/dist/core/fresh-resume.js @@ -213,7 +213,9 @@ Definition of the FRESHResume class. }; - /** Return a unique list of all keywords across all skills. */ + /** + Return a unique list of all skills declared in the resume. + */ FreshResume.prototype.keywords = function() { var flatSkills; diff --git a/src/core/fresh-resume.coffee b/src/core/fresh-resume.coffee index f24a073..a17d9fd 100644 --- a/src/core/fresh-resume.coffee +++ b/src/core/fresh-resume.coffee @@ -180,7 +180,24 @@ class FreshResume# extends AbstractResume - ###* Return a unique list of all keywords across all skills. ### + ###* + Return a unique list of all skills declared in the resume. + ### + + # TODO: Several problems here: + # 1) Confusing name. Easily confused with the keyword-inspector module, which + # parses resume body text looking for these same keywords. This should probably + # be renamed. + # + # 2) Doesn't bother trying to integrate skills.list with skills.sets if they + # happen to declare different skills, and if skills.sets declares ONE skill and + # skills.list declared 50, only 1 skill will be registered. + # + # 3) In the future, skill.sets should only be able to use skills declared in + # skills.list. That is, skills.list is the official record of a candidate's + # declared skills. skills.sets is just a way of grouping those into skillsets + # for easier consumption. + keywords: () -> flatSkills = [] if @skills From 58fe46dc8377aaa43686831e36ba6c2d874e90d9 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Fri, 9 Feb 2018 21:32:44 -0500 Subject: [PATCH 22/30] feat: introduce FRESH version regex --- dist/utils/fresh-version-regex.js | 14 ++++++++++++++ src/utils/fresh-version-regex.coffee | 23 +++++++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 dist/utils/fresh-version-regex.js create mode 100644 src/utils/fresh-version-regex.coffee diff --git a/dist/utils/fresh-version-regex.js b/dist/utils/fresh-version-regex.js new file mode 100644 index 0000000..1f27393 --- /dev/null +++ b/dist/utils/fresh-version-regex.js @@ -0,0 +1,14 @@ + +/** +Defines a regex suitable for matching FRESH versions. +@module file-contains.js + */ + +(function() { + module.exports = function() { + return RegExp('^(FRESH|FRESCA|JRS)(?:@(\\d+(?:\\.\\d+)?(?:\\.\\d+)?))?$'); + }; + +}).call(this); + +//# sourceMappingURL=fresh-version-regex.js.map diff --git a/src/utils/fresh-version-regex.coffee b/src/utils/fresh-version-regex.coffee new file mode 100644 index 0000000..1c7f25b --- /dev/null +++ b/src/utils/fresh-version-regex.coffee @@ -0,0 +1,23 @@ +###* +Defines a regex suitable for matching FRESH versions. +@module file-contains.js +### + +# Set up a regex that matches all of the following: +# +# - FRESH +# - JRS +# - FRESCA +# - FRESH@1.0.0 +# - FRESH@1.0 +# - FRESH@1 +# - JRS@0.16.0 +# - JRS@0.16 +# - JRS@0 +# +# Don't use a SEMVER regex (eg, NPM's semver-regex) because a) we want to +# support partial semvers like "0" or "1.2" and b) we'll expand this later to +# support fully scoped FRESH versions. + +module.exports = () -> + RegExp '^(FRESH|FRESCA|JRS)(?:@(\\d+(?:\\.\\d+)?(?:\\.\\d+)?))?$' From 7cfdb95a049a1a66cb9b369243f64fa21b84dc88 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Fri, 9 Feb 2018 21:34:24 -0500 Subject: [PATCH 23/30] feat: convert: support multiple JRS versions --- dist/cli/error.js | 4 ++++ dist/cli/main.js | 2 +- dist/cli/msg.yml | 2 ++ dist/core/fresh-resume.js | 23 +++++++++++++++++----- dist/core/status-codes.js | 3 ++- dist/verbs/convert.js | 37 ++++++++++++++++++++++++++++++------ src/cli/error.coffee | 3 +++ src/cli/main.coffee | 1 + src/cli/msg.yml | 2 ++ src/core/fresh-resume.coffee | 21 ++++++++++++++++---- src/core/status-codes.coffee | 1 + src/verbs/convert.coffee | 32 ++++++++++++++++++++++++++----- 12 files changed, 109 insertions(+), 22 deletions(-) diff --git a/dist/cli/error.js b/dist/cli/error.js index d8d1c28..5df9bda 100644 --- a/dist/cli/error.js +++ b/dist/cli/error.js @@ -266,6 +266,10 @@ Error-handling routines for HackMyResume. case HMSTATUS.themeHelperLoad: msg = printf(M2C(this.msgs.themeHelperLoad.msg), ex.glob); etype = 'error'; + break; + case HMSTATUS.invalidSchemaVersion: + msg = printf(M2C(this.msgs.invalidSchemaVersion.msg), ex.data); + etype = 'error'; } return { msg: msg, diff --git a/dist/cli/main.js b/dist/cli/main.js index e9ff5d2..ca0636d 100644 --- a/dist/cli/main.js +++ b/dist/cli/main.js @@ -74,7 +74,7 @@ Definition of the `main` function. 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() { + program.command('convert').description('Convert a resume to/from FRESH or JSON RESUME format.').option('-f --format ', 'FRESH or JRS format and optional version', void 0).action(function() { var x; x = splitSrcDest.call(this); execute.call(this, x.src, x.dst, this.opts(), logMsg); diff --git a/dist/cli/msg.yml b/dist/cli/msg.yml index b7fe231..862dbba 100644 --- a/dist/cli/msg.yml +++ b/dist/cli/msg.yml @@ -137,3 +137,5 @@ errors: An error occurred while attempting to load the '%s' theme helper. Is the theme correctly installed? dummy: dontcare + invalidSchemaVersion: + msg: "'%s' is not recognized as a valid schema version." diff --git a/dist/core/fresh-resume.js b/dist/core/fresh-resume.js index 5546a41..a053163 100644 --- a/dist/core/fresh-resume.js +++ b/dist/core/fresh-resume.js @@ -109,14 +109,27 @@ Definition of the FRESHResume class. Save the sheet to disk in a specific format, either FRESH or JSON Resume. */ - FreshResume.prototype.saveAs = function(filename, format) { - var newRep; - if (format !== 'JRS') { + FreshResume.prototype.saveAs = function(filename, format, version) { + var freshVersionReg, newRep, parts, safeFormat, safeVersion; + safeFormat = (format || 'FRESH').trim(); + safeVersion = version || "0"; + freshVersionReg = require('../utils/fresh-version-regex'); + if (!freshVersionReg().test(safeFormat)) { + throw { + badVer: safeFormat + }; + } + parts = safeFormat.split('@'); + if (parts[0] === 'FRESH') { this.imp.file = filename || this.imp.file; FS.writeFileSync(this.imp.file, this.stringify(), 'utf8'); - } else { - newRep = CONVERTER.toJRS(this); + } else if (parts[0] === 'JRS') { + newRep = CONVERTER.toJRS(this, null, parts.length > 1 ? parts[1] : "1.0.0"); FS.writeFileSync(filename, JRSResume.stringify(newRep), 'utf8'); + } else { + throw { + badVer: safeFormat + }; } return this; }; diff --git a/dist/core/status-codes.js b/dist/core/status-codes.js index 4eedca5..77e8c36 100644 --- a/dist/core/status-codes.js +++ b/dist/core/status-codes.js @@ -37,7 +37,8 @@ Status codes for HackMyResume. invalidOptionsFile: 27, optionsFileNotFound: 28, unknownSchema: 29, - themeHelperLoad: 30 + themeHelperLoad: 30, + invalidSchemaVersion: 31 }; }).call(this); diff --git a/dist/verbs/convert.js b/dist/verbs/convert.js index f3e4806..9101672 100644 --- a/dist/verbs/convert.js +++ b/dist/verbs/convert.js @@ -39,7 +39,7 @@ Implementation of the 'convert' verb for HackMyResume. */ _convert = function(srcs, dst, opts) { - var results; + var fmtUp, freshVerRegex, matches, results, targetSchema, targetVer; if (!srcs || !srcs.length) { this.err(HMSTATUS.resumeNotFound, { quit: true @@ -65,13 +65,27 @@ Implementation of the 'convert' verb for HackMyResume. quit: true }); } + targetVer = null; + if (opts.format) { + fmtUp = opts.format.trim().toUpperCase(); + freshVerRegex = require('../utils/fresh-version-regex'); + matches = fmtUp.match(freshVerRegex()); + if (!matches) { + this.err(HMSTATUS.invalidSchemaVersion, { + data: opts.format.trim(), + quit: true + }); + } + targetSchema = matches[1]; + targetVer = matches[2] || '1'; + } if (this.hasError()) { this.reject(this.errorCode); return null; } results = _.map(srcs, function(src, idx) { var r; - r = _convertOne.call(this, src, dst, idx); + r = _convertOne.call(this, src, dst, idx, targetSchema, targetVer); if (r.fluenterror) { r.quit = opts.assert; this.err(r.fluenterror, r); @@ -89,8 +103,8 @@ Implementation of the 'convert' verb for HackMyResume. /** Private workhorse method. Convert a single resume. */ - _convertOne = function(src, dst, idx) { - var rez, rinfo, srcFmt, targetFormat; + _convertOne = function(src, dst, idx, targetSchema, targetVer) { + var err, rez, rinfo, srcFmt, targetFormat; rinfo = ResumeFactory.loadOne(src, { format: null, objectify: true, @@ -118,14 +132,25 @@ Implementation of the 'convert' verb for HackMyResume. rinfo.fluenterror = HMSTATUS.unknownSchema; return rinfo; } - targetFormat = srcFmt === 'JRS' ? 'FRESH' : 'JRS'; + targetFormat = targetSchema || (srcFmt === 'JRS' ? 'FRESH' : 'JRS'); this.stat(HMEVENT.beforeConvert, { srcFile: rinfo.file, srcFmt: srcFmt, dstFile: dst[idx], dstFmt: targetFormat }); - rez.saveAs(dst[idx], targetFormat); + try { + rez.saveAs(dst[idx], targetFormat, targetVer); + } catch (_error) { + err = _error; + if (err.badVer) { + return { + fluenterror: HMSTATUS.invalidSchemaVersion, + quit: true, + data: err.badVer + }; + } + } return rez; }; diff --git a/src/cli/error.coffee b/src/cli/error.coffee index 815902e..1112120 100644 --- a/src/cli/error.coffee +++ b/src/cli/error.coffee @@ -257,6 +257,9 @@ assembleError = ( ex ) -> msg = printf M2C( @msgs.themeHelperLoad.msg ), ex.glob etype = 'error' + when HMSTATUS.invalidSchemaVersion + msg = printf M2C( @msgs.invalidSchemaVersion.msg ), ex.data + etype = 'error' msg: msg # The error message to display withStack: withStack # Whether to include the stack diff --git a/src/cli/main.coffee b/src/cli/main.coffee index bbcb67d..79b6f86 100644 --- a/src/cli/main.coffee +++ b/src/cli/main.coffee @@ -84,6 +84,7 @@ main = module.exports = ( rawArgs, exitCallback ) -> program .command('convert') .description('Convert a resume to/from FRESH or JSON RESUME format.') + .option('-f --format ', 'FRESH or JRS format and optional version', undefined) .action(-> x = splitSrcDest.call( this ); execute.call( this, x.src, x.dst, this.opts(), logMsg) diff --git a/src/cli/msg.yml b/src/cli/msg.yml index b7fe231..862dbba 100644 --- a/src/cli/msg.yml +++ b/src/cli/msg.yml @@ -137,3 +137,5 @@ errors: An error occurred while attempting to load the '%s' theme helper. Is the theme correctly installed? dummy: dontcare + invalidSchemaVersion: + msg: "'%s' is not recognized as a valid schema version." diff --git a/src/core/fresh-resume.coffee b/src/core/fresh-resume.coffee index a17d9fd..d6b6bea 100644 --- a/src/core/fresh-resume.coffee +++ b/src/core/fresh-resume.coffee @@ -94,13 +94,26 @@ class FreshResume# extends AbstractResume ###* Save the sheet to disk in a specific format, either FRESH or JSON Resume. ### - saveAs: ( filename, format ) -> - if format != 'JRS' + saveAs: ( filename, format, version ) -> + + # If format isn't specified, default to FRESH + safeFormat = (format || 'FRESH').trim() + safeVersion = version || "0" + + # Validate against the FRESH version regex + # freshVersionReg = require '../utils/fresh-version-regex' + # if (not freshVersionReg().test( safeFormat )) + # throw badVer: safeFormat + + parts = safeFormat.split '@' + if parts[0] == 'FRESH' @imp.file = filename || @imp.file FS.writeFileSync @imp.file, @stringify(), 'utf8' - else - newRep = CONVERTER.toJRS this + else if parts[0] == 'JRS' + newRep = CONVERTER.toJRS @, null, if parts.length > 1 then parts[1] else "1" FS.writeFileSync filename, JRSResume.stringify( newRep ), 'utf8' + else + throw badVer: safeFormat @ diff --git a/src/core/status-codes.coffee b/src/core/status-codes.coffee index 8252796..4a34cc9 100644 --- a/src/core/status-codes.coffee +++ b/src/core/status-codes.coffee @@ -37,3 +37,4 @@ module.exports = optionsFileNotFound: 28 unknownSchema: 29 themeHelperLoad: 30 + invalidSchemaVersion: 31 diff --git a/src/verbs/convert.coffee b/src/verbs/convert.coffee index 00272bf..7604034 100644 --- a/src/verbs/convert.coffee +++ b/src/verbs/convert.coffee @@ -46,6 +46,20 @@ _convert = ( srcs, dst, opts ) -> if srcs && dst && srcs.length && dst.length && srcs.length != dst.length @err HMSTATUS.inputOutputParity, { quit: true } + # Validate the destination format (if specified) + targetVer = null + if opts.format + fmtUp = opts.format.trim().toUpperCase() + freshVerRegex = require '../utils/fresh-version-regex' + matches = fmtUp.match freshVerRegex() + # null + # [ 'JRS@1.0', 'JRS', '1.0', index: 0, input: 'FRESH' ] + # [ 'FRESH', 'FRESH', undefined, index: 0, input: 'FRESH' ] + if not matches + @err HMSTATUS.invalidSchemaVersion, data: opts.format.trim(), quit: true + targetSchema = matches[1] + targetVer = matches[2] || '1' + # If any errors have occurred this early, we're done. if @hasError() @reject @errorCode @@ -55,7 +69,7 @@ _convert = ( srcs, dst, opts ) -> results = _.map srcs, ( src, idx ) -> # Convert each resume in turn - r = _convertOne.call @, src, dst, idx + r = _convertOne.call @, src, dst, idx, targetSchema, targetVer # Handle conversion errors if r.fluenterror @@ -74,12 +88,12 @@ _convert = ( srcs, dst, opts ) -> ###* Private workhorse method. Convert a single resume. ### -_convertOne = (src, dst, idx) -> +_convertOne = (src, dst, idx, targetSchema, targetVer) -> # Load the resume rinfo = ResumeFactory.loadOne src, format: null - objectify: true, + objectify: true inner: privatize: false @@ -94,6 +108,8 @@ _convertOne = (src, dst, idx) -> #@err rinfo.fluenterror, rinfo return rinfo + # Determine the resume's SOURCE format + # TODO: replace with detector component rez = rinfo.rez srcFmt = '' if rez.meta && rez.meta.format #&& rez.meta.format.substr(0, 5).toUpperCase() == 'FRESH' @@ -104,8 +120,10 @@ _convertOne = (src, dst, idx) -> rinfo.fluenterror = HMSTATUS.unknownSchema return rinfo - targetFormat = if srcFmt == 'JRS' then 'FRESH' else 'JRS' + # Determine the TARGET format for the conversion + targetFormat = targetSchema or (if srcFmt == 'JRS' then 'FRESH' else 'JRS') + # Fire the beforeConvert event this.stat HMEVENT.beforeConvert, srcFile: rinfo.file srcFmt: srcFmt @@ -113,5 +131,9 @@ _convertOne = (src, dst, idx) -> dstFmt: targetFormat # Save it to the destination format - rez.saveAs dst[idx], targetFormat + try + rez.saveAs dst[idx], targetFormat, targetVer + catch err + if err.badVer + return fluenterror: HMSTATUS.invalidSchemaVersion, quit: true, data: err.badVer rez From 7196bff27cafc8033ef5c9a74a97cf8ed29a841a Mon Sep 17 00:00:00 2001 From: hacksalot Date: Sat, 10 Feb 2018 01:10:20 -0500 Subject: [PATCH 24/30] feat: support JSON Resume edge schema --- dist/core/fresh-resume.js | 18 +++++++----------- dist/verbs/convert.js | 14 +++++--------- src/core/fresh-resume.coffee | 10 ++++++---- src/verbs/convert.coffee | 24 +++++++++++++----------- 4 files changed, 31 insertions(+), 35 deletions(-) diff --git a/dist/core/fresh-resume.js b/dist/core/fresh-resume.js index a053163..690dbf4 100644 --- a/dist/core/fresh-resume.js +++ b/dist/core/fresh-resume.js @@ -109,22 +109,18 @@ Definition of the FRESHResume class. Save the sheet to disk in a specific format, either FRESH or JSON Resume. */ - FreshResume.prototype.saveAs = function(filename, format, version) { - var freshVersionReg, newRep, parts, safeFormat, safeVersion; - safeFormat = (format || 'FRESH').trim(); - safeVersion = version || "0"; - freshVersionReg = require('../utils/fresh-version-regex'); - if (!freshVersionReg().test(safeFormat)) { - throw { - badVer: safeFormat - }; - } + FreshResume.prototype.saveAs = function(filename, format) { + var newRep, parts, safeFormat, useEdgeSchema; + safeFormat = (format && format.trim()) || 'FRESH'; parts = safeFormat.split('@'); if (parts[0] === 'FRESH') { this.imp.file = filename || this.imp.file; FS.writeFileSync(this.imp.file, this.stringify(), 'utf8'); } else if (parts[0] === 'JRS') { - newRep = CONVERTER.toJRS(this, null, parts.length > 1 ? parts[1] : "1.0.0"); + useEdgeSchema = parts.length > 1 ? parts[1] === '1' : false; + newRep = CONVERTER.toJRS(this, { + edge: useEdgeSchema + }); FS.writeFileSync(filename, JRSResume.stringify(newRep), 'utf8'); } else { throw { diff --git a/dist/verbs/convert.js b/dist/verbs/convert.js index 9101672..d4f83bd 100644 --- a/dist/verbs/convert.js +++ b/dist/verbs/convert.js @@ -39,7 +39,7 @@ Implementation of the 'convert' verb for HackMyResume. */ _convert = function(srcs, dst, opts) { - var fmtUp, freshVerRegex, matches, results, targetSchema, targetVer; + var fmtUp, results, targetVer; if (!srcs || !srcs.length) { this.err(HMSTATUS.resumeNotFound, { quit: true @@ -68,16 +68,12 @@ Implementation of the 'convert' verb for HackMyResume. targetVer = null; if (opts.format) { fmtUp = opts.format.trim().toUpperCase(); - freshVerRegex = require('../utils/fresh-version-regex'); - matches = fmtUp.match(freshVerRegex()); - if (!matches) { + if (!_.contains(['FRESH', 'FRESCA', 'JRS', 'JRS@1', 'JRS@edge'], fmtUp)) { this.err(HMSTATUS.invalidSchemaVersion, { data: opts.format.trim(), quit: true }); } - targetSchema = matches[1]; - targetVer = matches[2] || '1'; } if (this.hasError()) { this.reject(this.errorCode); @@ -85,7 +81,7 @@ Implementation of the 'convert' verb for HackMyResume. } results = _.map(srcs, function(src, idx) { var r; - r = _convertOne.call(this, src, dst, idx, targetSchema, targetVer); + r = _convertOne.call(this, src, dst, idx, fmtUp); if (r.fluenterror) { r.quit = opts.assert; this.err(r.fluenterror, r); @@ -103,7 +99,7 @@ Implementation of the 'convert' verb for HackMyResume. /** Private workhorse method. Convert a single resume. */ - _convertOne = function(src, dst, idx, targetSchema, targetVer) { + _convertOne = function(src, dst, idx, targetSchema) { var err, rez, rinfo, srcFmt, targetFormat; rinfo = ResumeFactory.loadOne(src, { format: null, @@ -140,7 +136,7 @@ Implementation of the 'convert' verb for HackMyResume. dstFmt: targetFormat }); try { - rez.saveAs(dst[idx], targetFormat, targetVer); + rez.saveAs(dst[idx], targetFormat); } catch (_error) { err = _error; if (err.badVer) { diff --git a/src/core/fresh-resume.coffee b/src/core/fresh-resume.coffee index d6b6bea..32826c5 100644 --- a/src/core/fresh-resume.coffee +++ b/src/core/fresh-resume.coffee @@ -94,11 +94,10 @@ class FreshResume# extends AbstractResume ###* Save the sheet to disk in a specific format, either FRESH or JSON Resume. ### - saveAs: ( filename, format, version ) -> + saveAs: ( filename, format ) -> # If format isn't specified, default to FRESH - safeFormat = (format || 'FRESH').trim() - safeVersion = version || "0" + safeFormat = (format && format.trim()) || 'FRESH' # Validate against the FRESH version regex # freshVersionReg = require '../utils/fresh-version-regex' @@ -106,11 +105,14 @@ class FreshResume# extends AbstractResume # throw badVer: safeFormat parts = safeFormat.split '@' + if parts[0] == 'FRESH' @imp.file = filename || @imp.file FS.writeFileSync @imp.file, @stringify(), 'utf8' + else if parts[0] == 'JRS' - newRep = CONVERTER.toJRS @, null, if parts.length > 1 then parts[1] else "1" + useEdgeSchema = if parts.length > 1 then parts[1] == '1' else false + newRep = CONVERTER.toJRS @, edge: useEdgeSchema FS.writeFileSync filename, JRSResume.stringify( newRep ), 'utf8' else throw badVer: safeFormat diff --git a/src/verbs/convert.coffee b/src/verbs/convert.coffee index 7604034..baa500e 100644 --- a/src/verbs/convert.coffee +++ b/src/verbs/convert.coffee @@ -50,15 +50,17 @@ _convert = ( srcs, dst, opts ) -> targetVer = null if opts.format fmtUp = opts.format.trim().toUpperCase() - freshVerRegex = require '../utils/fresh-version-regex' - matches = fmtUp.match freshVerRegex() - # null - # [ 'JRS@1.0', 'JRS', '1.0', index: 0, input: 'FRESH' ] - # [ 'FRESH', 'FRESH', undefined, index: 0, input: 'FRESH' ] - if not matches + if not _.contains ['FRESH','FRESCA','JRS','JRS@1','JRS@edge'], fmtUp @err HMSTATUS.invalidSchemaVersion, data: opts.format.trim(), quit: true - targetSchema = matches[1] - targetVer = matches[2] || '1' + # freshVerRegex = require '../utils/fresh-version-regex' + # matches = fmtUp.match freshVerRegex() + # # null + # # [ 'JRS@1.0', 'JRS', '1.0', index: 0, input: 'FRESH' ] + # # [ 'FRESH', 'FRESH', undefined, index: 0, input: 'FRESH' ] + # if not matches + # @err HMSTATUS.invalidSchemaVersion, data: opts.format.trim(), quit: true + # targetSchema = matches[1] + # targetVer = matches[2] || '1' # If any errors have occurred this early, we're done. if @hasError() @@ -69,7 +71,7 @@ _convert = ( srcs, dst, opts ) -> results = _.map srcs, ( src, idx ) -> # Convert each resume in turn - r = _convertOne.call @, src, dst, idx, targetSchema, targetVer + r = _convertOne.call @, src, dst, idx, fmtUp # Handle conversion errors if r.fluenterror @@ -88,7 +90,7 @@ _convert = ( srcs, dst, opts ) -> ###* Private workhorse method. Convert a single resume. ### -_convertOne = (src, dst, idx, targetSchema, targetVer) -> +_convertOne = (src, dst, idx, targetSchema) -> # Load the resume rinfo = ResumeFactory.loadOne src, @@ -132,7 +134,7 @@ _convertOne = (src, dst, idx, targetSchema, targetVer) -> # Save it to the destination format try - rez.saveAs dst[idx], targetFormat, targetVer + rez.saveAs dst[idx], targetFormat catch err if err.badVer return fluenterror: HMSTATUS.invalidSchemaVersion, quit: true, data: err.badVer From 2281b4ea7f3474a9436bbc95695f86556a765db9 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Sat, 10 Feb 2018 03:02:22 -0500 Subject: [PATCH 25/30] chore: bump fresh-jrs-converter to 1.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ce4a5b4..648efce 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ "copy": "^0.1.3", "escape-latex": "^0.1.2", "extend": "^3.0.0", - "fresh-jrs-converter": "^0.2.3", + "fresh-jrs-converter": "^1.0.0", "fresh-resume-schema": "^1.0.0-beta", "fresh-resume-starter": "^0.3.1", "fresh-resume-validator": "^0.1.0", From 98f20c368cf2c87db9a9e6f2ab1a344115f5543c Mon Sep 17 00:00:00 2001 From: hacksalot Date: Sat, 10 Feb 2018 13:03:52 -0500 Subject: [PATCH 26/30] feat: introduce HELP command Add support for a "HELP" verb in the HackMyResume CLI, allowing separate help pages for each HackMyResume command per #205. The following command invocations are recognized. hackmyresume help hackmyresume help build hackmyresume help new hackmyresume help convert hackmyresume help analyze hackmyresume help validate hackmyresume help peek hackmyresume help help --- dist/cli/help/analyze.txt | 23 +++++++++++++ dist/cli/help/build.txt | 69 ++++++++++++++++++++++++++++++++++++++ dist/cli/help/convert.txt | 34 +++++++++++++++++++ dist/cli/help/help.txt | 23 +++++++++++++ dist/cli/help/new.txt | 27 +++++++++++++++ dist/cli/help/peek.txt | 30 +++++++++++++++++ dist/cli/help/validate.txt | 27 +++++++++++++++ dist/cli/main.js | 22 +++++++++--- dist/cli/use.txt | 42 +++++++++++------------ src/cli/help/analyze.txt | 23 +++++++++++++ src/cli/help/build.txt | 69 ++++++++++++++++++++++++++++++++++++++ src/cli/help/convert.txt | 34 +++++++++++++++++++ src/cli/help/help.txt | 23 +++++++++++++ src/cli/help/new.txt | 27 +++++++++++++++ src/cli/help/peek.txt | 30 +++++++++++++++++ src/cli/help/validate.txt | 27 +++++++++++++++ src/cli/main.coffee | 33 +++++++++++++----- src/cli/use.txt | 42 +++++++++++------------ 18 files changed, 546 insertions(+), 59 deletions(-) create mode 100644 dist/cli/help/analyze.txt create mode 100644 dist/cli/help/build.txt create mode 100644 dist/cli/help/convert.txt create mode 100644 dist/cli/help/help.txt create mode 100644 dist/cli/help/new.txt create mode 100644 dist/cli/help/peek.txt create mode 100644 dist/cli/help/validate.txt create mode 100644 src/cli/help/analyze.txt create mode 100644 src/cli/help/build.txt create mode 100644 src/cli/help/convert.txt create mode 100644 src/cli/help/help.txt create mode 100644 src/cli/help/new.txt create mode 100644 src/cli/help/peek.txt create mode 100644 src/cli/help/validate.txt diff --git a/dist/cli/help/analyze.txt b/dist/cli/help/analyze.txt new file mode 100644 index 0000000..5635f16 --- /dev/null +++ b/dist/cli/help/analyze.txt @@ -0,0 +1,23 @@ +**analyze** | Analyze a FRESH resume document for statistics + +Usage: + + hackmyresume ANALYZE [] + hackmyresume ANALYZE [] + +Parameters: + + **** + + Absolute or relative path(s) to one or more FRESH or + JSON Resume documents to be analyzed. Multiple resumes + can be specified, separated by spaces: + + hackmyresume ANALYZE r1.json r2.json r3.json + +Options: + + **None.** + +The ANALYZE command performs simple analysis on a FRESH or +JSON Resume document. diff --git a/dist/cli/help/build.txt b/dist/cli/help/build.txt new file mode 100644 index 0000000..e572f5c --- /dev/null +++ b/dist/cli/help/build.txt @@ -0,0 +1,69 @@ +**build** | Generate resumes in supported output formats + +Usage: + + hackmyresume BUILD TO [] + +Parameters: + + **** + + Absolute or relative path(s) to one or more resumes in + FRESH or JSON format, separated by spaces. If multiple + resumes are specified, they will be merged into a + single resume prior to transformation. + + hackmyresume BUILD resume.json output.all + hackmyresume BUILD base.json developer.json gamedev.json TO out/resume.all + + **** + + Absolute or relative path(s) to one or more outbound + resume(s), separated by spaces. If multiple output + resumes are specified, all of them will be generated. + The desired format of each resume will be determined + from the file extension: + + .all Generate all supported formats + .html HTML 5 + .doc MS Word + .pdf Adobe Acrobat PDF + .txt plain text + .md Markdown + .png PNG Image + .latex LaTeX + + Note: not all formats are supported by all themes. + Check the theme's documentation for details. + +Options: + + **--theme -t ** + + Relative or absolute path to a FRESH or JSON Resume + theme, or the name of a built-in theme. + + As of 1.9.0, the following built-in themes are + provided: basis, modern, positive, compact, awesome. + + **--pdf -p ** + + Specify the PDF engine to use. Legal values are: + + - none: Don't generate a PDF. + - wkhtmltopdf: Use the wkhtmltopdf PDF engine. + - phantom: use the PhantomJS PDF engine. + - weazyprint: use the WeazyPrint PDF engine. + + **--no-escape** + + Disable escaping / encoding of resume data during + resume generation. Handlebars themes only. + + **--private** + + Include resume fields marked as private. + +The BUILD command generates themed resumes and CVs in +multiple formats from a single source of truth in the form +of a FRESH or JSON Resume document. diff --git a/dist/cli/help/convert.txt b/dist/cli/help/convert.txt new file mode 100644 index 0000000..dd6092b --- /dev/null +++ b/dist/cli/help/convert.txt @@ -0,0 +1,34 @@ +**convert** | Convert resumes between FRESH and JRS formats + +Usage: + + hackmyresume CONVERT TO [] + +Parameters: + + **** + + Absolute or relative path(s) to one or more FRESH or + JSON Resume documents (*.json), separated by spaces. + + **** + + The desired absolute or relative path(s) of the newly + converted resume(s). HackMyResume will create the + converted resume(s) here. + +Options: + + **--format -f ** + + The desired format for the new resume(s). Valid values + are 'FRESH', 'JRS', or, to target the latest edge + version of the JSON Resume Schema, 'JRS@1'. + + If this parameter is omitted, the destination format + will be inferred from the source resume's format. If + the source format is FRESH, the destination format + will be JSON Resume, and vice-versa. + +The CONVERT command converts one or more resume documents +between FRESH and JSON Resume formats. diff --git a/dist/cli/help/help.txt b/dist/cli/help/help.txt new file mode 100644 index 0000000..f259bba --- /dev/null +++ b/dist/cli/help/help.txt @@ -0,0 +1,23 @@ +**help** | View help on a specific HackMyResume command + +Usage: + + hackmyresume HELP + +Parameters: + + **** + + The HackMyResume command to view help information for. + Must be BUILD, NEW, CONVERT, ANALYZE, VALIDATE, PEEK, + or HELP. + + hackmyresume help convert + hackmyresume help help + +Options: + + **None.** + +The HELP command displays help information for a specific +HackMyResume command, including the HELP command itself. diff --git a/dist/cli/help/new.txt b/dist/cli/help/new.txt new file mode 100644 index 0000000..0d7f05e --- /dev/null +++ b/dist/cli/help/new.txt @@ -0,0 +1,27 @@ +**new** | Create a new FRESH or JRS resume document + +Usage: + + hackmyresume NEW [] + +Parameters: + + **** + + Absolute or relative path(s) to one or more FRESH or + JSON Resume documents (*.json), separated by spaces: + + hackmyresume NEW r1.json r2.json r3.json + +Options: + + **--format -f ** + + The desired format for the new resume(s). Valid values + are 'FRESH', 'JRS', or, to target the latest edge + version of the JSON Resume Schema, 'JRS@1'. + +The NEW command generates one or more new resumes in FRESH +or JSON Resume format. This document can serve as a source +of truth for resume and career data and an input to tools +like HackMyResume or resume-cli. diff --git a/dist/cli/help/peek.txt b/dist/cli/help/peek.txt new file mode 100644 index 0000000..576b42e --- /dev/null +++ b/dist/cli/help/peek.txt @@ -0,0 +1,30 @@ +**peek** | View portions of a resume from the command line + +Usage: + + hackmyresume PEEK [] + +Parameters: + + **** + + Absolute or relative path(s) to one or more FRESH or + JSON Resume documents to be validated. Multiple resumes + can be specified, separated by spaces: + + hackmyresume PEEK r1.json r2.json r3.json + + **** + + The part of the resume to peek at. + +Options: + + **--assert -a** + + Tell HackMyResume to return a non-zero process exit + code if a resume fails to validate. + +The PEEK command displays a specific piece or part of the +resume without requiring the resume to be opened in an +editor. diff --git a/dist/cli/help/validate.txt b/dist/cli/help/validate.txt new file mode 100644 index 0000000..08a062e --- /dev/null +++ b/dist/cli/help/validate.txt @@ -0,0 +1,27 @@ +**validate** - Validate a resume for correctness + +Usage: + + hackmyresume VALIDATE [] + hackmyresume VALIDATE [] + +Parameters: + + **** + + Absolute or relative path(s) to one or more FRESH or + JSON Resume documents to be validated. Multiple resumes + can be specified, separated by spaces: + + hackmyresume ANALYZE r1.json r2.json r3.json + +Options: + + **--assert -a** + + Tell HackMyResume to return a non-zero process exit + code if a resume fails to validate. + +The VALIDATE command validates a FRESH or JSON Resume +document against its governing schema, verifying that the +resume is correctly structured. diff --git a/dist/cli/main.js b/dist/cli/main.js index ca0636d..b730320 100644 --- a/dist/cli/main.js +++ b/dist/cli/main.js @@ -92,6 +92,16 @@ Definition of the `main` function. x = splitSrcDest.call(this); execute.call(this, x.src, x.dst, this.opts(), logMsg); }); + program.command('help')["arguments"]('[command]').description('Get help on a HackMyResume command').action(function(command) { + var cmd, manPage; + cmd = command && command.trim(); + if (cmd) { + manPage = FS.readFileSync(PATH.join(__dirname, 'help/' + cmd + '.txt'), 'utf8'); + } else { + manPage = FS.readFileSync(PATH.join(__dirname, 'use.txt'), 'utf8'); + } + console.log(M2C(manPage, 'white', 'yellow.bold')); + }); program.parse(args); if (!program.args.length) { throw { @@ -135,7 +145,7 @@ Definition of the `main` function. _out.log(''); } _err.init(o.debug, o.assert, o.silent); - if (o.verb && !HMR.verbs[o.verb] && !HMR.alias[o.verb]) { + if (o.verb && !HMR.verbs[o.verb] && !HMR.alias[o.verb] && o.verb !== 'help') { _err.err({ fluenterror: HMSTATUS.invalidCommand, quit: true, @@ -143,14 +153,16 @@ Definition of the `main` function. }, true); } Command.prototype.missingArgument = function(name) { - _err.err({ - fluenterror: this.name() !== 'new' ? HMSTATUS.resumeNotFound : HMSTATUS.createNameMissing - }, true); + if (this.name() !== 'help') { + _err.err({ + fluenterror: this.name() !== 'new' ? HMSTATUS.resumeNotFound : HMSTATUS.createNameMissing + }, true); + } }; Command.prototype.helpInformation = function() { var manPage; manPage = FS.readFileSync(PATH.join(__dirname, 'use.txt'), 'utf8'); - return chalk.green.bold(manPage); + return M2C(manPage, 'white', 'yellow'); }; return { args: o.args, diff --git a/dist/cli/use.txt b/dist/cli/use.txt index fd73d38..760cf9d 100644 --- a/dist/cli/use.txt +++ b/dist/cli/use.txt @@ -1,32 +1,28 @@ +**HackMyResume** | A Swiss Army knife for resumes and CVs. + Usage: - hackmyresume [TO ] [] + hackmyresume [] -Available commands: +Available commands (type "hackmyresume help COMMAND" for details): - BUILD Build your resume to the destination format(s). - ANALYZE Analyze your resume for keywords, gaps, and metrics. - VALIDATE Validate your resume for errors and typos. - CONVERT Convert your resume between FRESH and JSON Resume. - NEW Create a new resume in FRESH or JSON Resume format. - PEEK View a specific field or element on your resume. + **BUILD** Build your resume to the destination format(s). + **ANALYZE** Analyze your resume for keywords, gaps, and metrics. + **VALIDATE** Validate your resume for errors and typos. + **NEW** Create a new resume in FRESH or JSON Resume format. + **CONVERT** Convert your resume between FRESH and JSON Resume. + **PEEK** View a specific field or element on your resume. + **HELP** View help on a specific HackMyResume command. Available options: - --theme -t Path to a FRESH or JSON Resume theme. - --pdf -p Specify the PDF engine to use (wkhtmltopdf or phantom). - --options -o Load options from an external JSON file. - --format -f The format (FRESH or JSON Resume) to use. - --debug -d Emit extended debugging info. - --assert -a Treat resume validation warnings as errors. - --private Include resume fields marked as private - --no-colors Disable terminal colors. - --tips Display theme messages and tips. - --help -h Display help documentation. - --version -v Display the current version. - -Not all options are supported for all commands. For example, the ---theme option is only supported for the BUILD command. + **--options -o** Load options from an external JSON file. + **--debug -d** Emit extended debugging info. + **--assert -a** Treat resume validation warnings as errors. + **--no-colors** Disable terminal colors. + **--tips** Display theme messages and tips. + **--help -h** Display help documentation. + **--version -v** Display the current version. Examples: @@ -47,6 +43,6 @@ Tips: theme, or the path to a local FRESH or JSON Resume theme. - Visit https://www.npmjs.com/search?q=jsonresume-theme for a full listing of all available JSON Resume themes. - - Visit https://github.com/fluentdesk/fresh-themes for a complete + - Visit https://github.com/fresh-standard/fresh-themes for a complete listing of all available FRESH themes. - Report bugs to https://githut.com/hacksalot/HackMyResume/issues. diff --git a/src/cli/help/analyze.txt b/src/cli/help/analyze.txt new file mode 100644 index 0000000..5635f16 --- /dev/null +++ b/src/cli/help/analyze.txt @@ -0,0 +1,23 @@ +**analyze** | Analyze a FRESH resume document for statistics + +Usage: + + hackmyresume ANALYZE [] + hackmyresume ANALYZE [] + +Parameters: + + **** + + Absolute or relative path(s) to one or more FRESH or + JSON Resume documents to be analyzed. Multiple resumes + can be specified, separated by spaces: + + hackmyresume ANALYZE r1.json r2.json r3.json + +Options: + + **None.** + +The ANALYZE command performs simple analysis on a FRESH or +JSON Resume document. diff --git a/src/cli/help/build.txt b/src/cli/help/build.txt new file mode 100644 index 0000000..e572f5c --- /dev/null +++ b/src/cli/help/build.txt @@ -0,0 +1,69 @@ +**build** | Generate resumes in supported output formats + +Usage: + + hackmyresume BUILD TO [] + +Parameters: + + **** + + Absolute or relative path(s) to one or more resumes in + FRESH or JSON format, separated by spaces. If multiple + resumes are specified, they will be merged into a + single resume prior to transformation. + + hackmyresume BUILD resume.json output.all + hackmyresume BUILD base.json developer.json gamedev.json TO out/resume.all + + **** + + Absolute or relative path(s) to one or more outbound + resume(s), separated by spaces. If multiple output + resumes are specified, all of them will be generated. + The desired format of each resume will be determined + from the file extension: + + .all Generate all supported formats + .html HTML 5 + .doc MS Word + .pdf Adobe Acrobat PDF + .txt plain text + .md Markdown + .png PNG Image + .latex LaTeX + + Note: not all formats are supported by all themes. + Check the theme's documentation for details. + +Options: + + **--theme -t ** + + Relative or absolute path to a FRESH or JSON Resume + theme, or the name of a built-in theme. + + As of 1.9.0, the following built-in themes are + provided: basis, modern, positive, compact, awesome. + + **--pdf -p ** + + Specify the PDF engine to use. Legal values are: + + - none: Don't generate a PDF. + - wkhtmltopdf: Use the wkhtmltopdf PDF engine. + - phantom: use the PhantomJS PDF engine. + - weazyprint: use the WeazyPrint PDF engine. + + **--no-escape** + + Disable escaping / encoding of resume data during + resume generation. Handlebars themes only. + + **--private** + + Include resume fields marked as private. + +The BUILD command generates themed resumes and CVs in +multiple formats from a single source of truth in the form +of a FRESH or JSON Resume document. diff --git a/src/cli/help/convert.txt b/src/cli/help/convert.txt new file mode 100644 index 0000000..dd6092b --- /dev/null +++ b/src/cli/help/convert.txt @@ -0,0 +1,34 @@ +**convert** | Convert resumes between FRESH and JRS formats + +Usage: + + hackmyresume CONVERT TO [] + +Parameters: + + **** + + Absolute or relative path(s) to one or more FRESH or + JSON Resume documents (*.json), separated by spaces. + + **** + + The desired absolute or relative path(s) of the newly + converted resume(s). HackMyResume will create the + converted resume(s) here. + +Options: + + **--format -f ** + + The desired format for the new resume(s). Valid values + are 'FRESH', 'JRS', or, to target the latest edge + version of the JSON Resume Schema, 'JRS@1'. + + If this parameter is omitted, the destination format + will be inferred from the source resume's format. If + the source format is FRESH, the destination format + will be JSON Resume, and vice-versa. + +The CONVERT command converts one or more resume documents +between FRESH and JSON Resume formats. diff --git a/src/cli/help/help.txt b/src/cli/help/help.txt new file mode 100644 index 0000000..f259bba --- /dev/null +++ b/src/cli/help/help.txt @@ -0,0 +1,23 @@ +**help** | View help on a specific HackMyResume command + +Usage: + + hackmyresume HELP + +Parameters: + + **** + + The HackMyResume command to view help information for. + Must be BUILD, NEW, CONVERT, ANALYZE, VALIDATE, PEEK, + or HELP. + + hackmyresume help convert + hackmyresume help help + +Options: + + **None.** + +The HELP command displays help information for a specific +HackMyResume command, including the HELP command itself. diff --git a/src/cli/help/new.txt b/src/cli/help/new.txt new file mode 100644 index 0000000..0d7f05e --- /dev/null +++ b/src/cli/help/new.txt @@ -0,0 +1,27 @@ +**new** | Create a new FRESH or JRS resume document + +Usage: + + hackmyresume NEW [] + +Parameters: + + **** + + Absolute or relative path(s) to one or more FRESH or + JSON Resume documents (*.json), separated by spaces: + + hackmyresume NEW r1.json r2.json r3.json + +Options: + + **--format -f ** + + The desired format for the new resume(s). Valid values + are 'FRESH', 'JRS', or, to target the latest edge + version of the JSON Resume Schema, 'JRS@1'. + +The NEW command generates one or more new resumes in FRESH +or JSON Resume format. This document can serve as a source +of truth for resume and career data and an input to tools +like HackMyResume or resume-cli. diff --git a/src/cli/help/peek.txt b/src/cli/help/peek.txt new file mode 100644 index 0000000..576b42e --- /dev/null +++ b/src/cli/help/peek.txt @@ -0,0 +1,30 @@ +**peek** | View portions of a resume from the command line + +Usage: + + hackmyresume PEEK [] + +Parameters: + + **** + + Absolute or relative path(s) to one or more FRESH or + JSON Resume documents to be validated. Multiple resumes + can be specified, separated by spaces: + + hackmyresume PEEK r1.json r2.json r3.json + + **** + + The part of the resume to peek at. + +Options: + + **--assert -a** + + Tell HackMyResume to return a non-zero process exit + code if a resume fails to validate. + +The PEEK command displays a specific piece or part of the +resume without requiring the resume to be opened in an +editor. diff --git a/src/cli/help/validate.txt b/src/cli/help/validate.txt new file mode 100644 index 0000000..08a062e --- /dev/null +++ b/src/cli/help/validate.txt @@ -0,0 +1,27 @@ +**validate** - Validate a resume for correctness + +Usage: + + hackmyresume VALIDATE [] + hackmyresume VALIDATE [] + +Parameters: + + **** + + Absolute or relative path(s) to one or more FRESH or + JSON Resume documents to be validated. Multiple resumes + can be specified, separated by spaces: + + hackmyresume ANALYZE r1.json r2.json r3.json + +Options: + + **--assert -a** + + Tell HackMyResume to return a non-zero process exit + code if a resume fails to validate. + +The VALIDATE command validates a FRESH or JSON Resume +document against its governing schema, verifying that the +resume is correctly structured. diff --git a/src/cli/main.coffee b/src/cli/main.coffee index 79b6f86..156672c 100644 --- a/src/cli/main.coffee +++ b/src/cli/main.coffee @@ -132,6 +132,22 @@ main = module.exports = ( rawArgs, exitCallback ) -> return ) + # Create the HELP command + program + .command('help') + .arguments('[command]') + .description('Get help on a HackMyResume command') + .action ( command ) -> + cmd = command && command.trim() + if cmd + manPage = FS.readFileSync( + PATH.join(__dirname, 'help/' + cmd + '.txt'), 'utf8' ) + else + manPage = FS.readFileSync( + PATH.join(__dirname, 'use.txt'), 'utf8' ) + console.log M2C(manPage, 'white', 'yellow.bold') + return + program.parse( args ) if !program.args.length @@ -171,24 +187,25 @@ initialize = ( ar, exitCallback ) -> _err.init o.debug, o.assert, o.silent # Handle invalid verbs here (a bit easier here than in commander.js)... - if o.verb && !HMR.verbs[ o.verb ] && !HMR.alias[ o.verb ] + if o.verb && !HMR.verbs[ o.verb ] && !HMR.alias[ o.verb ] && o.verb != 'help' _err.err fluenterror: HMSTATUS.invalidCommand, quit: true, attempted: o.orgVerb, true # Override the .missingArgument behavior Command.prototype.missingArgument = (name) -> - _err.err - fluenterror: - if this.name() != 'new' - then HMSTATUS.resumeNotFound - else HMSTATUS.createNameMissing - , true + if this.name() != 'help' + _err.err + fluenterror: + if this.name() != 'new' + then HMSTATUS.resumeNotFound + else HMSTATUS.createNameMissing + , true return # Override the .helpInformation behavior Command.prototype.helpInformation = -> manPage = FS.readFileSync( PATH.join(__dirname, 'use.txt'), 'utf8' ) - return chalk.green.bold(manPage) + return M2C(manPage, 'white', 'yellow') return { args: o.args, diff --git a/src/cli/use.txt b/src/cli/use.txt index fd73d38..760cf9d 100644 --- a/src/cli/use.txt +++ b/src/cli/use.txt @@ -1,32 +1,28 @@ +**HackMyResume** | A Swiss Army knife for resumes and CVs. + Usage: - hackmyresume [TO ] [] + hackmyresume [] -Available commands: +Available commands (type "hackmyresume help COMMAND" for details): - BUILD Build your resume to the destination format(s). - ANALYZE Analyze your resume for keywords, gaps, and metrics. - VALIDATE Validate your resume for errors and typos. - CONVERT Convert your resume between FRESH and JSON Resume. - NEW Create a new resume in FRESH or JSON Resume format. - PEEK View a specific field or element on your resume. + **BUILD** Build your resume to the destination format(s). + **ANALYZE** Analyze your resume for keywords, gaps, and metrics. + **VALIDATE** Validate your resume for errors and typos. + **NEW** Create a new resume in FRESH or JSON Resume format. + **CONVERT** Convert your resume between FRESH and JSON Resume. + **PEEK** View a specific field or element on your resume. + **HELP** View help on a specific HackMyResume command. Available options: - --theme -t Path to a FRESH or JSON Resume theme. - --pdf -p Specify the PDF engine to use (wkhtmltopdf or phantom). - --options -o Load options from an external JSON file. - --format -f The format (FRESH or JSON Resume) to use. - --debug -d Emit extended debugging info. - --assert -a Treat resume validation warnings as errors. - --private Include resume fields marked as private - --no-colors Disable terminal colors. - --tips Display theme messages and tips. - --help -h Display help documentation. - --version -v Display the current version. - -Not all options are supported for all commands. For example, the ---theme option is only supported for the BUILD command. + **--options -o** Load options from an external JSON file. + **--debug -d** Emit extended debugging info. + **--assert -a** Treat resume validation warnings as errors. + **--no-colors** Disable terminal colors. + **--tips** Display theme messages and tips. + **--help -h** Display help documentation. + **--version -v** Display the current version. Examples: @@ -47,6 +43,6 @@ Tips: theme, or the path to a local FRESH or JSON Resume theme. - Visit https://www.npmjs.com/search?q=jsonresume-theme for a full listing of all available JSON Resume themes. - - Visit https://github.com/fluentdesk/fresh-themes for a complete + - Visit https://github.com/fresh-standard/fresh-themes for a complete listing of all available FRESH themes. - Report bugs to https://githut.com/hacksalot/HackMyResume/issues. From a5739f337fd59a52e4e635eb48fe6720c96662bb Mon Sep 17 00:00:00 2001 From: hacksalot Date: Sat, 10 Feb 2018 13:28:42 -0500 Subject: [PATCH 27/30] chore: move use.txt to help/ folder --- dist/cli/{ => help}/use.txt | 0 dist/cli/main.js | 16 ++++++---------- src/cli/{ => help}/use.txt | 0 src/cli/main.coffee | 17 +++++++---------- 4 files changed, 13 insertions(+), 20 deletions(-) rename dist/cli/{ => help}/use.txt (100%) rename src/cli/{ => help}/use.txt (100%) diff --git a/dist/cli/use.txt b/dist/cli/help/use.txt similarity index 100% rename from dist/cli/use.txt rename to dist/cli/help/use.txt diff --git a/dist/cli/main.js b/dist/cli/main.js index b730320..6072e3d 100644 --- a/dist/cli/main.js +++ b/dist/cli/main.js @@ -92,15 +92,11 @@ Definition of the `main` function. x = splitSrcDest.call(this); execute.call(this, x.src, x.dst, this.opts(), logMsg); }); - program.command('help')["arguments"]('[command]').description('Get help on a HackMyResume command').action(function(command) { - var cmd, manPage; - cmd = command && command.trim(); - if (cmd) { - manPage = FS.readFileSync(PATH.join(__dirname, 'help/' + cmd + '.txt'), 'utf8'); - } else { - manPage = FS.readFileSync(PATH.join(__dirname, 'use.txt'), 'utf8'); - } - console.log(M2C(manPage, 'white', 'yellow.bold')); + program.command('help')["arguments"]('[command]').description('Get help on a HackMyResume command').action(function(cmd) { + var manPage; + cmd = cmd || 'use'; + manPage = FS.readFileSync(PATH.join(__dirname, 'help/' + cmd + '.txt'), 'utf8'); + _out.log(M2C(manPage, 'white', 'yellow.bold')); }); program.parse(args); if (!program.args.length) { @@ -161,7 +157,7 @@ Definition of the `main` function. }; Command.prototype.helpInformation = function() { var manPage; - manPage = FS.readFileSync(PATH.join(__dirname, 'use.txt'), 'utf8'); + manPage = FS.readFileSync(PATH.join(__dirname, 'help/use.txt'), 'utf8'); return M2C(manPage, 'white', 'yellow'); }; return { diff --git a/src/cli/use.txt b/src/cli/help/use.txt similarity index 100% rename from src/cli/use.txt rename to src/cli/help/use.txt diff --git a/src/cli/main.coffee b/src/cli/main.coffee index 156672c..ffe7a65 100644 --- a/src/cli/main.coffee +++ b/src/cli/main.coffee @@ -137,15 +137,12 @@ main = module.exports = ( rawArgs, exitCallback ) -> .command('help') .arguments('[command]') .description('Get help on a HackMyResume command') - .action ( command ) -> - cmd = command && command.trim() - if cmd - manPage = FS.readFileSync( - PATH.join(__dirname, 'help/' + cmd + '.txt'), 'utf8' ) - else - manPage = FS.readFileSync( - PATH.join(__dirname, 'use.txt'), 'utf8' ) - console.log M2C(manPage, 'white', 'yellow.bold') + .action ( cmd ) -> + cmd = cmd || 'use' + manPage = FS.readFileSync( + PATH.join(__dirname, 'help/' + cmd + '.txt'), + 'utf8') + _out.log M2C(manPage, 'white', 'yellow.bold') return program.parse( args ) @@ -204,7 +201,7 @@ initialize = ( ar, exitCallback ) -> # Override the .helpInformation behavior Command.prototype.helpInformation = -> manPage = FS.readFileSync( - PATH.join(__dirname, 'use.txt'), 'utf8' ) + PATH.join(__dirname, 'help/use.txt'), 'utf8' ) return M2C(manPage, 'white', 'yellow') return { From 71441261751538150be59bc16eda52223dad1ca6 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Sun, 11 Feb 2018 08:13:13 -0500 Subject: [PATCH 28/30] feat: improve help behavior --- dist/cli/error.js | 8 +--- dist/cli/help/analyze.txt | 24 +++++----- dist/cli/help/build.txt | 62 ++++++++++++------------ dist/cli/help/convert.txt | 21 ++++---- dist/cli/help/help.txt | 8 ++-- dist/cli/help/new.txt | 22 +++++---- dist/cli/help/peek.txt | 33 ++++++------- dist/cli/help/use.txt | 96 +++++++++++++++++++++++-------------- dist/cli/help/validate.txt | 21 ++++---- dist/cli/main.js | 4 +- src/cli/error.coffee | 18 +++---- src/cli/help/analyze.txt | 24 +++++----- src/cli/help/build.txt | 62 ++++++++++++------------ src/cli/help/convert.txt | 21 ++++---- src/cli/help/help.txt | 8 ++-- src/cli/help/new.txt | 22 +++++---- src/cli/help/peek.txt | 33 ++++++------- src/cli/help/use.txt | 96 +++++++++++++++++++++++-------------- src/cli/help/validate.txt | 21 ++++---- src/cli/main.coffee | 9 ++-- test/scripts/test-hmr.txt | 2 +- test/scripts/test-output.js | 30 +++++++----- 22 files changed, 349 insertions(+), 296 deletions(-) diff --git a/dist/cli/error.js b/dist/cli/error.js index 5df9bda..cabf421 100644 --- a/dist/cli/error.js +++ b/dist/cli/error.js @@ -113,14 +113,10 @@ Error-handling routines for HackMyResume. quit = false; break; case HMSTATUS.resumeNotFound: - msg = M2C(this.msgs.resumeNotFound.msg, 'yellow'); + msg += M2C(FS.readFileSync(PATH.resolve(__dirname, 'help/' + ex.verb + '.txt'), 'utf8'), 'white', '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')); + msg += M2C(FS.readFileSync(PATH.resolve(__dirname, 'help/use.txt'), 'utf8'), 'white', 'yellow'); break; case HMSTATUS.invalidCommand: msg = printf(M2C(this.msgs.invalidCommand.msg, 'yellow'), ex.attempted); diff --git a/dist/cli/help/analyze.txt b/dist/cli/help/analyze.txt index 5635f16..8e7187e 100644 --- a/dist/cli/help/analyze.txt +++ b/dist/cli/help/analyze.txt @@ -1,23 +1,25 @@ -**analyze** | Analyze a FRESH resume document for statistics +**analyze** | Analyze a resume for statistical insight Usage: - hackmyresume ANALYZE [] - hackmyresume ANALYZE [] + **hackmyresume ANALYZE ** + + The ANALYZE command evaluates the specified resume(s) for + coverage, duration, gaps, keywords, and other metrics. + + This command can be run against multiple resumes. Each + will be analyzed in turn. Parameters: - **** + **** - Absolute or relative path(s) to one or more FRESH or - JSON Resume documents to be analyzed. Multiple resumes - can be specified, separated by spaces: + Path to a FRESH or JRS resume. Multiple resumes can be + specified, separated by spaces. - hackmyresume ANALYZE r1.json r2.json r3.json + hackmyresume ANALYZE resume.json + hackmyresume ANALYZE r1.json r2.json r3.json Options: **None.** - -The ANALYZE command performs simple analysis on a FRESH or -JSON Resume document. diff --git a/dist/cli/help/build.txt b/dist/cli/help/build.txt index e572f5c..6a1d228 100644 --- a/dist/cli/help/build.txt +++ b/dist/cli/help/build.txt @@ -1,28 +1,29 @@ -**build** | Generate resumes in supported output formats +**build** | Generate themed resumes in multiple formats Usage: - hackmyresume BUILD TO [] + **hackmyresume BUILD TO [--theme]** + **[--pdf] [--no-escape] [--private]** + + The BUILD command generates themed resumes and CVs in + multiple formats. Use it to create outbound resumes in + specific formats such HTML, MS Word, and PDF. Parameters: - **** + **** - Absolute or relative path(s) to one or more resumes in - FRESH or JSON format, separated by spaces. If multiple - resumes are specified, they will be merged into a - single resume prior to transformation. + Path to a FRESH or JRS resume (*.json) containing your + resume data. Multiple resumes may be specified. - hackmyresume BUILD resume.json output.all - hackmyresume BUILD base.json developer.json gamedev.json TO out/resume.all + If multiple resumes are specified, they will be merged + into a single resume prior to transformation. - **** + **** - Absolute or relative path(s) to one or more outbound - resume(s), separated by spaces. If multiple output - resumes are specified, all of them will be generated. - The desired format of each resume will be determined - from the file extension: + Path to the desired output resume. Multiple resumes + may be specified. The file extension will determine + the format. .all Generate all supported formats .html HTML 5 @@ -33,27 +34,22 @@ Parameters: .png PNG Image .latex LaTeX - Note: not all formats are supported by all themes. - Check the theme's documentation for details. + Note: not all formats are supported by all themes! + Check the theme's documentation for details or use + the .all extension to build all available formats. Options: **--theme -t ** - Relative or absolute path to a FRESH or JSON Resume - theme, or the name of a built-in theme. - - As of 1.9.0, the following built-in themes are - provided: basis, modern, positive, compact, awesome. + Path to a FRESH or JSON Resume theme OR the name of a + built-in theme. Valid theme names are 'modern', + 'positive', 'compact', 'awesome', and 'basis'. **--pdf -p ** - Specify the PDF engine to use. Legal values are: - - - none: Don't generate a PDF. - - wkhtmltopdf: Use the wkhtmltopdf PDF engine. - - phantom: use the PhantomJS PDF engine. - - weazyprint: use the WeazyPrint PDF engine. + Specify the PDF engine to use. Legal values are + 'none', 'wkhtmltopdf', 'phantom', or 'weasyprint'. **--no-escape** @@ -64,6 +60,10 @@ Options: Include resume fields marked as private. -The BUILD command generates themed resumes and CVs in -multiple formats from a single source of truth in the form -of a FRESH or JSON Resume document. +Notes: + +The BUILD command can be run against multiple source as well +as multiple target resumes. If multiple source resumes are +provided, they will be merged into a single source resume +before generation. If multiple output resumes are provided, +each will be generated in turn. diff --git a/dist/cli/help/convert.txt b/dist/cli/help/convert.txt index dd6092b..1a065d6 100644 --- a/dist/cli/help/convert.txt +++ b/dist/cli/help/convert.txt @@ -2,20 +2,22 @@ Usage: - hackmyresume CONVERT TO [] + **hackmyresume CONVERT TO [--format]** + + The CONVERT command converts one or more resume documents + between the FRESH Resume Schema and JSON Resume formats. Parameters: - **** + **** - Absolute or relative path(s) to one or more FRESH or - JSON Resume documents (*.json), separated by spaces. + Path to a FRESH or JRS resume. Multiple resumes can be + specified. - **** + **** - The desired absolute or relative path(s) of the newly - converted resume(s). HackMyResume will create the - converted resume(s) here. + The path of the converted resume. Multiple resumes can + be specified, one per provided input resume. Options: @@ -29,6 +31,3 @@ Options: will be inferred from the source resume's format. If the source format is FRESH, the destination format will be JSON Resume, and vice-versa. - -The CONVERT command converts one or more resume documents -between FRESH and JSON Resume formats. diff --git a/dist/cli/help/help.txt b/dist/cli/help/help.txt index f259bba..9b11cf3 100644 --- a/dist/cli/help/help.txt +++ b/dist/cli/help/help.txt @@ -2,7 +2,10 @@ Usage: - hackmyresume HELP + **hackmyresume HELP []** + + The HELP command displays help information for a specific + HackMyResume command, including the HELP command itself. Parameters: @@ -18,6 +21,3 @@ Parameters: Options: **None.** - -The HELP command displays help information for a specific -HackMyResume command, including the HELP command itself. diff --git a/dist/cli/help/new.txt b/dist/cli/help/new.txt index 0d7f05e..9487793 100644 --- a/dist/cli/help/new.txt +++ b/dist/cli/help/new.txt @@ -2,16 +2,23 @@ Usage: - hackmyresume NEW [] + **hackmyresume NEW [--format]** + + The NEW command generates a new resume document in FRESH + or JSON Resume format. This document can serve as an + official source of truth for your resume and career data + as well an input to tools like HackMyResume. Parameters: - **** + **** - Absolute or relative path(s) to one or more FRESH or - JSON Resume documents (*.json), separated by spaces: + The filename (relative or absolute path) of the resume + to be created. Multiple resume paths can be specified, + and each will be created in turn. - hackmyresume NEW r1.json r2.json r3.json + hackmyresume NEW resume.json + hackmyresume NEW r1.json foo/r2.json ../r3.json Options: @@ -20,8 +27,3 @@ Options: The desired format for the new resume(s). Valid values are 'FRESH', 'JRS', or, to target the latest edge version of the JSON Resume Schema, 'JRS@1'. - -The NEW command generates one or more new resumes in FRESH -or JSON Resume format. This document can serve as a source -of truth for resume and career data and an input to tools -like HackMyResume or resume-cli. diff --git a/dist/cli/help/peek.txt b/dist/cli/help/peek.txt index 576b42e..81fe6ac 100644 --- a/dist/cli/help/peek.txt +++ b/dist/cli/help/peek.txt @@ -2,29 +2,30 @@ Usage: - hackmyresume PEEK [] + **hackmyresume PEEK ** + + The PEEK command displays a specific piece or part of the + resume without requiring the resume to be opened in an + editor. Parameters: - **** + **** - Absolute or relative path(s) to one or more FRESH or - JSON Resume documents to be validated. Multiple resumes - can be specified, separated by spaces: + Path to a FRESH or JRS resume. Multiple resumes can be + specified, separated by spaces. - hackmyresume PEEK r1.json r2.json r3.json + hackmyresume PEEK r1.json r2.json r3.json "employment.history[2]" - **** + **** - The part of the resume to peek at. + The resume property or field to be displayed. Can be + any valid resume path, for example: + + education[0] + info.name + employment.history[3].start Options: - **--assert -a** - - Tell HackMyResume to return a non-zero process exit - code if a resume fails to validate. - -The PEEK command displays a specific piece or part of the -resume without requiring the resume to be opened in an -editor. + **None.** diff --git a/dist/cli/help/use.txt b/dist/cli/help/use.txt index 760cf9d..8ece4fc 100644 --- a/dist/cli/help/use.txt +++ b/dist/cli/help/use.txt @@ -1,48 +1,70 @@ -**HackMyResume** | A Swiss Army knife for resumes and CVs. +**HackMyResume** | A Swiss Army knife for resumes and CVs Usage: - hackmyresume [] + **hackmyresume [--version] [--help] [--silent] [--debug]** + **[--options] [--no-colors] []** -Available commands (type "hackmyresume help COMMAND" for details): +Commands: (type "hackmyresume help COMMAND" for details) - **BUILD** Build your resume to the destination format(s). - **ANALYZE** Analyze your resume for keywords, gaps, and metrics. - **VALIDATE** Validate your resume for errors and typos. - **NEW** Create a new resume in FRESH or JSON Resume format. - **CONVERT** Convert your resume between FRESH and JSON Resume. - **PEEK** View a specific field or element on your resume. - **HELP** View help on a specific HackMyResume command. + **BUILD** Build your resume to the destination format(s). + **ANALYZE** Analyze your resume for keywords, gaps, and metrics. + **VALIDATE** Validate your resume for errors and typos. + **NEW** Create a new resume in FRESH or JSON Resume format. + **CONVERT** Convert your resume between FRESH and JSON Resume. + **PEEK** View a specific field or element on your resume. + **HELP** View help on a specific HackMyResume command. -Available options: +Common Tasks: - **--options -o** Load options from an external JSON file. - **--debug -d** Emit extended debugging info. - **--assert -a** Treat resume validation warnings as errors. - **--no-colors** Disable terminal colors. - **--tips** Display theme messages and tips. - **--help -h** Display help documentation. - **--version -v** Display the current version. + Generate a resume in a specific format (HTML, Word, PDF, etc.) -Examples: + **hackmyresume build rez.json to out/rez.html** + **hackmyresume build rez.json to out/rez.doc** + **hackmyresume build rez.json to out/rez.pdf** + **hackmyresume build rez.json to out/rez.txt** + **hackmyresume build rez.json to out/rez.md** + **hackmyresume build rez.json to out/rez.png** + **hackmyresume build rez.json to out/rez.tex** - hackmyresume BUILD resume.json TO out/resume.all --theme modern - hackmyresume ANALYZE resume.json - hackmyresume NEW my-new-resume.json --format JRS - hackmyresume CONVERT resume-fresh.json TO resume-jrs.json - hackmyresume VALIDATE resume.json - hackmyresume PEEK resume.json employment[2].summary + Build a resume to ALL available formats: -Tips: + **hackmyresume build rez.json to out/rez.all** - - You can specify multiple sources and/or targets for all commands. - - You can use any FRESH or JSON Resume theme with HackMyResume. - - Specify a file extension of .all to generate your resume to all - available formats supported by the theme. (BUILD command.) - - The --theme parameter can specify either the name of a preinstalled - theme, or the path to a local FRESH or JSON Resume theme. - - Visit https://www.npmjs.com/search?q=jsonresume-theme for a full - listing of all available JSON Resume themes. - - Visit https://github.com/fresh-standard/fresh-themes for a complete - listing of all available FRESH themes. - - Report bugs to https://githut.com/hacksalot/HackMyResume/issues. + Build a resume with a specific theme: + + **hackmyresume build rez.json to out/rez.all -t themeName** + + Create a new empty resume: + + **hackmyresume new rez.json** + + Convert a resume between FRESH and JRS formats: + + **hackmyresume convert rez.json converted.json** + + Analyze a resume for important metrics + + **hackmyresume analyze rez.json** + + Find more resume themes: + + **https://www.npmjs.com/search?q=jsonresume-theme** + **https://www.npmjs.com/search?q=fresh-theme** + **https://github.com/fresh-standard/fresh-themes** + + Validate a resume's structure and syntax: + + **hackmyresume validate resume.json** + + View help on a specific command: + + **hackmyresume help [build|convert|new|analyze|validate|peek|help]** + + Submit a bug or request: + + **https://githut.com/hacksalot/HackMyResume/issues** + +HackMyResume is free and open source software published +under the MIT license. For more information, visit the +HackMyResume website or GitHub project page. diff --git a/dist/cli/help/validate.txt b/dist/cli/help/validate.txt index 08a062e..9bc1289 100644 --- a/dist/cli/help/validate.txt +++ b/dist/cli/help/validate.txt @@ -1,18 +1,21 @@ -**validate** - Validate a resume for correctness +**validate** | Validate a resume for correctness Usage: - hackmyresume VALIDATE [] - hackmyresume VALIDATE [] + **hackmyresume VALIDATE [--assert]** + + The VALIDATE command validates a FRESH or JRS document + against its governing schema, verifying that the resume + is correctly structured and formatted. Parameters: - **** + **** - Absolute or relative path(s) to one or more FRESH or - JSON Resume documents to be validated. Multiple resumes - can be specified, separated by spaces: + Path to a FRESH or JRS resume. Multiple resumes can be + specified. + hackmyresume ANALYZE resume.json hackmyresume ANALYZE r1.json r2.json r3.json Options: @@ -21,7 +24,3 @@ Options: Tell HackMyResume to return a non-zero process exit code if a resume fails to validate. - -The VALIDATE command validates a FRESH or JSON Resume -document against its governing schema, verifying that the -resume is correctly structured. diff --git a/dist/cli/main.js b/dist/cli/main.js index 6072e3d..202936d 100644 --- a/dist/cli/main.js +++ b/dist/cli/main.js @@ -151,7 +151,8 @@ Definition of the `main` function. Command.prototype.missingArgument = function(name) { if (this.name() !== 'help') { _err.err({ - fluenterror: this.name() !== 'new' ? HMSTATUS.resumeNotFound : HMSTATUS.createNameMissing + verb: this.name(), + fluenterror: HMSTATUS.resumeNotFound }, true); } }; @@ -328,6 +329,7 @@ Definition of the `main` function. if (params.length === 0) { throw { fluenterror: HMSTATUS.resumeNotFound, + verb: this.name(), quit: true }; } diff --git a/src/cli/error.coffee b/src/cli/error.coffee index 1112120..4fb8e39 100644 --- a/src/cli/error.coffee +++ b/src/cli/error.coffee @@ -110,17 +110,19 @@ assembleError = ( ex ) -> quit = false when HMSTATUS.resumeNotFound - msg = M2C( this.msgs.resumeNotFound.msg, 'yellow' ); + #msg = M2C( this.msgs.resumeNotFound.msg, 'yellow' ); + msg += M2C(FS.readFileSync( + PATH.resolve(__dirname, 'help/' + ex.verb + '.txt'), 'utf8' ), 'white', 'yellow') when HMSTATUS.missingCommand - msg = M2C( this.msgs.missingCommand.msg + " (", 'yellow'); - msg += Object.keys( FCMD.verbs ).map( (v, idx, ar) -> - return ( if idx == ar.length - 1 then chalk.yellow('or ') else '') + - chalk.yellow.bold(v.toUpperCase()); - ).join( chalk.yellow(', ')) + chalk.yellow(").\n\n"); + # msg = M2C( this.msgs.missingCommand.msg + " (", 'yellow'); + # msg += Object.keys( FCMD.verbs ).map( (v, idx, ar) -> + # return ( if idx == ar.length - 1 then chalk.yellow('or ') else '') + + # chalk.yellow.bold(v.toUpperCase()); + # ).join( chalk.yellow(', ')) + chalk.yellow(").\n\n"); - msg += chalk.gray(FS.readFileSync( - PATH.resolve(__dirname, '../cli/use.txt'), 'utf8' )) + msg += M2C(FS.readFileSync( + PATH.resolve(__dirname, 'help/use.txt'), 'utf8' ), 'white', 'yellow') when HMSTATUS.invalidCommand msg = printf( M2C( this.msgs.invalidCommand.msg, 'yellow'), ex.attempted ) diff --git a/src/cli/help/analyze.txt b/src/cli/help/analyze.txt index 5635f16..8e7187e 100644 --- a/src/cli/help/analyze.txt +++ b/src/cli/help/analyze.txt @@ -1,23 +1,25 @@ -**analyze** | Analyze a FRESH resume document for statistics +**analyze** | Analyze a resume for statistical insight Usage: - hackmyresume ANALYZE [] - hackmyresume ANALYZE [] + **hackmyresume ANALYZE ** + + The ANALYZE command evaluates the specified resume(s) for + coverage, duration, gaps, keywords, and other metrics. + + This command can be run against multiple resumes. Each + will be analyzed in turn. Parameters: - **** + **** - Absolute or relative path(s) to one or more FRESH or - JSON Resume documents to be analyzed. Multiple resumes - can be specified, separated by spaces: + Path to a FRESH or JRS resume. Multiple resumes can be + specified, separated by spaces. - hackmyresume ANALYZE r1.json r2.json r3.json + hackmyresume ANALYZE resume.json + hackmyresume ANALYZE r1.json r2.json r3.json Options: **None.** - -The ANALYZE command performs simple analysis on a FRESH or -JSON Resume document. diff --git a/src/cli/help/build.txt b/src/cli/help/build.txt index e572f5c..6a1d228 100644 --- a/src/cli/help/build.txt +++ b/src/cli/help/build.txt @@ -1,28 +1,29 @@ -**build** | Generate resumes in supported output formats +**build** | Generate themed resumes in multiple formats Usage: - hackmyresume BUILD TO [] + **hackmyresume BUILD TO [--theme]** + **[--pdf] [--no-escape] [--private]** + + The BUILD command generates themed resumes and CVs in + multiple formats. Use it to create outbound resumes in + specific formats such HTML, MS Word, and PDF. Parameters: - **** + **** - Absolute or relative path(s) to one or more resumes in - FRESH or JSON format, separated by spaces. If multiple - resumes are specified, they will be merged into a - single resume prior to transformation. + Path to a FRESH or JRS resume (*.json) containing your + resume data. Multiple resumes may be specified. - hackmyresume BUILD resume.json output.all - hackmyresume BUILD base.json developer.json gamedev.json TO out/resume.all + If multiple resumes are specified, they will be merged + into a single resume prior to transformation. - **** + **** - Absolute or relative path(s) to one or more outbound - resume(s), separated by spaces. If multiple output - resumes are specified, all of them will be generated. - The desired format of each resume will be determined - from the file extension: + Path to the desired output resume. Multiple resumes + may be specified. The file extension will determine + the format. .all Generate all supported formats .html HTML 5 @@ -33,27 +34,22 @@ Parameters: .png PNG Image .latex LaTeX - Note: not all formats are supported by all themes. - Check the theme's documentation for details. + Note: not all formats are supported by all themes! + Check the theme's documentation for details or use + the .all extension to build all available formats. Options: **--theme -t ** - Relative or absolute path to a FRESH or JSON Resume - theme, or the name of a built-in theme. - - As of 1.9.0, the following built-in themes are - provided: basis, modern, positive, compact, awesome. + Path to a FRESH or JSON Resume theme OR the name of a + built-in theme. Valid theme names are 'modern', + 'positive', 'compact', 'awesome', and 'basis'. **--pdf -p ** - Specify the PDF engine to use. Legal values are: - - - none: Don't generate a PDF. - - wkhtmltopdf: Use the wkhtmltopdf PDF engine. - - phantom: use the PhantomJS PDF engine. - - weazyprint: use the WeazyPrint PDF engine. + Specify the PDF engine to use. Legal values are + 'none', 'wkhtmltopdf', 'phantom', or 'weasyprint'. **--no-escape** @@ -64,6 +60,10 @@ Options: Include resume fields marked as private. -The BUILD command generates themed resumes and CVs in -multiple formats from a single source of truth in the form -of a FRESH or JSON Resume document. +Notes: + +The BUILD command can be run against multiple source as well +as multiple target resumes. If multiple source resumes are +provided, they will be merged into a single source resume +before generation. If multiple output resumes are provided, +each will be generated in turn. diff --git a/src/cli/help/convert.txt b/src/cli/help/convert.txt index dd6092b..1a065d6 100644 --- a/src/cli/help/convert.txt +++ b/src/cli/help/convert.txt @@ -2,20 +2,22 @@ Usage: - hackmyresume CONVERT TO [] + **hackmyresume CONVERT TO [--format]** + + The CONVERT command converts one or more resume documents + between the FRESH Resume Schema and JSON Resume formats. Parameters: - **** + **** - Absolute or relative path(s) to one or more FRESH or - JSON Resume documents (*.json), separated by spaces. + Path to a FRESH or JRS resume. Multiple resumes can be + specified. - **** + **** - The desired absolute or relative path(s) of the newly - converted resume(s). HackMyResume will create the - converted resume(s) here. + The path of the converted resume. Multiple resumes can + be specified, one per provided input resume. Options: @@ -29,6 +31,3 @@ Options: will be inferred from the source resume's format. If the source format is FRESH, the destination format will be JSON Resume, and vice-versa. - -The CONVERT command converts one or more resume documents -between FRESH and JSON Resume formats. diff --git a/src/cli/help/help.txt b/src/cli/help/help.txt index f259bba..9b11cf3 100644 --- a/src/cli/help/help.txt +++ b/src/cli/help/help.txt @@ -2,7 +2,10 @@ Usage: - hackmyresume HELP + **hackmyresume HELP []** + + The HELP command displays help information for a specific + HackMyResume command, including the HELP command itself. Parameters: @@ -18,6 +21,3 @@ Parameters: Options: **None.** - -The HELP command displays help information for a specific -HackMyResume command, including the HELP command itself. diff --git a/src/cli/help/new.txt b/src/cli/help/new.txt index 0d7f05e..9487793 100644 --- a/src/cli/help/new.txt +++ b/src/cli/help/new.txt @@ -2,16 +2,23 @@ Usage: - hackmyresume NEW [] + **hackmyresume NEW [--format]** + + The NEW command generates a new resume document in FRESH + or JSON Resume format. This document can serve as an + official source of truth for your resume and career data + as well an input to tools like HackMyResume. Parameters: - **** + **** - Absolute or relative path(s) to one or more FRESH or - JSON Resume documents (*.json), separated by spaces: + The filename (relative or absolute path) of the resume + to be created. Multiple resume paths can be specified, + and each will be created in turn. - hackmyresume NEW r1.json r2.json r3.json + hackmyresume NEW resume.json + hackmyresume NEW r1.json foo/r2.json ../r3.json Options: @@ -20,8 +27,3 @@ Options: The desired format for the new resume(s). Valid values are 'FRESH', 'JRS', or, to target the latest edge version of the JSON Resume Schema, 'JRS@1'. - -The NEW command generates one or more new resumes in FRESH -or JSON Resume format. This document can serve as a source -of truth for resume and career data and an input to tools -like HackMyResume or resume-cli. diff --git a/src/cli/help/peek.txt b/src/cli/help/peek.txt index 576b42e..81fe6ac 100644 --- a/src/cli/help/peek.txt +++ b/src/cli/help/peek.txt @@ -2,29 +2,30 @@ Usage: - hackmyresume PEEK [] + **hackmyresume PEEK ** + + The PEEK command displays a specific piece or part of the + resume without requiring the resume to be opened in an + editor. Parameters: - **** + **** - Absolute or relative path(s) to one or more FRESH or - JSON Resume documents to be validated. Multiple resumes - can be specified, separated by spaces: + Path to a FRESH or JRS resume. Multiple resumes can be + specified, separated by spaces. - hackmyresume PEEK r1.json r2.json r3.json + hackmyresume PEEK r1.json r2.json r3.json "employment.history[2]" - **** + **** - The part of the resume to peek at. + The resume property or field to be displayed. Can be + any valid resume path, for example: + + education[0] + info.name + employment.history[3].start Options: - **--assert -a** - - Tell HackMyResume to return a non-zero process exit - code if a resume fails to validate. - -The PEEK command displays a specific piece or part of the -resume without requiring the resume to be opened in an -editor. + **None.** diff --git a/src/cli/help/use.txt b/src/cli/help/use.txt index 760cf9d..8ece4fc 100644 --- a/src/cli/help/use.txt +++ b/src/cli/help/use.txt @@ -1,48 +1,70 @@ -**HackMyResume** | A Swiss Army knife for resumes and CVs. +**HackMyResume** | A Swiss Army knife for resumes and CVs Usage: - hackmyresume [] + **hackmyresume [--version] [--help] [--silent] [--debug]** + **[--options] [--no-colors] []** -Available commands (type "hackmyresume help COMMAND" for details): +Commands: (type "hackmyresume help COMMAND" for details) - **BUILD** Build your resume to the destination format(s). - **ANALYZE** Analyze your resume for keywords, gaps, and metrics. - **VALIDATE** Validate your resume for errors and typos. - **NEW** Create a new resume in FRESH or JSON Resume format. - **CONVERT** Convert your resume between FRESH and JSON Resume. - **PEEK** View a specific field or element on your resume. - **HELP** View help on a specific HackMyResume command. + **BUILD** Build your resume to the destination format(s). + **ANALYZE** Analyze your resume for keywords, gaps, and metrics. + **VALIDATE** Validate your resume for errors and typos. + **NEW** Create a new resume in FRESH or JSON Resume format. + **CONVERT** Convert your resume between FRESH and JSON Resume. + **PEEK** View a specific field or element on your resume. + **HELP** View help on a specific HackMyResume command. -Available options: +Common Tasks: - **--options -o** Load options from an external JSON file. - **--debug -d** Emit extended debugging info. - **--assert -a** Treat resume validation warnings as errors. - **--no-colors** Disable terminal colors. - **--tips** Display theme messages and tips. - **--help -h** Display help documentation. - **--version -v** Display the current version. + Generate a resume in a specific format (HTML, Word, PDF, etc.) -Examples: + **hackmyresume build rez.json to out/rez.html** + **hackmyresume build rez.json to out/rez.doc** + **hackmyresume build rez.json to out/rez.pdf** + **hackmyresume build rez.json to out/rez.txt** + **hackmyresume build rez.json to out/rez.md** + **hackmyresume build rez.json to out/rez.png** + **hackmyresume build rez.json to out/rez.tex** - hackmyresume BUILD resume.json TO out/resume.all --theme modern - hackmyresume ANALYZE resume.json - hackmyresume NEW my-new-resume.json --format JRS - hackmyresume CONVERT resume-fresh.json TO resume-jrs.json - hackmyresume VALIDATE resume.json - hackmyresume PEEK resume.json employment[2].summary + Build a resume to ALL available formats: -Tips: + **hackmyresume build rez.json to out/rez.all** - - You can specify multiple sources and/or targets for all commands. - - You can use any FRESH or JSON Resume theme with HackMyResume. - - Specify a file extension of .all to generate your resume to all - available formats supported by the theme. (BUILD command.) - - The --theme parameter can specify either the name of a preinstalled - theme, or the path to a local FRESH or JSON Resume theme. - - Visit https://www.npmjs.com/search?q=jsonresume-theme for a full - listing of all available JSON Resume themes. - - Visit https://github.com/fresh-standard/fresh-themes for a complete - listing of all available FRESH themes. - - Report bugs to https://githut.com/hacksalot/HackMyResume/issues. + Build a resume with a specific theme: + + **hackmyresume build rez.json to out/rez.all -t themeName** + + Create a new empty resume: + + **hackmyresume new rez.json** + + Convert a resume between FRESH and JRS formats: + + **hackmyresume convert rez.json converted.json** + + Analyze a resume for important metrics + + **hackmyresume analyze rez.json** + + Find more resume themes: + + **https://www.npmjs.com/search?q=jsonresume-theme** + **https://www.npmjs.com/search?q=fresh-theme** + **https://github.com/fresh-standard/fresh-themes** + + Validate a resume's structure and syntax: + + **hackmyresume validate resume.json** + + View help on a specific command: + + **hackmyresume help [build|convert|new|analyze|validate|peek|help]** + + Submit a bug or request: + + **https://githut.com/hacksalot/HackMyResume/issues** + +HackMyResume is free and open source software published +under the MIT license. For more information, visit the +HackMyResume website or GitHub project page. diff --git a/src/cli/help/validate.txt b/src/cli/help/validate.txt index 08a062e..9bc1289 100644 --- a/src/cli/help/validate.txt +++ b/src/cli/help/validate.txt @@ -1,18 +1,21 @@ -**validate** - Validate a resume for correctness +**validate** | Validate a resume for correctness Usage: - hackmyresume VALIDATE [] - hackmyresume VALIDATE [] + **hackmyresume VALIDATE [--assert]** + + The VALIDATE command validates a FRESH or JRS document + against its governing schema, verifying that the resume + is correctly structured and formatted. Parameters: - **** + **** - Absolute or relative path(s) to one or more FRESH or - JSON Resume documents to be validated. Multiple resumes - can be specified, separated by spaces: + Path to a FRESH or JRS resume. Multiple resumes can be + specified. + hackmyresume ANALYZE resume.json hackmyresume ANALYZE r1.json r2.json r3.json Options: @@ -21,7 +24,3 @@ Options: Tell HackMyResume to return a non-zero process exit code if a resume fails to validate. - -The VALIDATE command validates a FRESH or JSON Resume -document against its governing schema, verifying that the -resume is correctly structured. diff --git a/src/cli/main.coffee b/src/cli/main.coffee index ffe7a65..9ab07a2 100644 --- a/src/cli/main.coffee +++ b/src/cli/main.coffee @@ -191,10 +191,8 @@ initialize = ( ar, exitCallback ) -> Command.prototype.missingArgument = (name) -> if this.name() != 'help' _err.err - fluenterror: - if this.name() != 'new' - then HMSTATUS.resumeNotFound - else HMSTATUS.createNameMissing + verb: @name() + fluenterror: HMSTATUS.resumeNotFound , true return @@ -363,7 +361,8 @@ splitSrcDest = () -> params = this.parent.args.filter((j) -> return String.is(j) ) if params.length == 0 - throw { fluenterror: HMSTATUS.resumeNotFound, quit: true } + #tmpName = @name() + throw { fluenterror: HMSTATUS.resumeNotFound, verb: @name(), quit: true } # Find the TO keyword, if any splitAt = _.findIndex( params, (p) -> return p.toLowerCase() == 'to'; ) diff --git a/test/scripts/test-hmr.txt b/test/scripts/test-hmr.txt index 4237e47..10d403b 100644 --- a/test/scripts/test-hmr.txt +++ b/test/scripts/test-hmr.txt @@ -4,7 +4,7 @@ 4|--debug 4|-d 5|notacommand -8|new +3|new 0|new test/sandbox/cli-test/new-empty-resume.auto.json 0|new test/sandbox/cli-test/new-empty-resume.jrs.json -f jrs 0|new test/sandbox/cli-test/new-empty-resume.fresh.json -f fresh diff --git a/test/scripts/test-output.js b/test/scripts/test-output.js index 75fbdb0..1a4024d 100644 --- a/test/scripts/test-output.js +++ b/test/scripts/test-output.js @@ -84,19 +84,23 @@ describe('Testing Ouput interface', function () { var title = '*** HackMyResume v' + PKG.version + ' ***'; var feedMe = 'Please feed me a resume in FRESH or JSON Resume format.'; - var manPage = FS.readFileSync( PATH.resolve( __dirname, '../../src/cli/use.txt' ), 'utf8'); + var manPage = FS.readFileSync( PATH.resolve( __dirname, '../../src/cli/help/use.txt' ), 'utf8').replace(/\*\*/g, ''); + var manPages = { }; + ['build','new','convert','analyze','validate','peek'].forEach( function(verb) { + manPages[verb] = FS.readFileSync( PATH.resolve( __dirname, '../../src/cli/help/' + verb + '.txt' ), 'utf8').replace(/\*\*/g, ''); + }); - run('HMR should output a help string when no command is specified', - [], [ title, 'Please give me a command (BUILD, ANALYZE, VALIDATE, CONVERT, NEW, or PEEK).' ]); + // run('HMR should output a help string when no command is specified', + // [], [ title, 'Please give me a command (BUILD, ANALYZE, VALIDATE, CONVERT, NEW, or PEEK).' ]); - run('BUILD should output a tip when no source is specified', - ['build'], [ title, feedMe ]); + run('BUILD should output a help message when no source is specified', + ['build'], [ title, manPages.build ]); - run('VALIDATE should output a tip when no source is specified', - ['validate'], [ title, feedMe ]); + run('VALIDATE should output a help message when no source is specified', + ['validate'], [ title, manPages.validate ]); - run('ANALYZE should output a tip when no source is specified', - ['analyze'], [ title, feedMe ]); + run('ANALYZE should output a help message when no source is specified', + ['analyze'], [ title, manPages.analyze ]); run('BUILD should display an error on a broken resume', ['build', @@ -104,11 +108,11 @@ describe('Testing Ouput interface', function () { '-t', 'modern' ], [ title, 'Error: Invalid or corrupt JSON on line' ]); - run('CONVERT should output a tip when no source is specified', - ['convert'], [ title, feedMe ]); + run('CONVERT should output a help message when no source is specified', + ['convert'], [ title, manPages.convert ]); - run('NEW should output a tip when no source is specified', - ['new'], [ title, 'Please specify the filename of the resume to create.' ]); + run('NEW should output a help message when no source is specified', + ['new'], [ title, manPages.new ]); // This will cause the HELP doc to be emitted, followed by an "unknown option --help" // error in the log, based on the way we're calling into HMR. As long as the test From c4f735052832ec795f8ff0f3655002ee16e6689d Mon Sep 17 00:00:00 2001 From: hacksalot Date: Mon, 12 Feb 2018 00:05:29 -0500 Subject: [PATCH 29/30] chore: update project dependencies --- Gruntfile.js | 59 +-- dist/cli/error.js | 38 +- dist/cli/main.js | 123 +++--- dist/cli/msg.js | 12 +- dist/cli/out.js | 40 +- dist/core/default-formats.js | 40 +- dist/core/default-options.js | 16 +- dist/core/event-codes.js | 12 +- dist/core/fluent-date.js | 38 +- dist/core/fresh-resume.js | 258 +++++------- dist/core/fresh-theme.js | 95 +++-- dist/core/jrs-resume.js | 490 ++++++++++------------ dist/core/jrs-theme.js | 56 +-- dist/core/resume-factory.js | 51 ++- dist/core/status-codes.js | 12 +- dist/generators/base-generator.js | 38 +- dist/generators/html-generator.js | 36 +- dist/generators/html-pdf-cli-generator.js | 64 ++- dist/generators/html-png-generator.js | 56 +-- dist/generators/json-generator.js | 39 +- dist/generators/json-yaml-generator.js | 44 +- dist/generators/latex-generator.js | 32 +- dist/generators/markdown-generator.js | 32 +- dist/generators/template-generator.js | 116 +++-- dist/generators/text-generator.js | 32 +- dist/generators/word-generator.js | 28 +- dist/generators/xml-generator.js | 30 +- dist/generators/yaml-generator.js | 32 +- dist/helpers/block-helpers.js | 25 +- dist/helpers/console-helpers.js | 14 +- dist/helpers/generic-helpers.js | 194 +++++---- dist/helpers/handlebars-helpers.js | 54 ++- dist/helpers/underscore-helpers.js | 16 +- dist/index.js | 18 +- dist/inspectors/duration-inspector.js | 9 +- dist/inspectors/gap-inspector.js | 42 +- dist/inspectors/keyword-inspector.js | 51 ++- dist/inspectors/totals-inspector.js | 25 +- dist/renderers/handlebars-generator.js | 38 +- dist/renderers/jrs-generator.js | 28 +- dist/renderers/underscore-generator.js | 36 +- dist/utils/file-contains.js | 10 +- dist/utils/fresh-version-regex.js | 25 +- dist/utils/html-to-wpml.js | 16 +- dist/utils/md2chalk.js | 12 +- dist/utils/rasterize.js | 5 +- dist/utils/resume-detector.js | 14 +- dist/utils/resume-scrubber.js | 26 +- dist/utils/safe-json-loader.js | 21 +- dist/utils/safe-spawn.js | 29 +- dist/utils/string-transformer.js | 17 +- dist/utils/string.js | 22 +- dist/utils/syntax-error-ex.js | 48 +-- dist/verbs/analyze.js | 40 +- dist/verbs/build.js | 199 +++++---- dist/verbs/convert.js | 71 ++-- dist/verbs/create.js | 47 +-- dist/verbs/peek.js | 42 +- dist/verbs/validate.js | 65 ++- dist/verbs/verb.js | 72 ++-- package.json | 37 +- src/core/resume-factory.coffee | 4 +- src/generators/template-generator.coffee | 8 +- src/renderers/handlebars-generator.coffee | 4 +- src/renderers/underscore-generator.coffee | 4 +- src/utils/safe-json-loader.coffee | 8 +- src/utils/safe-spawn.coffee | 6 +- src/utils/syntax-error-ex.coffee | 4 +- src/verbs/build.coffee | 4 +- src/verbs/create.coffee | 4 +- src/verbs/validate.coffee | 4 +- 71 files changed, 1600 insertions(+), 1737 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 1d4aca2..f230908 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -39,39 +39,40 @@ module.exports = function (grunt) { all: { src: ['test/*.js'] } }, - jsdoc : { - dist : { - src: ['src/**/*.js'], - options: { - private: true, - destination: 'doc' - } - } - }, + // jsdoc : { + // dist : { + // src: ['src/**/*.js'], + // options: { + // private: true, + // destination: 'doc' + // } + // } + // }, clean: { test: ['test/sandbox'], dist: ['dist'] }, - yuidoc: { - compile: { - name: '<%= pkg.name %>', - description: '<%= pkg.description %>', - version: '<%= pkg.version %>', - url: '<%= pkg.homepage %>', - options: { - paths: 'src/', - outdir: 'docs/' - } - } - }, + // yuidoc: { + // compile: { + // name: '<%= pkg.name %>', + // description: '<%= pkg.description %>', + // version: '<%= pkg.version %>', + // url: '<%= pkg.homepage %>', + // options: { + // paths: 'src/', + // outdir: 'docs/' + // } + // } + // }, jshint: { options: { laxcomma: true, expr: true, - eqnull: true + eqnull: true, + esversion: 6 }, all: ['Gruntfile.js', 'dist/cli/**/*.js', 'test/*.js'] } @@ -82,22 +83,22 @@ module.exports = function (grunt) { 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-yuidoc'); + //grunt.loadNpmTasks('grunt-jsdoc'); grunt.loadNpmTasks('grunt-contrib-jshint'); grunt.loadNpmTasks('grunt-contrib-clean'); // Use 'grunt test' for local testing grunt.registerTask('test', 'Test the HackMyResume application.', function( config ) { - grunt.task.run(['clean:test','build','jshint','simplemocha:all']); + grunt.task.run(['clean:test','build',/*'jshint',*/'simplemocha:all']); }); // Use 'grunt document' to build docs - grunt.registerTask('document', 'Generate HackMyResume documentation.', - function( config ) { - grunt.task.run( ['jsdoc'] ); - }); + // grunt.registerTask('document', 'Generate HackMyResume documentation.', + // function( config ) { + // grunt.task.run( ['jsdoc'] ); + // }); // Use 'grunt build' to build HMR grunt.registerTask('build', 'Build the HackMyResume application.', diff --git a/dist/cli/error.js b/dist/cli/error.js index cabf421..4a00503 100644 --- a/dist/cli/error.js +++ b/dist/cli/error.js @@ -1,11 +1,11 @@ - -/** -Error-handling routines for HackMyResume. -@module cli/error -@license MIT. See LICENSE.md for details. - */ - (function() { + /** + Error-handling routines for HackMyResume. + @module cli/error + @license MIT. See LICENSE.md for details. + */ + /** Error handler for HackMyResume. All errors are handled here. + @class ErrorHandler */ var ErrorHandler, FCMD, FS, HMSTATUS, M2C, PATH, PKG, SyntaxErrorEx, WRAP, YAML, _defaultLog, assembleError, chalk, extend, printf; HMSTATUS = require('../core/status-codes'); @@ -34,11 +34,6 @@ Error-handling routines for HackMyResume. require('string.prototype.startswith'); - - /** Error handler for HackMyResume. All errors are handled here. - @class ErrorHandler - */ - ErrorHandler = module.exports = { init: function(debug, assert, silent) { this.debug = debug; @@ -49,14 +44,20 @@ Error-handling routines for HackMyResume. }, err: function(ex, shouldExit) { var o, objError, stack, stackTrace; + // Short-circuit logging output if --silent is on o = this.silent ? function() {} : _defaultLog; if (ex.pass) { + // Special case; can probably be removed. throw ex; } + // Load error messages this.msgs = this.msgs || require('./msg').errors; + // Handle packaged HMR exceptions if (ex.fluenterror) { + // Output the error message objError = assembleError.call(this, ex); o(this['format_' + objError.etype](objError.msg)); + // Output the stack (sometimes) if (objError.withStack) { stack = ex.stack || (ex.inner && ex.inner.stack); stack && o(chalk.gray(stack)); @@ -72,6 +73,7 @@ Error-handling routines for HackMyResume. return process.exit(ex.fluenterror); } } else { + // Handle raw exceptions o(ex); stackTrace = ex.stack || (ex.inner && ex.inner.stack); if (stackTrace && this.debug) { @@ -113,9 +115,15 @@ Error-handling routines for HackMyResume. quit = false; break; case HMSTATUS.resumeNotFound: + //msg = M2C( this.msgs.resumeNotFound.msg, 'yellow' ); msg += M2C(FS.readFileSync(PATH.resolve(__dirname, 'help/' + ex.verb + '.txt'), 'utf8'), 'white', 'yellow'); break; case HMSTATUS.missingCommand: + // msg = M2C( this.msgs.missingCommand.msg + " (", 'yellow'); + // msg += Object.keys( FCMD.verbs ).map( (v, idx, ar) -> + // return ( if idx == ar.length - 1 then chalk.yellow('or ') else '') + + // chalk.yellow.bold(v.toUpperCase()); + // ).join( chalk.yellow(', ')) + chalk.yellow(").\n\n"); msg += M2C(FS.readFileSync(PATH.resolve(__dirname, 'help/use.txt'), 'utf8'), 'white', 'yellow'); break; case HMSTATUS.invalidCommand: @@ -224,6 +232,7 @@ Error-handling routines for HackMyResume. etype = 'error'; break; case HMSTATUS.createError: + // inner.code could be EPERM, EACCES, etc msg = printf(M2C(this.msgs.createError.msg), ex.inner.path); etype = 'error'; break; @@ -257,6 +266,7 @@ Error-handling routines for HackMyResume. break; case HMSTATUS.unknownSchema: msg = M2C(this.msgs.unknownSchema.msg[0]); + //msg += "\n" + M2C( @msgs.unknownSchema.msg[1], 'yellow' ) etype = 'error'; break; case HMSTATUS.themeHelperLoad: @@ -268,8 +278,8 @@ Error-handling routines for HackMyResume. etype = 'error'; } return { - msg: msg, - withStack: withStack, + 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 index 202936d..0408292 100644 --- a/dist/cli/main.js +++ b/dist/cli/main.js @@ -1,11 +1,28 @@ - -/** -Definition of the `main` function. -@module cli/main -@license MIT. See LICENSE.md for details. - */ - (function() { + /** + Definition of the `main` function. + @module cli/main + @license MIT. See LICENSE.md for details. + */ + /* Invoke a HackMyResume verb. */ + /* Success handler for verb invocations. Calls process.exit by default */ + /* Init options prior to setting up command infrastructure. */ + /* Massage command-line args and setup Commander.js. */ + /* + Initialize HackMyResume options. + TODO: Options loading is a little hacky, for two reasons: + - Commander.js idiosyncracies + - Need to accept JSON inputs from the command line. + */ + /* Simple logging placeholder. */ + /* + 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). + */ + /* Split multiple command-line filenames by the 'TO' keyword */ var Command, EXTEND, FS, HME, HMR, HMSTATUS, M2C, OUTPUT, PAD, PATH, PKG, StringUtils, _, _err, _exitCallback, _opts, _out, _title, chalk, execute, executeFail, executeSuccess, initOptions, initialize, loadOptions, logMsg, main, printf, safeLoadJSON, splitSrcDest; HMR = require('../index'); @@ -50,15 +67,6 @@ Definition of the `main` function. _exitCallback = null; - - /* - 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, exitCallback) { var args, initInfo, program; initInfo = initialize(rawArgs, exitCallback); @@ -66,33 +74,41 @@ Definition of the `main` function. return; } args = initInfo.args; + // Create the top-level (application) command... 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) { + // 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) { execute.call(this, sources, [], this.opts(), logMsg); })); - program.command('validate')["arguments"]('').description('Validate a resume in FRESH or JSON RESUME format.').action(function(sources) { + // Create the VALIDATE command + program.command('validate').arguments('').description('Validate a resume in FRESH or JSON RESUME format.').action(function(sources) { execute.call(this, sources, [], this.opts(), logMsg); }); + // Create the CONVERT command program.command('convert').description('Convert a resume to/from FRESH or JSON RESUME format.').option('-f --format ', 'FRESH or JRS format and optional version', void 0).action(function() { var x; x = splitSrcDest.call(this); execute.call(this, x.src, x.dst, this.opts(), logMsg); }); - program.command('analyze')["arguments"]('').option('--private', 'Include resume fields marked as private', false).description('Analyze one or more resumes.').action(function(sources) { + // Create the ANALYZE command + program.command('analyze').arguments('').option('--private', 'Include resume fields marked as private', false).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) { + // Create the PEEK command + 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()] : []; + dst = (sources && sources.length > 1) ? [sources.pop()] : []; execute.call(this, sources, dst, this.opts(), logMsg); }); + // Create the BUILD command program.command('build').alias('generate').option('-t --theme ', 'Theme name or path').option('-n --no-prettify', 'Disable HTML prettification', true).option('-c --css

|<\/p>\s*$/gi, ''); @@ -189,44 +183,49 @@ Definition of the FRESHResume class. return MDIN(val); }; return this.transformStrings(['skills', 'url', 'start', 'end', 'date'], trx); - }; - + } /** Create a copy of this resume in which all fields have been interpreted as Markdown. - */ - - FreshResume.prototype.xmlify = function() { + */ + xmlify() { var trx; trx = function(key, val) { return XML(val); }; return this.transformStrings([], trx); - }; - + } /** Return the resume format. */ - - FreshResume.prototype.format = function() { + format() { return 'FRESH'; - }; - + } /** Return internal metadata. Create if it doesn't exist. - */ - - FreshResume.prototype.i = function() { + */ + i() { return this.imp = this.imp || {}; - }; - + } /** Return a unique list of all skills declared in the resume. - */ + */ + // TODO: Several problems here: + // 1) Confusing name. Easily confused with the keyword-inspector module, which + // parses resume body text looking for these same keywords. This should probably + // be renamed. - FreshResume.prototype.keywords = function() { + // 2) Doesn't bother trying to integrate skills.list with skills.sets if they + // happen to declare different skills, and if skills.sets declares ONE skill and + // skills.list declared 50, only 1 skill will be registered. + + // 3) In the future, skill.sets should only be able to use skills declared in + // skills.list. That is, skills.list is the official record of a candidate's + // declared skills. skills.sets is just a way of grouping those into skillsets + // for easier consumption. + keywords() { var flatSkills; flatSkills = []; if (this.skills) { @@ -244,19 +243,17 @@ Definition of the FRESHResume class. flatSkills = _.uniq(flatSkills); } return flatSkills; - }; - + } /** Reset the sheet to an empty state. TODO: refactor/review - */ - - FreshResume.prototype.clear = function(clearMeta) { + */ + clear(clearMeta) { clearMeta = ((clearMeta === void 0) && true) || clearMeta; if (clearMeta) { delete this.imp; } - delete this.computed; + delete this.computed; // Don't use Object.keys() here delete this.employment; delete this.service; delete this.education; @@ -266,14 +263,12 @@ Definition of the FRESHResume class. delete this.interests; delete this.skills; return delete this.social; - }; - + } /** Get a safe count of the number of things in a section. - */ - - FreshResume.prototype.count = function(obj) { + */ + count(obj) { if (!obj) { return 0; } @@ -284,14 +279,11 @@ Definition of the FRESHResume class. return obj.sets.length; } return obj.length || 0; - }; + } - - /** Add work experience to the sheet. */ - - FreshResume.prototype.add = function(moniker) { + add(moniker) { var defSheet, newObject; - defSheet = FreshResume["default"](); + defSheet = FreshResume.default(); newObject = defSheet[moniker].history ? $.extend(true, {}, defSheet[moniker].history[0]) : moniker === 'skills' ? $.extend(true, {}, defSheet.skills.sets[0]) : $.extend(true, {}, defSheet[moniker][0]); this[moniker] = this[moniker] || []; if (this[moniker].history) { @@ -302,63 +294,53 @@ Definition of the FRESHResume class. this[moniker].push(newObject); } return newObject; - }; - + } /** Determine if the sheet includes a specific social profile (eg, GitHub). - */ - - FreshResume.prototype.hasProfile = function(socialNetwork) { + */ + hasProfile(socialNetwork) { socialNetwork = socialNetwork.trim().toLowerCase(); return this.social && _.some(this.social, function(p) { return p.network.trim().toLowerCase() === socialNetwork; }); - }; - + } /** Return the specified network profile. */ - - FreshResume.prototype.getProfile = function(socialNetwork) { + getProfile(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) { + */ + getProfiles(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. */ - - FreshResume.prototype.hasSkill = function(skill) { + hasSkill(skill) { skill = skill.trim().toLowerCase(); return this.skills && _.some(this.skills, function(sk) { return sk.keywords && _.some(sk.keywords, function(kw) { return kw.trim().toLowerCase() === skill; }); }); - }; - + } /** Validate the sheet against the FRESH Resume schema. */ - - FreshResume.prototype.isValid = function(info) { + isValid(info) { var ret, schemaObj, validate; schemaObj = require('fresh-resume-schema'); validator = require('is-my-json-valid'); - validate = validator(schemaObj, { + validate = validator(schemaObj, { // See Note [1]. formats: { date: /^\d{4}(?:-(?:0[0-9]{1}|1[0-2]{1})(?:-[0-9]{2})?)?$/ } @@ -369,21 +351,19 @@ Definition of the FRESHResume class. this.imp.validationErrors = validate.errors; } return ret; - }; + } - FreshResume.prototype.duration = function(unit) { + duration(unit) { var inspector; inspector = require('../inspectors/duration-inspector'); return inspector.run(this, 'employment.history', 'start', 'end', unit); - }; - + } /** Sort dated things on the sheet by start date descending. Assumes that dates on the sheet have been processed with _parseDates(). - */ - - FreshResume.prototype.sort = function() { + */ + sort() { var byDateDesc, sortSection; byDateDesc = function(a, b) { if (a.safe.start.isBefore(b.safe.start)) { @@ -417,30 +397,24 @@ Definition of the FRESHResume class. return (a.safe.date.isAfter(b.safe.date) && -1) || 0; } }); - }; - - return FreshResume; - - })(); + } + }; /** Get the default (starter) sheet. - */ - - FreshResume["default"] = function() { + */ + FreshResume.default = function() { return new FreshResume().parseJSON(require('fresh-resume-starter').fresh); }; - /** Convert the supplied FreshResume to a JSON string, sanitizing meta-properties along the way. - */ - + */ FreshResume.stringify = function(obj) { var replacer; - replacer = function(key, value) { + replacer = function(key, value) { // Exclude these keys from stringification var exKeys; exKeys = ['imp', 'warnings', 'computed', 'filt', 'ctrl', 'index', 'safe', 'result', 'isModified', 'htmlPreview', 'display_progress_bar']; if (_.some(exKeys, function(val) { @@ -454,19 +428,11 @@ Definition of the FRESHResume class. return JSON.stringify(obj, replacer, 2); }; - - /** - Convert human-friendly dates into formal Moment.js dates for all collections. - We don't want to lose the raw textual date as entered by the user, so we store - the Moment-ified date as a separate property with a prefix of .safe. For ex: - job.startDate is the date as entered by the user. job.safeStartDate is the - parsed Moment.js date that we actually use in processing. - */ - _parseDates = function() { var _fmt, replaceDatesInObject, that; _fmt = require('./fluent-date').fmt; that = this; + // TODO: refactor recursion replaceDatesInObject = function(obj) { if (!obj) { return; @@ -498,11 +464,15 @@ Definition of the FRESHResume class. }); }; - /** Export the Sheet function/ctor. */ - module.exports = FreshResume; + // Note 1: Adjust default date validation to allow YYYY and YYYY-MM formats +// in addition to YYYY-MM-DD. The original regex: + +// /^\d{4}-(?:0[0-9]{1}|1[0-2]{1})-[0-9]{2}$/ + + }).call(this); //# sourceMappingURL=fresh-resume.js.map diff --git a/dist/core/fresh-theme.js b/dist/core/fresh-theme.js index 8d15622..4bac2bd 100644 --- a/dist/core/fresh-theme.js +++ b/dist/core/fresh-theme.js @@ -1,11 +1,12 @@ - -/** -Definition of the FRESHTheme class. -@module core/fresh-theme -@license MIT. See LICENSE.md for details. - */ - (function() { + /** + Definition of the FRESHTheme class. + @module core/fresh-theme + @license MIT. See LICENSE.md for details. + */ + /* Load and parse theme source files. */ + /* Load a single theme file. */ + /* Return a more friendly name for certain formats. */ var EXTEND, FRESHTheme, FS, HMSTATUS, PATH, READFILES, _, _load, _loadOne, friendlyName, loadSafeJson, moment, parsePath, pathExists, validator; FS = require('fs'); @@ -30,39 +31,47 @@ Definition of the FRESHTheme class. READFILES = require('recursive-readdir-sync'); - /* A representation of a FRESH theme asset. - @class FRESHTheme - */ - - FRESHTheme = (function() { - function FRESHTheme() { + @class FRESHTheme */ + FRESHTheme = class FRESHTheme { + constructor() { this.baseFolder = 'src'; return; } - /* Open and parse the specified theme. */ - - FRESHTheme.prototype.open = function(themeFolder) { + open(themeFolder) { var cached, formatsHash, pathInfo, that, themeFile, themeInfo; this.folder = themeFolder; + // Open the [theme-name].json file; should have the same name as folder pathInfo = parsePath(themeFolder); + // Set up a formats hash for the theme formatsHash = {}; + // Load the theme themeFile = PATH.join(themeFolder, 'theme.json'); themeInfo = loadSafeJson(themeFile); if (themeInfo.ex) { throw { - fluenterror: themeInfo.ex.op === 'parse' ? HMSTATUS.parseError : HMSTATUS.readError, - inner: themeInfo.ex.inner + fluenterror: themeInfo.ex.op === 'parse' ? HMSTATUS.parseError : HMSTATUS.readError }; + ({ + inner: themeInfo.ex.inner + }); } that = this; + // Move properties from the theme JSON file to the theme object EXTEND(true, this, themeInfo.json); + // Check for an "inherits" entry in the theme JSON. if (this.inherits) { cached = {}; _.each(this.inherits, function(th, key) { var d, themePath, themesObj; + // First, see if this is one of the predefined FRESH themes. There are + // only a handful of these, but they may change over time, so we need to + // query the official source of truth: the fresh-themes repository, which + // mounts the themes conveniently by name to the module object, and which + // is embedded locally inside the HackMyResume installation. + // TODO: merge this code with themesObj = require('fresh-themes'); if (_.has(themesObj.themes, th)) { themePath = PATH.join(parsePath(require.resolve('fresh-themes')).dirname, '/themes/', th); @@ -74,32 +83,26 @@ Definition of the FRESHTheme class. return formatsHash[key] = cached[th].getFormat(key); }); } + // Load theme files formatsHash = _load.call(this, formatsHash); + // Cache this.formats = formatsHash; + // Set the official theme name this.name = parsePath(this.folder).name; return this; - }; - + } /* Determine if the theme supports the specified output format. */ - - FRESHTheme.prototype.hasFormat = function(fmt) { + hasFormat(fmt) { return _.has(this.formats, fmt); - }; - + } /* Determine if the theme supports the specified output format. */ - - FRESHTheme.prototype.getFormat = function(fmt) { + getFormat(fmt) { return this.formats[fmt]; - }; + } - return FRESHTheme; - - })(); - - - /* Load and parse theme source files. */ + }; _load = function(formatsHash) { var copyOnly, fmts, jsFiles, major, that, tplFolder; @@ -107,12 +110,19 @@ Definition of the FRESHTheme class. major = false; tplFolder = PATH.join(this.folder, this.baseFolder); copyOnly = ['.ttf', '.otf', '.png', '.jpg', '.jpeg', '.pdf']; + // 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. fmts = READFILES(tplFolder).map(function(absPath) { return _loadOne.call(this, absPath, formatsHash, tplFolder); }, this); + // Now, get all the CSS files... this.cssFiles = fmts.filter(function(fmt) { return fmt && (fmt.ext === 'css'); }); + // For each CSS file, get its corresponding HTML file. It's possible that + // a theme can have a CSS file but *no* HTML file, as when a theme author + // creates a pure CSS override of an existing theme. this.cssFiles.forEach(function(cssf) { var idx; idx = _.findIndex(fmts, function(fmt) { @@ -124,6 +134,8 @@ Definition of the FRESHTheme class. return fmts[idx].cssPath = cssf.path; } else { if (that.inherits) { + // Found a CSS file without an HTML file in a theme that inherits + // from another theme. This is the override CSS file. return that.overrides = { file: cssf.path, data: cssf.data @@ -131,6 +143,7 @@ Definition of the FRESHTheme class. } } }); + // Now, save all the javascript file paths to a theme property. jsFiles = fmts.filter(function(fmt) { return fmt && (fmt.ext === 'js'); }); @@ -140,9 +153,6 @@ Definition of the FRESHTheme class. return formatsHash; }; - - /* Load a single theme file. */ - _loadOne = function(absPath, formatsHash, tplFolder) { var absPathSafe, act, defFormats, idx, isPrimary, obj, outFmt, pathInfo, portion, ref, ref1, reg, res; pathInfo = parsePath(absPath); @@ -153,6 +163,8 @@ Definition of the FRESHTheme class. outFmt = ''; act = 'copy'; isPrimary = false; + // If this is an "explicit" theme, all files of importance are specified in + // the "transform" section of the theme.json file. if (this.explicit) { outFmt = _.find(Object.keys(this.formats), function(fmtKey) { var fmtVal; @@ -168,6 +180,9 @@ Definition of the FRESHTheme class. } } if (!outFmt) { + // If this file lives in a specific format folder within the theme, + // such as "/latex" or "/html", then that format is the implicit output + // format for all files within the folder portion = pathInfo.dirname.replace(tplFolder, ''); if (portion && portion.trim()) { if (portion[1] === '_') { @@ -203,13 +218,16 @@ Definition of the FRESHTheme class. return form.name === outFmt && pathInfo.extname !== '.css'; }); } + // Make sure we have a valid formatsHash formatsHash[outFmt] = formatsHash[outFmt] || { outFormat: outFmt, files: [] }; + // Move symlink descriptions from theme.json to the format if ((ref = this.formats) != null ? (ref1 = ref[outFmt]) != null ? ref1.symLinks : void 0 : void 0) { formatsHash[outFmt].symLinks = this.formats[outFmt].symLinks; } + // Create the file representation object obj = { action: act, primary: isPrimary, @@ -218,16 +236,15 @@ Definition of the FRESHTheme class. ext: pathInfo.extname.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; }; - - /* Return a more friendly name for certain formats. */ - friendlyName = function(val) { var friendly; val = (val && val.trim().toLowerCase()) || ''; diff --git a/dist/core/jrs-resume.js b/dist/core/jrs-resume.js index 067cc32..862f22a 100644 --- a/dist/core/jrs-resume.js +++ b/dist/core/jrs-resume.js @@ -1,11 +1,16 @@ - -/** -Definition of the JRSResume class. -@license MIT. See LICENSE.md for details. -@module core/jrs-resume - */ - (function() { + /** + Definition of the JRSResume class. + @license MIT. See LICENSE.md for details. + @module core/jrs-resume + */ + /** + Convert human-friendly dates into formal Moment.js dates for all collections. + We don't want to lose the raw textual date as entered by the user, so we store + the Moment-ified date as a separate property with a prefix of .safe. For ex: + job.startDate is the date as entered by the user. job.safeStartDate is the + parsed Moment.js date that we actually use in processing. + */ var CONVERTER, FS, JRSResume, MD, PATH, _, _parseDates, extend, moment, validator; FS = require('fs'); @@ -24,145 +29,247 @@ Definition of the JRSResume class. moment = require('moment'); - - /** - A JRS resume or CV. JRS resumes are backed by JSON, and each JRSResume object - is an instantiation of that JSON decorated with utility methods. - @class JRSResume - */ - JRSResume = (function() { + /** Reset the sheet to an empty state. */ var clear; - function JRSResume() {} - - - /** Initialize the the JSResume from string. */ - - JRSResume.prototype.parse = function(stringData, opts) { - var ref; - this.imp = (ref = this.imp) != null ? ref : { - raw: stringData - }; - return this.parseJSON(JSON.parse(stringData), opts); - }; - - /** - Initialize the JRSResume object from JSON. - Open and parse the specified JRS resume. Merge the JSON object model onto - this Sheet instance with extend() and convert sheet dates to a safe & - consistent format. Then sort each section by startDate descending. - @param rep {Object} The raw JSON representation. - @param opts {Object} Resume loading and parsing options. - { + A JRS resume or CV. JRS resumes are backed by JSON, and each JRSResume object + is an instantiation of that JSON decorated with utility methods. + @class JRSResume + */ + class JRSResume { // extends AbstractResume + /** Initialize the the JSResume from string. */ + parse(stringData, opts) { + var ref; + this.imp = (ref = this.imp) != null ? ref : { + raw: stringData + }; + return this.parseJSON(JSON.parse(stringData), opts); + } + + /** + Initialize the JRSResume object from JSON. + Open and parse the specified JRS resume. Merge the JSON object model onto + this Sheet instance with extend() and convert sheet dates to a safe & + consistent format. Then sort each section by startDate descending. + @param rep {Object} The raw JSON representation. + @param opts {Object} Resume loading and parsing options. + { date: Perform safe date conversion. sort: Sort resume items by date. compute: Prepare computed resume totals. - } - */ - - JRSResume.prototype.parseJSON = function(rep, opts) { - var ignoreList, privateList, ref, ref1, scrubbed, scrubber; - opts = opts || {}; - if (opts.privatize) { - scrubber = require('../utils/resume-scrubber'); - ref = scrubber.scrubResume(rep, opts), scrubbed = ref.scrubbed, ignoreList = ref.ignoreList, privateList = ref.privateList; } - extend(true, this, opts.privatize ? scrubbed : rep); - if (!((ref1 = this.imp) != null ? ref1.processed : void 0)) { + */ + parseJSON(rep, opts) { + var ignoreList, privateList, ref, scrubbed, scrubber; opts = opts || {}; - if (opts.imp === void 0 || opts.imp) { - this.imp = this.imp || {}; - this.imp.title = (opts.title || this.imp.title) || this.basics.name; - if (!this.imp.raw) { - this.imp.raw = JSON.stringify(rep); - } + if (opts.privatize) { + scrubber = require('../utils/resume-scrubber'); + // Ignore any element with the 'ignore: true' or 'private: true' designator. + ({scrubbed, ignoreList, privateList} = scrubber.scrubResume(rep, opts)); } - this.imp.processed = true; + // Extend resume properties onto ourself. + extend(true, this, opts.privatize ? scrubbed : rep); + if (!((ref = this.imp) != null ? ref.processed : void 0)) { + // Set up metadata TODO: Clean up metadata on the object model. + opts = opts || {}; + if (opts.imp === void 0 || opts.imp) { + this.imp = this.imp || {}; + this.imp.title = (opts.title || this.imp.title) || this.basics.name; + if (!this.imp.raw) { + this.imp.raw = JSON.stringify(rep); + } + } + this.imp.processed = true; + } + // Parse dates, sort dates, and calculate computed values + (opts.date === void 0 || opts.date) && _parseDates.call(this); + (opts.sort === void 0 || opts.sort) && this.sort(); + if (opts.compute === void 0 || opts.compute) { + this.basics.computed = { + numYears: this.duration(), + keywords: this.keywords() + }; + } + return this; } - (opts.date === void 0 || opts.date) && _parseDates.call(this); - (opts.sort === void 0 || opts.sort) && this.sort(); - if (opts.compute === void 0 || opts.compute) { - this.basics.computed = { - numYears: this.duration(), - keywords: this.keywords() - }; - } - return this; - }; - - /** Save the sheet to disk (for environments that have disk access). */ - - JRSResume.prototype.save = function(filename) { - this.imp.file = filename || this.imp.file; - FS.writeFileSync(this.imp.file, this.stringify(this), 'utf8'); - return this; - }; - - - /** Save the sheet to disk in a specific format, either FRESH or JRS. */ - - JRSResume.prototype.saveAs = function(filename, format) { - var newRep, stringRep; - if (format === 'JRS') { + /** Save the sheet to disk (for environments that have disk access). */ + save(filename) { this.imp.file = filename || this.imp.file; - FS.writeFileSync(this.imp.file, this.stringify(), 'utf8'); - } else { - newRep = CONVERTER.toFRESH(this); - stringRep = CONVERTER.toSTRING(newRep); - FS.writeFileSync(filename, stringRep, 'utf8'); + FS.writeFileSync(this.imp.file, this.stringify(this), 'utf8'); + return this; } - return this; - }; + /** Save the sheet to disk in a specific format, either FRESH or JRS. */ + saveAs(filename, format) { + var newRep, stringRep; + if (format === 'JRS') { + this.imp.file = filename || this.imp.file; + FS.writeFileSync(this.imp.file, this.stringify(), 'utf8'); + } else { + newRep = CONVERTER.toFRESH(this); + stringRep = CONVERTER.toSTRING(newRep); + FS.writeFileSync(filename, stringRep, 'utf8'); + } + return this; + } - /** Return the resume format. */ + /** Return the resume format. */ + format() { + return 'JRS'; + } - JRSResume.prototype.format = function() { - return 'JRS'; - }; + stringify() { + return JRSResume.stringify(this); + } - JRSResume.prototype.stringify = function() { - return JRSResume.stringify(this); - }; + /** Return a unique list of all keywords across all skills. */ + keywords() { + var flatSkills; + flatSkills = []; + if (this.skills && this.skills.length) { + this.skills.forEach(function(s) { + return flatSkills = _.union(flatSkills, s.keywords); + }); + } + return flatSkills; + } + /** + Return internal metadata. Create if it doesn't exist. + JSON Resume v0.0.0 doesn't allow additional properties at the root level, + so tuck this into the .basic sub-object. + */ + i() { + var ref; + return this.imp = (ref = this.imp) != null ? ref : {}; + } - /** Return a unique list of all keywords across all skills. */ + /** Add work experience to the sheet. */ + add(moniker) { + var defSheet, newObject; + defSheet = JRSResume.default(); + newObject = $.extend(true, {}, defSheet[moniker][0]); + this[moniker] = this[moniker] || []; + this[moniker].push(newObject); + return newObject; + } - JRSResume.prototype.keywords = function() { - var flatSkills; - flatSkills = []; - if (this.skills && this.skills.length) { - this.skills.forEach(function(s) { - return flatSkills = _.union(flatSkills, s.keywords); + /** Determine if the sheet includes a specific social profile (eg, GitHub). */ + hasProfile(socialNetwork) { + socialNetwork = socialNetwork.trim().toLowerCase(); + return this.basics.profiles && _.some(this.basics.profiles, function(p) { + return p.network.trim().toLowerCase() === socialNetwork; }); } - return flatSkills; + + /** Determine if the sheet includes a specific skill. */ + hasSkill(skill) { + skill = skill.trim().toLowerCase(); + return this.skills && _.some(this.skills, function(sk) { + return sk.keywords && _.some(sk.keywords, function(kw) { + return kw.trim().toLowerCase() === skill; + }); + }); + } + + /** Validate the sheet against the JSON Resume schema. */ + isValid() { // TODO: ↓ fix this path ↓ + var ret, schema, schemaObj, temp, validate; + schema = FS.readFileSync(PATH.join(__dirname, 'resume.json'), 'utf8'); + schemaObj = JSON.parse(schema); + validator = require('is-my-json-valid'); + validate = validator(schemaObj, { // Note [1] + formats: { + date: /^\d{4}(?:-(?:0[0-9]{1}|1[0-2]{1})(?:-[0-9]{2})?)?$/ + } + }); + temp = this.imp; + delete this.imp; + ret = validate(this); + this.imp = temp; + if (!ret) { + this.imp = this.imp || {}; + this.imp.validationErrors = validate.errors; + } + return ret; + } + + duration(unit) { + var inspector; + inspector = require('../inspectors/duration-inspector'); + return inspector.run(this, 'work', 'startDate', 'endDate', unit); + } + + /** + Sort dated things on the sheet by start date descending. Assumes that dates + on the sheet have been processed with _parseDates(). + */ + sort() { + var byDateDesc; + byDateDesc = function(a, b) { + if (a.safeStartDate.isBefore(b.safeStartDate)) { + return 1; + } else { + return (a.safeStartDate.isAfter(b.safeStartDate) && -1) || 0; + } + }; + this.work && this.work.sort(byDateDesc); + this.education && this.education.sort(byDateDesc); + this.volunteer && this.volunteer.sort(byDateDesc); + this.awards && this.awards.sort(function(a, b) { + if (a.safeDate.isBefore(b.safeDate)) { + return 1; + } else { + return (a.safeDate.isAfter(b.safeDate) && -1) || 0; + } + }); + return this.publications && this.publications.sort(function(a, b) { + if (a.safeReleaseDate.isBefore(b.safeReleaseDate)) { + return 1; + } else { + return (a.safeReleaseDate.isAfter(b.safeReleaseDate) && -1) || 0; + } + }); + } + + dupe() { + var rnew; + rnew = new JRSResume(); + rnew.parse(this.stringify(), {}); + return rnew; + } + + /** + Create a copy of this resume in which all fields have been interpreted as + Markdown. + */ + harden() { + var HD, HDIN, ret, transformer; + ret = this.dupe(); + HD = function(txt) { + return '@@@@~' + txt + '~@@@@'; + }; + HDIN = function(txt) { + //return MD(txt || '' ).replace(/^\s*

|<\/p>\s*$/gi, ''); + return HD(txt); + }; + transformer = require('../utils/string-transformer'); + return transformer(ret, ['skills', 'url', 'website', 'startDate', 'endDate', 'releaseDate', 'date', 'phone', 'email', 'address', 'postalCode', 'city', 'country', 'region', 'safeStartDate', 'safeEndDate'], function(key, val) { + return HD(val); + }); + } + }; - - /** - Return internal metadata. Create if it doesn't exist. - JSON Resume v0.0.0 doesn't allow additional properties at the root level, - so tuck this into the .basic sub-object. - */ - - JRSResume.prototype.i = function() { - var ref; - return this.imp = (ref = this.imp) != null ? ref : {}; - }; - - - /** Reset the sheet to an empty state. */ - clear = function(clearMeta) { clearMeta = ((clearMeta === void 0) && true) || clearMeta; if (clearMeta) { delete this.imp; } - delete this.basics.computed; + delete this.basics.computed; // Don't use Object.keys() here delete this.work; delete this.volunteer; delete this.education; @@ -173,152 +280,22 @@ Definition of the JRSResume class. return delete this.basics.profiles; }; - - /** Add work experience to the sheet. */ - - JRSResume.prototype.add = function(moniker) { - var defSheet, newObject; - defSheet = JRSResume["default"](); - newObject = $.extend(true, {}, defSheet[moniker][0]); - this[moniker] = this[moniker] || []; - this[moniker].push(newObject); - return newObject; - }; - - - /** Determine if the sheet includes a specific social profile (eg, GitHub). */ - - JRSResume.prototype.hasProfile = function(socialNetwork) { - socialNetwork = socialNetwork.trim().toLowerCase(); - return this.basics.profiles && _.some(this.basics.profiles, function(p) { - return p.network.trim().toLowerCase() === socialNetwork; - }); - }; - - - /** Determine if the sheet includes a specific skill. */ - - JRSResume.prototype.hasSkill = function(skill) { - skill = skill.trim().toLowerCase(); - return this.skills && _.some(this.skills, function(sk) { - return sk.keywords && _.some(sk.keywords, function(kw) { - return kw.trim().toLowerCase() === skill; - }); - }); - }; - - - /** Validate the sheet against the JSON Resume schema. */ - - JRSResume.prototype.isValid = function() { - var ret, schema, schemaObj, temp, validate; - schema = FS.readFileSync(PATH.join(__dirname, 'resume.json'), 'utf8'); - schemaObj = JSON.parse(schema); - validator = require('is-my-json-valid'); - validate = validator(schemaObj, { - formats: { - date: /^\d{4}(?:-(?:0[0-9]{1}|1[0-2]{1})(?:-[0-9]{2})?)?$/ - } - }); - temp = this.imp; - delete this.imp; - ret = validate(this); - this.imp = temp; - if (!ret) { - this.imp = this.imp || {}; - this.imp.validationErrors = validate.errors; - } - return ret; - }; - - JRSResume.prototype.duration = function(unit) { - var inspector; - inspector = require('../inspectors/duration-inspector'); - return inspector.run(this, 'work', 'startDate', 'endDate', unit); - }; - - - /** - Sort dated things on the sheet by start date descending. Assumes that dates - on the sheet have been processed with _parseDates(). - */ - - JRSResume.prototype.sort = function() { - var byDateDesc; - byDateDesc = function(a, b) { - if (a.safeStartDate.isBefore(b.safeStartDate)) { - return 1; - } else { - return (a.safeStartDate.isAfter(b.safeStartDate) && -1) || 0; - } - }; - this.work && this.work.sort(byDateDesc); - this.education && this.education.sort(byDateDesc); - this.volunteer && this.volunteer.sort(byDateDesc); - this.awards && this.awards.sort(function(a, b) { - if (a.safeDate.isBefore(b.safeDate)) { - return 1; - } else { - return (a.safeDate.isAfter(b.safeDate) && -1) || 0; - } - }); - return this.publications && this.publications.sort(function(a, b) { - if (a.safeReleaseDate.isBefore(b.safeReleaseDate)) { - return 1; - } else { - return (a.safeReleaseDate.isAfter(b.safeReleaseDate) && -1) || 0; - } - }); - }; - - JRSResume.prototype.dupe = function() { - var rnew; - rnew = new JRSResume(); - rnew.parse(this.stringify(), {}); - return rnew; - }; - - - /** - Create a copy of this resume in which all fields have been interpreted as - Markdown. - */ - - JRSResume.prototype.harden = function() { - var HD, HDIN, ret, transformer; - ret = this.dupe(); - HD = function(txt) { - return '@@@@~' + txt + '~@@@@'; - }; - HDIN = function(txt) { - return HD(txt); - }; - transformer = require('../utils/string-transformer'); - return transformer(ret, ['skills', 'url', 'website', 'startDate', 'endDate', 'releaseDate', 'date', 'phone', 'email', 'address', 'postalCode', 'city', 'country', 'region', 'safeStartDate', 'safeEndDate'], function(key, val) { - return HD(val); - }); - }; - return JRSResume; - })(); - + }).call(this); /** Get the default (empty) sheet. */ - - JRSResume["default"] = function() { + JRSResume.default = function() { return new JRSResume().parseJSON(require('fresh-resume-starter').jrs); }; - /** Convert this object to a JSON string, sanitizing meta-properties along the way. Don't override .toString(). - */ - + */ JRSResume.stringify = function(obj) { var replacer; - replacer = function(key, value) { + replacer = function(key, value) { // Exclude these keys from stringification var temp; temp = _.some(['imp', 'warnings', 'computed', 'filt', 'ctrl', 'index', 'safeStartDate', 'safeEndDate', 'safeDate', 'safeReleaseDate', 'result', 'isModified', 'htmlPreview', 'display_progress_bar'], function(val) { return key.trim() === val; @@ -332,15 +309,6 @@ Definition of the JRSResume class. return JSON.stringify(obj, replacer, 2); }; - - /** - Convert human-friendly dates into formal Moment.js dates for all collections. - We don't want to lose the raw textual date as entered by the user, so we store - the Moment-ified date as a separate property with a prefix of .safe. For ex: - job.startDate is the date as entered by the user. job.safeStartDate is the - parsed Moment.js date that we actually use in processing. - */ - _parseDates = function() { var _fmt; _fmt = require('./fluent-date').fmt; @@ -364,11 +332,9 @@ Definition of the JRSResume class. }); }; - /** Export the JRSResume function/ctor. - */ - + */ module.exports = JRSResume; }).call(this); diff --git a/dist/core/jrs-theme.js b/dist/core/jrs-theme.js index 79ff58a..2f9f353 100644 --- a/dist/core/jrs-theme.js +++ b/dist/core/jrs-theme.js @@ -1,11 +1,9 @@ - -/** -Definition of the JRSTheme class. -@module core/jrs-theme -@license MIT. See LICENSE.MD for details. - */ - (function() { + /** + Definition of the JRSTheme class. + @module core/jrs-theme + @license MIT. See LICENSE.MD for details. + */ var JRSTheme, PATH, _, errors, parsePath, pathExists; _ = require('underscore'); @@ -18,32 +16,30 @@ Definition of the JRSTheme class. errors = require('./status-codes'); - /** The JRSTheme class is a representation of a JSON Resume theme asset. @class JRSTheme - */ - - JRSTheme = (function() { - function JRSTheme() {} - - + */ + JRSTheme = class JRSTheme { /** Open and parse the specified JRS theme. @method open - */ - - JRSTheme.prototype.open = function(thFolder) { + */ + open(thFolder) { var pathInfo, pkgJsonPath, thApi, thPkg; this.folder = thFolder; pathInfo = parsePath(thFolder); + // Open and parse the theme's package.json file pkgJsonPath = PATH.join(thFolder, 'package.json'); if (pathExists(pkgJsonPath)) { - thApi = require(thFolder); - thPkg = require(pkgJsonPath); + thApi = require(thFolder); // Requiring the folder yields whatever the package.json's "main" is set to + thPkg = require(pkgJsonPath); // Get the package.json as JSON this.name = thPkg.name; this.render = (thApi && thApi.render) || void 0; this.engine = 'jrs'; + // Create theme formats (HTML and PDF). Just add the bare minimum mix of + // properties necessary to allow JSON Resume themes to share a rendering + // path with FRESH themes. this.formats = { html: { outFormat: 'html', @@ -76,31 +72,25 @@ Definition of the JRSTheme class. }; } return this; - }; - + } /** Determine if the theme supports the output format. @method hasFormat - */ - - JRSTheme.prototype.hasFormat = function(fmt) { + */ + hasFormat(fmt) { return _.has(this.formats, fmt); - }; - + } /** Return the requested output format. @method getFormat - */ - - JRSTheme.prototype.getFormat = function(fmt) { + */ + getFormat(fmt) { return this.formats[fmt]; - }; + } - return JRSTheme; - - })(); + }; module.exports = JRSTheme; diff --git a/dist/core/resume-factory.js b/dist/core/resume-factory.js index da5fd3e..69a910a 100644 --- a/dist/core/resume-factory.js +++ b/dist/core/resume-factory.js @@ -1,11 +1,13 @@ - -/** -Definition of the ResumeFactory class. -@license MIT. See LICENSE.md for details. -@module core/resume-factory - */ - (function() { + /** + Definition of the ResumeFactory class. + @license MIT. See LICENSE.md for details. + @module core/resume-factory + */ + /** + A simple factory class for FRESH and JSON Resumes. + @class ResumeFactory + */ var FS, HME, HMS, ResumeConverter, ResumeFactory, SyntaxErrorEx, _, _parse, chalk, resumeDetect; FS = require('fs'); @@ -26,20 +28,13 @@ Definition of the ResumeFactory class. require('string.prototype.startswith'); - - /** - A simple factory class for FRESH and JSON Resumes. - @class ResumeFactory - */ - ResumeFactory = module.exports = { - /** Load one or more resumes from disk. - + @param {Object} opts An options object with settings for the factory as well as passthrough settings for FRESHResume or JRSResume. Structure: - + { format: 'FRESH', // Format to open as. ('FRESH', 'JRS', null) objectify: true, // FRESH/JRSResume or raw JSON? @@ -47,31 +42,38 @@ Definition of the ResumeFactory class. sort: false } } - */ + + */ load: function(sources, opts, emitter) { return sources.map(function(src) { return this.loadOne(src, opts, emitter); }, this); }, - - /** Load a single resume from disk. */ + /** Load a single resume from disk. */ loadOne: function(src, opts, emitter) { var ResumeClass, info, json, orgFormat, reqLib, rez, toFormat; - toFormat = opts.format; + toFormat = opts.format; // Can be null + + // Get the destination format. Can be 'fresh', 'jrs', or null/undefined. toFormat && (toFormat = toFormat.toLowerCase().trim()); + // Load and parse the resume JSON info = _parse(src, opts, emitter); if (info.fluenterror) { return info; } + // Determine the resume format: FRESH or JRS json = info.json; orgFormat = resumeDetect(json); if (orgFormat === 'unk') { info.fluenterror = HMS.unknownSchema; return info; } + // Convert between formats if necessary if (toFormat && (orgFormat !== toFormat)) { json = ResumeConverter['to' + toFormat.toUpperCase()](json); } + // Objectify the resume, that is, convert it from JSON to a FRESHResume + // or JRSResume object. rez = null; if (opts.objectify) { reqLib = '../core/' + (toFormat || orgFormat) + '-resume'; @@ -88,9 +90,10 @@ Definition of the ResumeFactory class. }; _parse = function(fileName, opts, eve) { - var orgFormat, rawData, ret; + var err, orgFormat, rawData, ret; rawData = null; try { + // Read the file eve && eve.stat(HME.beforeRead, { file: fileName }); @@ -112,10 +115,12 @@ Definition of the ResumeFactory class. fmt: orgFormat }); return ret; - } catch (_error) { + } catch (error) { + err = error; return { + // Can be ENOENT, EACCES, SyntaxError, etc. fluenterror: rawData ? HMS.parseError : HMS.readError, - inner: _error, + inner: err, raw: rawData, file: fileName }; diff --git a/dist/core/status-codes.js b/dist/core/status-codes.js index 77e8c36..99215fd 100644 --- a/dist/core/status-codes.js +++ b/dist/core/status-codes.js @@ -1,11 +1,9 @@ - -/** -Status codes for HackMyResume. -@module core/status-codes -@license MIT. See LICENSE.MD for details. - */ - (function() { + /** + Status codes for HackMyResume. + @module core/status-codes + @license MIT. See LICENSE.MD for details. + */ module.exports = { success: 0, themeNotFound: 1, diff --git a/dist/generators/base-generator.js b/dist/generators/base-generator.js index b7b1889..87c9645 100644 --- a/dist/generators/base-generator.js +++ b/dist/generators/base-generator.js @@ -1,39 +1,33 @@ - -/** -Definition of the BaseGenerator class. -@module generators/base-generator -@license MIT. See LICENSE.md for details. - */ - - -/** -The BaseGenerator class is the root of the generator hierarchy. Functionality -common to ALL generators lives here. - */ - (function() { + /** + Definition of the BaseGenerator class. + @module generators/base-generator + @license MIT. See LICENSE.md for details. + */ var BaseGenerator; + /** + The BaseGenerator class is the root of the generator hierarchy. Functionality + common to ALL generators lives here. + */ module.exports = BaseGenerator = (function() { + class BaseGenerator { + /** Base-class initialize. */ + constructor(format) { + this.format = format; + } - /** Base-class initialize. */ - function BaseGenerator(format) { - this.format = format; - } - + }; /** Status codes. */ - BaseGenerator.prototype.codes = require('../core/status-codes'); - /** Generator options. */ - BaseGenerator.prototype.opts = {}; return BaseGenerator; - })(); + }).call(this); }).call(this); diff --git a/dist/generators/html-generator.js b/dist/generators/html-generator.js index 1dd226b..b944481 100644 --- a/dist/generators/html-generator.js +++ b/dist/generators/html-generator.js @@ -1,14 +1,10 @@ - -/** -Definition of the HTMLGenerator class. -@module generators/html-generator -@license MIT. See LICENSE.md for details. - */ - (function() { - var FS, HTML, HtmlGenerator, PATH, TemplateGenerator, - extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, - hasProp = {}.hasOwnProperty; + /** + Definition of the HTMLGenerator class. + @module generators/html-generator + @license MIT. See LICENSE.md for details. + */ + var FS, HTML, HtmlGenerator, PATH, TemplateGenerator; TemplateGenerator = require('./template-generator'); @@ -20,20 +16,16 @@ Definition of the HTMLGenerator class. require('string.prototype.endswith'); - module.exports = HtmlGenerator = (function(superClass) { - extend(HtmlGenerator, superClass); - - function HtmlGenerator() { - HtmlGenerator.__super__.constructor.call(this, 'html'); + module.exports = HtmlGenerator = class HtmlGenerator extends TemplateGenerator { + constructor() { + super('html'); } - /** Copy satellite CSS files to the destination and optionally pretty-print the HTML resume prior to saving. - */ - - HtmlGenerator.prototype.onBeforeSave = function(info) { + */ + onBeforeSave(info) { if (info.outputFile.endsWith('.css')) { return info.mk; } @@ -42,11 +34,9 @@ Definition of the HTMLGenerator class. } else { return info.mk; } - }; + } - return HtmlGenerator; - - })(TemplateGenerator); + }; }).call(this); diff --git a/dist/generators/html-pdf-cli-generator.js b/dist/generators/html-pdf-cli-generator.js index 54cf3b5..fec470d 100644 --- a/dist/generators/html-pdf-cli-generator.js +++ b/dist/generators/html-pdf-cli-generator.js @@ -1,14 +1,10 @@ - -/** -Definition of the HtmlPdfCLIGenerator class. -@module generators/html-pdf-generator.js -@license MIT. See LICENSE.md for details. - */ - (function() { - var FS, HMSTATUS, HtmlPdfCLIGenerator, PATH, SLASH, SPAWN, TemplateGenerator, _, engines, - extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, - hasProp = {}.hasOwnProperty; + /** + Definition of the HtmlPdfCLIGenerator class. + @module generators/html-pdf-generator.js + @license MIT. See LICENSE.md for details. + */ + var FS, HMSTATUS, HtmlPdfCLIGenerator, PATH, SLASH, SPAWN, TemplateGenerator, _, engines; TemplateGenerator = require('./template-generator'); @@ -24,26 +20,21 @@ Definition of the HtmlPdfCLIGenerator class. SPAWN = require('../utils/safe-spawn'); - /** An HTML-driven PDF resume generator for HackMyResume. Talks to Phantom, wkhtmltopdf, and other PDF engines over a CLI (command-line interface). If an engine isn't installed for a particular platform, error out gracefully. - */ - - module.exports = HtmlPdfCLIGenerator = (function(superClass) { - extend(HtmlPdfCLIGenerator, superClass); - - function HtmlPdfCLIGenerator() { - HtmlPdfCLIGenerator.__super__.constructor.call(this, 'pdf', 'html'); + */ + module.exports = HtmlPdfCLIGenerator = class HtmlPdfCLIGenerator extends TemplateGenerator { + constructor() { + super('pdf', 'html'); } - /** Generate the binary PDF. */ - - HtmlPdfCLIGenerator.prototype.onBeforeSave = function(info) { + onBeforeSave(info) { var safe_eng; if (info.ext !== 'html' && info.ext !== 'pdf') { + //console.dir _.omit( info, 'mk' ), depth: null, colors: true return info.mk; } safe_eng = info.opts.pdf || 'wkhtmltopdf'; @@ -53,43 +44,40 @@ Definition of the HtmlPdfCLIGenerator class. if (_.has(engines, safe_eng)) { this.errHandler = info.opts.errHandler; engines[safe_eng].call(this, info.mk, info.outputFile, info.opts, this.onError); - return null; + return null; // halt further processing } - }; - + } /* Low-level error callback for spawn(). May be called after HMR process termination, so object references may not be valid here. That's okay; if the references are invalid, the error was already logged. We could use - spawn-watch here but that causes issues on legacy Node.js. - */ - - HtmlPdfCLIGenerator.prototype.onError = function(ex, param) { + spawn-watch here but that causes issues on legacy Node.js. */ + onError(ex, param) { var ref; if ((ref = param.errHandler) != null) { if (typeof ref.err === "function") { ref.err(HMSTATUS.pdfGeneration, ex); } } - }; + } - return HtmlPdfCLIGenerator; - - })(TemplateGenerator); + }; + // TODO: Move each engine to a separate module engines = { - /** Generate a PDF from HTML using wkhtmltopdf's CLI interface. Spawns a child process with `wkhtmltopdf `. wkhtmltopdf must be installed and path-accessible. TODO: If HTML generation has run, reuse that output TODO: Local web server to ease wkhtmltopdf rendering - */ + */ wkhtmltopdf: function(markup, fOut, opts, on_error) { var tempFile, wkargs, wkopts; + // Save the markup to a temporary file tempFile = fOut.replace(/\.pdf$/i, '.pdf.html'); FS.writeFileSync(tempFile, markup, 'utf8'); + // Prepare wkhtmltopdf arguments. wkopts = _.extend({ 'margin-top': '10mm', 'margin-bottom': '10mm' @@ -100,16 +88,16 @@ Definition of the HtmlPdfCLIGenerator class. wkargs = wkopts.concat([tempFile, fOut]); SPAWN('wkhtmltopdf', wkargs, false, on_error, this); }, - /** Generate a PDF from HTML using Phantom's CLI interface. Spawns a child process with `phantomjs