diff --git a/dist/core/abstract-resume.js b/dist/core/abstract-resume.js new file mode 100644 index 0000000..4955970 --- /dev/null +++ b/dist/core/abstract-resume.js @@ -0,0 +1,71 @@ + +/** +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 = 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); + }; + + return AbstractResume; + + })(); + + module.exports = AbstractResume; + +}).call(this); diff --git a/dist/core/fluent-date.js b/dist/core/fluent-date.js index 3b798f9..10ff22f 100644 --- a/dist/core/fluent-date.js +++ b/dist/core/fluent-date.js @@ -10,6 +10,8 @@ The HackMyResume date representation. moment = require('moment'); + require('../utils/string'); + /** Create a FluentDate from a string or Moment date object. There are a few date @@ -33,6 +35,10 @@ The HackMyResume date representation. this.rep = this.fmt(dt); } + FluentDate.isCurrent = function(dt) { + return !dt || (String.is(dt) && /^(present|now|current)$/.test(dt)); + }; + return FluentDate; })(); diff --git a/dist/core/fresh-resume.js b/dist/core/fresh-resume.js index f9b8887..6fd9932 100644 --- a/dist/core/fresh-resume.js +++ b/dist/core/fresh-resume.js @@ -6,7 +6,9 @@ Definition of the FRESHResume class. */ (function() { - var CONVERTER, FS, FreshResume, JRSResume, MD, PATH, XML, _, __, _parseDates, extend, moment, validator; + 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; FS = require('fs'); @@ -30,6 +32,10 @@ Definition of the FRESHResume class. JRSResume = require('./jrs-resume'); + FluentDate = require('./fluent-date'); + + AbstractResume = require('./abstract-resume'); + /** A FRESH resume or CV. FRESH resumes are backed by JSON, and each FreshResume @@ -37,8 +43,12 @@ Definition of the FRESHResume class. @constructor */ - FreshResume = (function() { - function FreshResume() {} + FreshResume = (function(superClass) { + extend1(FreshResume, superClass); + + function FreshResume() { + return FreshResume.__super__.constructor.apply(this, arguments); + } /** Initialize the FreshResume from file. */ @@ -376,36 +386,8 @@ Definition of the FRESHResume class. return ret; }; - - /** - Calculate the total duration of the sheet. Assumes this.work has been sorted - by start date descending, perhaps via a call to Sheet.sort(). - @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. - */ - FreshResume.prototype.duration = function(unit) { - var careerLast, careerStart, empHist, firstJob; - unit = unit || 'years'; - empHist = __.get(this, 'employment.history'); - if (empHist && empHist.length) { - firstJob = _.last(empHist); - careerStart = firstJob.start ? firstJob.safe.start : ''; - if ((typeof careerStart === 'string' || careerStart instanceof String) && !careerStart.trim()) { - return 0; - } - careerLast = _.max(empHist, function(w) { - if (w.safe && w.safe.end) { - return w.safe.end.unix(); - } else { - return moment().unix(); - } - }); - return careerLast.safe.end.diff(careerStart, unit); - } - return 0; + return FreshResume.__super__.duration.call(this, 'employment.history', 'start', 'end', unit); }; @@ -420,7 +402,11 @@ Definition of the FRESHResume class. if (a.safe.start.isBefore(b.safe.start)) { return 1; } else { - return (a.safe.start.isAfter(b.safe.start) && -1) || 0; + if (a.safe.start.isAfter(b.safe.start)) { + return -1; + } else { + return 0; + } } }; sortSection = function(key) { @@ -448,7 +434,7 @@ Definition of the FRESHResume class. return FreshResume; - })(); + })(AbstractResume); /** @@ -499,7 +485,7 @@ Definition of the FRESHResume class. return; } if (Object.prototype.toString.call(obj) === '[object Array]') { - return obj.forEach(function(elem) { + obj.forEach(function(elem) { return replaceDatesInObject(elem); }); } else if (typeof obj === 'object') { @@ -509,19 +495,19 @@ Definition of the FRESHResume class. Object.keys(obj).forEach(function(key) { return replaceDatesInObject(obj[key]); }); - return ['start', 'end', 'date'].forEach(function(val) { + ['start', 'end', 'date'].forEach(function(val) { if ((obj[val] !== void 0) && (!obj.safe || !obj.safe[val])) { obj.safe = obj.safe || {}; obj.safe[val] = _fmt(obj[val]); if (obj[val] && (val === 'start') && !obj.end) { - return obj.safe.end = _fmt('current'); + obj.safe.end = _fmt('current'); } } }); } }; - return Object.keys(this).forEach(function(member) { - return replaceDatesInObject(that[member]); + Object.keys(this).forEach(function(member) { + replaceDatesInObject(that[member]); }); }; diff --git a/dist/core/jrs-resume.js b/dist/core/jrs-resume.js index d2a1dfc..0a5b8b5 100644 --- a/dist/core/jrs-resume.js +++ b/dist/core/jrs-resume.js @@ -6,7 +6,9 @@ Definition of the JRSResume class. */ (function() { - var CONVERTER, FS, JRSResume, MD, PATH, _, _parseDates, extend, moment, validator; + 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; FS = require('fs'); @@ -24,6 +26,8 @@ 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 @@ -31,10 +35,14 @@ Definition of the JRSResume class. @class JRSResume */ - JRSResume = (function() { + JRSResume = (function(superClass) { var clear, format; - function JRSResume() {} + extend1(JRSResume, superClass); + + function JRSResume() { + return JRSResume.__super__.constructor.apply(this, arguments); + } /** Initialize the JSResume from file. */ @@ -249,30 +257,8 @@ Definition of the JRSResume class. return ret; }; - - /** - Calculate the total duration of the sheet. Assumes this.work has been sorted - by start date descending, perhaps via a call to Sheet.sort(). - @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. - */ - JRSResume.prototype.duration = function(unit) { - var careerLast, careerStart; - unit = unit || 'years'; - if (this.work && this.work.length) { - careerStart = this.work[this.work.length - 1].safeStartDate; - if ((typeof careerStart === 'string' || careerStart instanceof String) && !careerStart.trim()) { - return 0; - } - careerLast = _.max(this.work, function(w) { - return w.safeEndDate.unix(); - }).safeEndDate; - return careerLast.diff(careerStart, unit); - } - return 0; + return JRSResume.__super__.duration.call(this, 'work', 'startDate', 'endDate', unit); }; @@ -372,7 +358,7 @@ Definition of the JRSResume class. return JRSResume; - })(); + })(AbstractResume); /** Get the default (empty) sheet. */ diff --git a/src/core/abstract-resume.coffee b/src/core/abstract-resume.coffee new file mode 100644 index 0000000..81e2588 --- /dev/null +++ b/src/core/abstract-resume.coffee @@ -0,0 +1,56 @@ +###* +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 = hist || __.get(this, 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] ) + return 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 + +module.exports = AbstractResume diff --git a/src/core/fluent-date.coffee b/src/core/fluent-date.coffee index 94654fe..f08d156 100644 --- a/src/core/fluent-date.coffee +++ b/src/core/fluent-date.coffee @@ -7,6 +7,7 @@ The HackMyResume date representation. moment = require 'moment' +require('../utils/string') ###* Create a FluentDate from a string or Moment date object. There are a few date @@ -30,6 +31,8 @@ class FluentDate constructor: (dt) -> @rep = this.fmt dt + @isCurrent: (dt) -> + !dt || (String.is(dt) and /^(present|now|current)$/.test(dt)) months = {} abbr = {} diff --git a/src/core/fresh-resume.coffee b/src/core/fresh-resume.coffee index 730c3d5..b378ea6 100644 --- a/src/core/fresh-resume.coffee +++ b/src/core/fresh-resume.coffee @@ -17,6 +17,8 @@ XML = require 'xml-escape' MD = require 'marked' CONVERTER = require 'fresh-jrs-converter' JRSResume = require './jrs-resume' +FluentDate = require './fluent-date' +AbstractResume = require './abstract-resume' @@ -25,7 +27,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 +class FreshResume extends AbstractResume ###* Initialize the FreshResume from file. ### open: ( file, opts ) -> @@ -306,28 +308,8 @@ class FreshResume ret - - ###* - Calculate the total duration of the sheet. Assumes this.work has been sorted - by start date descending, perhaps via a call to Sheet.sort(). - @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: (unit) -> - unit = unit || 'years' - empHist = __.get(this, 'employment.history') - if empHist && empHist.length - firstJob = _.last( empHist ) - careerStart = if firstJob.start then firstJob.safe.start else '' - if ((typeof careerStart == 'string' || careerStart instanceof String) && !careerStart.trim()) - return 0 - careerLast = _.max empHist, ( w ) -> - return if w.safe && w.safe.end then w.safe.end.unix() else moment().unix() - return careerLast.safe.end.diff careerStart, unit - 0 - + super('employment.history', 'start', 'end', unit) ###* @@ -337,9 +319,9 @@ class FreshResume sort: () -> byDateDesc = (a,b) -> - if ( a.safe.start.isBefore(b.safe.start) ) + if a.safe.start.isBefore(b.safe.start) then 1 - else ( a.safe.start.isAfter(b.safe.start) && -1 ) || 0 + else ( if a.safe.start.isAfter(b.safe.start) then -1 else 0 ) sortSection = ( key ) -> ar = __.get this, key @@ -352,10 +334,6 @@ class FreshResume sortSection 'service.history' sortSection 'projects' - # this.awards && this.awards.sort( function(a, b) { - # return( a.safeDate.isBefore(b.safeDate) ) ? 1 - # : ( a.safeDate.isAfter(b.safeDate) && -1 ) || 0; - # }); @writing && @writing.sort (a, b) -> if a.safe.date.isBefore b.safe.date then 1 @@ -400,6 +378,7 @@ _parseDates = () -> return if !obj if Object.prototype.toString.call( obj ) == '[object Array]' obj.forEach (elem) -> replaceDatesInObject( elem ) + return else if typeof obj == 'object' if obj._isAMomentObject || obj.safe return @@ -410,8 +389,12 @@ _parseDates = () -> obj.safe[ val ] = _fmt obj[val] if obj[val] && (val == 'start') && !obj.end obj.safe.end = _fmt 'current' - - Object.keys( this ).forEach (member) -> replaceDatesInObject(that[member]) + return + return + Object.keys( this ).forEach (member) -> + replaceDatesInObject(that[member]) + return + return diff --git a/src/core/jrs-resume.coffee b/src/core/jrs-resume.coffee index ba588c0..dfd5c08 100644 --- a/src/core/jrs-resume.coffee +++ b/src/core/jrs-resume.coffee @@ -14,7 +14,7 @@ PATH = require('path') MD = require('marked') CONVERTER = require('fresh-jrs-converter') moment = require('moment') - +AbstractResume = require('./abstract-resume') ###* @@ -22,7 +22,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 +class JRSResume extends AbstractResume @@ -203,25 +203,8 @@ class JRSResume ret - - ###* - Calculate the total duration of the sheet. Assumes this.work has been sorted - by start date descending, perhaps via a call to Sheet.sort(). - @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: ( unit ) -> - unit = unit || 'years'; - if this.work && this.work.length - careerStart = this.work[ this.work.length - 1].safeStartDate - if (typeof careerStart == 'string' || careerStart instanceof String) && !careerStart.trim() - return 0 - careerLast = _.max( this.work, ( w ) -> w.safeEndDate.unix() ).safeEndDate; - return careerLast.diff careerStart, unit - 0 - + duration: (unit) -> + super('work', 'startDate', 'endDate', unit) ###*