mirror of
				https://github.com/JuanCanham/HackMyResume.git
				synced 2025-10-31 13:17:26 +00:00 
			
		
		
		
	Compare commits
	
		
			14 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | bd278268f6 | ||
|  | abe31e30e0 | ||
|  | 314d8d8763 | ||
|  | ed0792e8f8 | ||
|  | 90765bf90b | ||
|  | f1ba7765ee | ||
|  | 27c7a0264a | ||
|  | 8e806dc04f | ||
|  | 8ec6b5ed6a | ||
|  | 4ef4ec5d42 | ||
|  | 2f523b845b | ||
|  | 1c416f39d3 | ||
|  | 1de0eff7b3 | ||
|  | f8a39b0908 | 
							
								
								
									
										58
									
								
								BUILDING.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								BUILDING.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,58 @@ | ||||
| Building | ||||
| ======== | ||||
|  | ||||
| *See [CONTRIBUTING.md][contrib] for more information on contributing to the | ||||
| HackMyResume or FluentCV projects.* | ||||
|  | ||||
| HackMyResume is a standard Node.js command line app implemented in a mix of | ||||
| CoffeeScript and JavaScript. Setting up a build environment is easy: | ||||
|  | ||||
|  | ||||
| ## Prerequisites ## | ||||
|  | ||||
| 1. OS: Linux, OS X, or Windows | ||||
|  | ||||
| 2. Install [Node.js][node] and [Grunt][grunt]. | ||||
|  | ||||
|  | ||||
| ## Set up a build environment ### | ||||
|  | ||||
| 1. Fork [hacksalot/HackMyResume][hmr] to your GitHub account. | ||||
|  | ||||
| 2. Clone your fork locally. | ||||
|  | ||||
| 3. From within the top-level HackMyResume folder, run `npm install` to install | ||||
| project dependencies. | ||||
|  | ||||
| 4. Create a new branch, based on the latest HackMyResume `dev` branch, to | ||||
| contain your work. | ||||
|  | ||||
| 5. Run `npm link` in the HackMyResume folder so that the `hackmyresume` command | ||||
| will reference your local installation (you may need to | ||||
| `npm uninstall -g hackmyresume` first). | ||||
|  | ||||
| ## Making changes | ||||
|  | ||||
| 1. HackMyResume sources live in the [`/src`][src] folder. Always make your edits | ||||
| there, never in the generated `/dist` folder. | ||||
|  | ||||
| 2. After making your changes, run `grunt build` to package the HackMyResume | ||||
| sources to the `/dist` folder. This will transform CoffeeScript files to | ||||
| JavaScript and perform other build steps as necessary. In the future, a watch | ||||
| task or guardfile will be added to automate this step. | ||||
|  | ||||
| 3. Do local spot testing with `hackmyresume` as normal. | ||||
|  | ||||
| 4. When you're ready to submit your changes, run `grunt test` to run the HMR | ||||
| test suite. Fix any errors that occur. | ||||
|  | ||||
| 5. Commit and push your changes. | ||||
|  | ||||
| 6. Submit a pull request targeting the HackMyResume `dev` branch. | ||||
|  | ||||
|  | ||||
| [node]: https://nodejs.org/en/ | ||||
| [grunt]: http://gruntjs.com/ | ||||
| [hmr]: https://github.com/hacksalot/HackMyResume | ||||
| [src]: https://github.com/hacksalot/HackMyResume/tree/master/src | ||||
| [contrib]: https://github.com/hacksalot/HackMyResume/blob/master/CONTRIBUTING.md | ||||
| @@ -4,17 +4,11 @@ Contributing | ||||
| *Note: HackMyResume is also available as [FluentCV][fcv]. Contributors are | ||||
| credited in both.* | ||||
|  | ||||
| HackMyResume needs your help! Our contribution workflow is based on [GitHub | ||||
| Flow][flow] and we respond to all pull requests and issues, usually within 24 | ||||
| hours. HackMyResume has no corporate affiliation and no commercial basis, which | ||||
| allows the project to maintain a strict user-first policy, rapid development | ||||
| velocity, and a liberal stance on contributions and exotic functionality in | ||||
| keeping with the spirit (and name) of the tool. | ||||
|  | ||||
| In short, your code is welcome here. | ||||
|  | ||||
| ## How To Contribute | ||||
|  | ||||
| *See [BUILDING.md][building] for instructions on setting up a HackMyResume | ||||
| development environment.* | ||||
|  | ||||
| 1. Optional: [**open an issue**][iss] identifying the feature or bug you'd like | ||||
| to implement or fix. This step isn't required — you can start hacking away on | ||||
| HackMyResume without clearing it with us — but helps avoid duplication of work | ||||
| @@ -25,7 +19,7 @@ similar; call it whatever you like) to perform your work in. | ||||
| 4. **Install dependencies** by running `npm install` in the top-level | ||||
| HackMyResume folder. | ||||
| 5. Make your **commits** as usual. | ||||
| 6. **Verify** your changes locally with `npm test`. | ||||
| 6. **Verify** your changes locally with `grunt test`. | ||||
| 7. **Push** your commits. | ||||
| 7. **Submit a pull request** from your feature branch to the HackMyResume `dev` | ||||
| branch. | ||||
| @@ -48,7 +42,7 @@ You can reach hacksalot directly at: | ||||
| hacksalot@indevious.com | ||||
| ``` | ||||
|  | ||||
| Thanks! See you out there in the trenches. | ||||
| Thanks for your interest in the HackMyResume project. | ||||
|  | ||||
| [fcv]: https://github.com/fluentdesk/fluentcv | ||||
| [flow]: https://guides.github.com/introduction/flow/ | ||||
| @@ -56,3 +50,4 @@ Thanks! See you out there in the trenches. | ||||
| [ha]: https://github.com/hacksalot | ||||
| [th]: https://github.com/tomheon | ||||
| [awesome]: https://github.com/hacksalot/HackMyResume/graphs/contributors | ||||
| [building]: https://github.com/hacksalot/HackMyResume/blob/master/BUILDING.md | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| The MIT License | ||||
| =============== | ||||
|  | ||||
| Copyright (c) 2016 hacksalot (https://github.com/hacksalot) | ||||
| Copyright (c) 2015-2016 hacksalot (https://github.com/hacksalot) | ||||
|  | ||||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| of this software and associated documentation files (the "Software"), to deal | ||||
|   | ||||
							
								
								
									
										71
									
								
								dist/core/abstract-resume.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								dist/core/abstract-resume.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -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 = __.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); | ||||
							
								
								
									
										36
									
								
								dist/core/fluent-date.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										36
									
								
								dist/core/fluent-date.js
									
									
									
									
										vendored
									
									
								
							| @@ -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; | ||||
|  | ||||
|   })(); | ||||
| @@ -54,7 +60,7 @@ The HackMyResume date representation. | ||||
|   module.exports = FluentDate; | ||||
|  | ||||
|   FluentDate.fmt = function(dt, throws) { | ||||
|     var defTime, month, mt, parts, ref, temp; | ||||
|     var month, mt, parts, ref, temp; | ||||
|     throws = (throws === void 0 || throws === null) || throws; | ||||
|     if (typeof dt === 'string' || dt instanceof String) { | ||||
|       dt = dt.toLowerCase().trim(); | ||||
| @@ -72,33 +78,7 @@ The HackMyResume date representation. | ||||
|       } else if (/^\s*\d{4}\s*$/.test(dt)) { | ||||
|         return moment(dt, 'YYYY'); | ||||
|       } else if (/^\s*$/.test(dt)) { | ||||
|         defTime = { | ||||
|           isNull: true, | ||||
|           isBefore: function(other) { | ||||
|             if (other && !other.isNull) { | ||||
|               return true; | ||||
|             } else { | ||||
|               return false; | ||||
|             } | ||||
|           }, | ||||
|           isAfter: function(other) { | ||||
|             if (other && !other.isNull) { | ||||
|               return false; | ||||
|             } else { | ||||
|               return false; | ||||
|             } | ||||
|           }, | ||||
|           unix: function() { | ||||
|             return 0; | ||||
|           }, | ||||
|           format: function() { | ||||
|             return ''; | ||||
|           }, | ||||
|           diff: function() { | ||||
|             return 0; | ||||
|           } | ||||
|         }; | ||||
|         return defTime; | ||||
|         return moment(); | ||||
|       } else { | ||||
|         mt = moment(dt); | ||||
|         if (mt.isValid()) { | ||||
|   | ||||
							
								
								
									
										64
									
								
								dist/core/fresh-resume.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										64
									
								
								dist/core/fresh-resume.js
									
									
									
									
										vendored
									
									
								
							| @@ -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]); | ||||
|     }); | ||||
|   }; | ||||
|  | ||||
|   | ||||
							
								
								
									
										40
									
								
								dist/core/jrs-resume.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										40
									
								
								dist/core/jrs-resume.js
									
									
									
									
										vendored
									
									
								
							| @@ -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. */ | ||||
|   | ||||
							
								
								
									
										30
									
								
								dist/core/jrs-theme.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										30
									
								
								dist/core/jrs-theme.js
									
									
									
									
										vendored
									
									
								
							| @@ -6,7 +6,7 @@ Definition of the JRSTheme class. | ||||
|  */ | ||||
|  | ||||
| (function() { | ||||
|   var JRSTheme, PATH, _, getFormat, parsePath, pathExists; | ||||
|   var JRSTheme, PATH, _, parsePath, pathExists; | ||||
|  | ||||
|   _ = require('underscore'); | ||||
|  | ||||
| @@ -25,17 +25,13 @@ Definition of the JRSTheme class. | ||||
|   JRSTheme = (function() { | ||||
|     function JRSTheme() {} | ||||
|  | ||||
|     return JRSTheme; | ||||
|  | ||||
|   })(); | ||||
|  | ||||
|   ({ | ||||
|  | ||||
|     /** | ||||
|     Open and parse the specified theme. | ||||
|     @method open | ||||
|      */ | ||||
|     open: function(thFolder) { | ||||
|  | ||||
|     JRSTheme.prototype.open = function(thFolder) { | ||||
|       var pathInfo, pkgJsonPath, thApi, thPkg; | ||||
|       this.folder = thFolder; | ||||
|       pathInfo = parsePath(thFolder); | ||||
| @@ -78,25 +74,31 @@ Definition of the JRSTheme class. | ||||
|         }; | ||||
|       } | ||||
|       return this; | ||||
|     }, | ||||
|     }; | ||||
|  | ||||
|  | ||||
|     /** | ||||
|     Determine if the theme supports the output format. | ||||
|     @method hasFormat | ||||
|      */ | ||||
|     hasFormat: function(fmt) { | ||||
|  | ||||
|     JRSTheme.prototype.hasFormat = function(fmt) { | ||||
|       return _.has(this.formats, fmt); | ||||
|     } | ||||
|     }; | ||||
|  | ||||
|  | ||||
|     /** | ||||
|     Return the requested output format. | ||||
|     @method getFormat | ||||
|      */ | ||||
|   }); | ||||
|  | ||||
|   getFormat = function(fmt) { | ||||
|     return this.formats[fmt]; | ||||
|   }; | ||||
|     JRSTheme.prototype.getFormat = function(fmt) { | ||||
|       return this.formats[fmt]; | ||||
|     }; | ||||
|  | ||||
|     return JRSTheme; | ||||
|  | ||||
|   })(); | ||||
|  | ||||
|   module.exports = JRSTheme; | ||||
|  | ||||
|   | ||||
							
								
								
									
										5
									
								
								dist/renderers/jrs-generator.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								dist/renderers/jrs-generator.js
									
									
									
									
										vendored
									
									
								
							| @@ -36,10 +36,11 @@ Definition of the JRSGenerator class. | ||||
|     generate: function(json, jst, format, cssInfo, opts, theme) { | ||||
|       var org, rezHtml, turnoff; | ||||
|       turnoff = ['log', 'error', 'dir']; | ||||
|       org = turnoff.map(c)(function() { | ||||
|       org = turnoff.map(function(c) { | ||||
|         var ret; | ||||
|         ret = console[c]; | ||||
|         return console[c] = function() {}; | ||||
|         console[c] = function() {}; | ||||
|         return ret; | ||||
|       }); | ||||
|       rezHtml = theme.render(json.harden()); | ||||
|       turnoff.forEach(function(c, idx) { | ||||
|   | ||||
							
								
								
									
										9
									
								
								dist/verbs/analyze.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								dist/verbs/analyze.js
									
									
									
									
										vendored
									
									
								
							| @@ -26,14 +26,7 @@ Implementation of the 'analyze' verb for HackMyResume. | ||||
|  | ||||
|   AnalyzeVerb = module.exports = Verb.extend({ | ||||
|     init: function() { | ||||
|       return this._super('analyze'); | ||||
|     }, | ||||
|     invoke: function() { | ||||
|       this.stat(HMEVENT.begin, { | ||||
|         cmd: 'analyze' | ||||
|       }); | ||||
|       analyze.apply(this, arguments); | ||||
|       return this.stat(HMEVENT.end); | ||||
|       return this._super('analyze', analyze); | ||||
|     } | ||||
|   }); | ||||
|  | ||||
|   | ||||
							
								
								
									
										41
									
								
								dist/verbs/build.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										41
									
								
								dist/verbs/build.js
									
									
									
									
										vendored
									
									
								
							| @@ -74,18 +74,7 @@ Implementation of the 'build' verb for HackMyResume. | ||||
|  | ||||
|     /** Create a new build verb. */ | ||||
|     init: function() { | ||||
|       return this._super('build'); | ||||
|     }, | ||||
|  | ||||
|     /** Invoke the Build command. */ | ||||
|     invoke: function() { | ||||
|       var ret; | ||||
|       this.stat(HMEVENT.begin, { | ||||
|         cmd: 'build' | ||||
|       }); | ||||
|       ret = build.apply(this, arguments); | ||||
|       this.stat(HMEVENT.end); | ||||
|       return ret; | ||||
|       return this._super('build', build); | ||||
|     } | ||||
|   }); | ||||
|  | ||||
| @@ -95,16 +84,16 @@ Implementation of the 'build' verb for HackMyResume. | ||||
|   theme file, generate 0..N resumes in the desired formats. | ||||
|   @param src Path to the source JSON resume file: "rez/resume.json". | ||||
|   @param dst An array of paths to the target resume file(s). | ||||
|   @param theme Friendly name of the resume theme. Defaults to "modern". | ||||
|   @param logger Optional logging override. | ||||
|   @param opts Generation options. | ||||
|    */ | ||||
|  | ||||
|   build = function(src, dst, opts) { | ||||
|     var ex, inv, isFRESH, mixed, newEx, orgFormat, rez, sheetObjects, sheets, tFolder, targets, theme, toFormat; | ||||
|     var ex, inv, isFRESH, mixed, newEx, orgFormat, problemSheets, rez, sheetObjects, sheets, tFolder, targets, theme, toFormat; | ||||
|     if (!src || !src.length) { | ||||
|       this.err(HMSTATUS.resumeNotFound, { | ||||
|         quit: true | ||||
|       }); | ||||
|       return null; | ||||
|     } | ||||
|     prep(src, dst, opts); | ||||
|     sheetObjects = ResumeFactory.load(src, { | ||||
| @@ -115,9 +104,12 @@ Implementation of the 'build' verb for HackMyResume. | ||||
|         sort: _opts.sort | ||||
|       } | ||||
|     }, this); | ||||
|     if (!sheetObjects || _.some(sheetObjects, function(so) { | ||||
|     problemSheets = _.filter(sheetObjects, function(so) { | ||||
|       return so.fluenterror; | ||||
|     })) { | ||||
|     }); | ||||
|     if (problemSheets && problemSheets.length) { | ||||
|       problemSheets[0].quit = true; | ||||
|       this.err(problemSheets[0].fluenterror, problemSheets[0]); | ||||
|       return null; | ||||
|     } | ||||
|     sheets = sheetObjects.map(function(r) { | ||||
| @@ -130,12 +122,14 @@ Implementation of the 'build' verb for HackMyResume. | ||||
|     try { | ||||
|       tFolder = verifyTheme.call(this, _opts.theme); | ||||
|       theme = _opts.themeObj = loadTheme(tFolder); | ||||
|       addFreebieFormats(theme); | ||||
|     } catch (_error) { | ||||
|       ex = _error; | ||||
|       newEx = { | ||||
|         fluenterror: HMSTATUS.themeLoad, | ||||
|         inner: ex, | ||||
|         attempted: _opts.theme | ||||
|         attempted: _opts.theme, | ||||
|         quit: true | ||||
|       }; | ||||
|       this.err(HMSTATUS.themeLoad, newEx); | ||||
|       return null; | ||||
| @@ -147,8 +141,10 @@ Implementation of the 'build' verb for HackMyResume. | ||||
|     if (inv && inv.length) { | ||||
|       this.err(HMSTATUS.invalidFormat, { | ||||
|         data: inv, | ||||
|         theme: theme | ||||
|         theme: theme, | ||||
|         quit: true | ||||
|       }); | ||||
|       return null; | ||||
|     } | ||||
|     rez = null; | ||||
|     if (sheets.length > 1) { | ||||
| @@ -186,7 +182,6 @@ Implementation of the 'build' verb for HackMyResume. | ||||
|         fmt: toFormat | ||||
|       }); | ||||
|     } | ||||
|     addFreebieFormats(theme); | ||||
|     this.stat(HMEVENT.applyTheme, { | ||||
|       r: rez, | ||||
|       theme: theme | ||||
| @@ -247,12 +242,12 @@ Implementation of the 'build' verb for HackMyResume. | ||||
|         fmt: targInfo.fmt.outFormat, | ||||
|         file: PATH.relative(process.cwd(), f) | ||||
|       }); | ||||
|       _opts.targets = finished; | ||||
|       if (targInfo.fmt.files && targInfo.fmt.files.length) { | ||||
|         theFormat = _fmts.filter(function(fmt) { | ||||
|           return fmt.name === targInfo.fmt.outFormat; | ||||
|         })[0]; | ||||
|         MKDIRP.sync(PATH.dirname(f)); | ||||
|         _opts.targets = finished; | ||||
|         ret = theFormat.gen.generate(_rezObj, f, _opts); | ||||
|       } else { | ||||
|         theFormat = _fmts.filter(function(fmt) { | ||||
| @@ -284,9 +279,7 @@ Implementation of the 'build' verb for HackMyResume. | ||||
|   }; | ||||
|  | ||||
|  | ||||
|   /** | ||||
|   Ensure that user-specified outputs/targets are valid. | ||||
|    */ | ||||
|   /** Ensure that user-specified outputs/targets are valid. */ | ||||
|  | ||||
|   verifyOutputs = function(targets, theme) { | ||||
|     this.stat(HMEVENT.verifyOutputs, { | ||||
|   | ||||
							
								
								
									
										9
									
								
								dist/verbs/convert.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								dist/verbs/convert.js
									
									
									
									
										vendored
									
									
								
							| @@ -22,14 +22,7 @@ Implementation of the 'convert' verb for HackMyResume. | ||||
|  | ||||
|   ConvertVerb = module.exports = Verb.extend({ | ||||
|     init: function() { | ||||
|       return this._super('convert'); | ||||
|     }, | ||||
|     invoke: function() { | ||||
|       this.stat(HMEVENT.begin, { | ||||
|         cmd: 'convert' | ||||
|       }); | ||||
|       convert.apply(this, arguments); | ||||
|       return this.stat(HMEVENT.end); | ||||
|       return this._super('convert', convert); | ||||
|     } | ||||
|   }); | ||||
|  | ||||
|   | ||||
							
								
								
									
										11
									
								
								dist/verbs/create.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										11
									
								
								dist/verbs/create.js
									
									
									
									
										vendored
									
									
								
							| @@ -24,16 +24,7 @@ Implementation of the 'create' verb for HackMyResume. | ||||
|  | ||||
|   CreateVerb = module.exports = Verb.extend({ | ||||
|     init: function() { | ||||
|       return this._super('new'); | ||||
|     }, | ||||
|     invoke: function() { | ||||
|       this.stat(HMEVENT.begin, { | ||||
|         cmd: 'create' | ||||
|       }); | ||||
|       create.apply(this, arguments); | ||||
|       this.stat(HMEVENT.begin, { | ||||
|         cmd: 'convert' | ||||
|       }); | ||||
|       return this._super('new', create); | ||||
|     } | ||||
|   }); | ||||
|  | ||||
|   | ||||
							
								
								
									
										9
									
								
								dist/verbs/peek.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								dist/verbs/peek.js
									
									
									
									
										vendored
									
									
								
							| @@ -22,14 +22,7 @@ Implementation of the 'peek' verb for HackMyResume. | ||||
|  | ||||
|   PeekVerb = module.exports = Verb.extend({ | ||||
|     init: function() { | ||||
|       return this._super('peek'); | ||||
|     }, | ||||
|     invoke: function() { | ||||
|       this.stat(HMEVENT.begin, { | ||||
|         cmd: 'peek' | ||||
|       }); | ||||
|       peek.apply(this, arguments); | ||||
|       return this.stat(HMEVENT.end); | ||||
|       return this._super('peek', peek); | ||||
|     } | ||||
|   }); | ||||
|  | ||||
|   | ||||
							
								
								
									
										12
									
								
								dist/verbs/validate.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										12
									
								
								dist/verbs/validate.js
									
									
									
									
										vendored
									
									
								
							| @@ -31,16 +31,7 @@ Implementation of the 'validate' verb for HackMyResume. | ||||
|  | ||||
|   ValidateVerb = module.exports = Verb.extend({ | ||||
|     init: function() { | ||||
|       return this._super('validate'); | ||||
|     }, | ||||
|     invoke: function() { | ||||
|       var ret; | ||||
|       this.stat(HMEVENT.begin, { | ||||
|         cmd: 'validate' | ||||
|       }); | ||||
|       ret = validate.apply(this, arguments); | ||||
|       this.stat(HMEVENT.end); | ||||
|       return ret; | ||||
|       return this._super('validate', validate); | ||||
|     } | ||||
|   }); | ||||
|  | ||||
| @@ -104,7 +95,6 @@ Implementation of the 'validate' verb for HackMyResume. | ||||
|           shouldExit: true | ||||
|         }; | ||||
|       } | ||||
|       console.log('1111'); | ||||
|       return ret; | ||||
|     }, this); | ||||
|   }; | ||||
|   | ||||
							
								
								
									
										18
									
								
								dist/verbs/verb.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										18
									
								
								dist/verbs/verb.js
									
									
									
									
										vendored
									
									
								
							| @@ -6,12 +6,14 @@ Definition of the Verb class. | ||||
|  */ | ||||
|  | ||||
| (function() { | ||||
|   var Class, EVENTS, Verb; | ||||
|   var Class, EVENTS, HMEVENT, Verb; | ||||
|  | ||||
|   Class = require('../utils/class'); | ||||
|  | ||||
|   EVENTS = require('events'); | ||||
|  | ||||
|   HMEVENT = require('../core/event-codes'); | ||||
|  | ||||
|  | ||||
|   /** | ||||
|   An instantiation of a HackMyResume command. | ||||
| @@ -21,9 +23,21 @@ Definition of the Verb class. | ||||
|   Verb = module.exports = Class.extend({ | ||||
|  | ||||
|     /** Constructor. Automatically called at creation. */ | ||||
|     init: function(moniker) { | ||||
|     init: function(moniker, workhorse) { | ||||
|       this.moniker = moniker; | ||||
|       this.emitter = new EVENTS.EventEmitter(); | ||||
|       this.workhorse = workhorse; | ||||
|     }, | ||||
|  | ||||
|     /** Invoke the command. */ | ||||
|     invoke: function() { | ||||
|       var ret; | ||||
|       this.stat(HMEVENT.begin, { | ||||
|         cmd: this.moniker | ||||
|       }); | ||||
|       ret = this.workhorse.apply(this, arguments); | ||||
|       this.stat(HMEVENT.end); | ||||
|       return ret; | ||||
|     }, | ||||
|  | ||||
|     /** Forward subscriptions to the event emitter. */ | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|   "name": "hackmyresume", | ||||
|   "version": "1.7.3", | ||||
|   "version": "1.7.4", | ||||
|   "description": "Generate polished résumés and CVs in HTML, Markdown, LaTeX, MS Word, PDF, plain text, JSON, XML, YAML, smoke signal, and carrier pigeon.", | ||||
|   "repository": { | ||||
|     "type": "git", | ||||
|   | ||||
							
								
								
									
										53
									
								
								src/core/abstract-resume.coffee
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								src/core/abstract-resume.coffee
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | ||||
| ###* | ||||
| 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 | ||||
|  | ||||
| module.exports = AbstractResume | ||||
| @@ -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 = {} | ||||
| @@ -56,16 +59,7 @@ FluentDate.fmt = ( dt, throws ) -> | ||||
|     else if /^\s*\d{4}\s*$/.test(dt) # "2015" | ||||
|       return moment dt, 'YYYY' | ||||
|     else if /^\s*$/.test(dt) # "", " " | ||||
|       defTime = | ||||
|         isNull: true | ||||
|         isBefore: ( other ) -> | ||||
|           if other and !other.isNull then true else false | ||||
|         isAfter: ( other ) -> | ||||
|           if other and !other.isNull then false else false | ||||
|         unix: () -> 0 | ||||
|         format: () -> '' | ||||
|         diff: () -> 0 | ||||
|       return defTime | ||||
|       return moment() | ||||
|     else | ||||
|       mt = moment dt | ||||
|       if mt.isValid() | ||||
|   | ||||
| @@ -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 | ||||
|  | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -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) | ||||
|  | ||||
|  | ||||
|   ###* | ||||
|   | ||||
| @@ -20,68 +20,69 @@ The JRSTheme class is a representation of a JSON Resume theme asset. | ||||
| class JRSTheme | ||||
|  | ||||
|  | ||||
| ###* | ||||
| Open and parse the specified theme. | ||||
| @method open | ||||
| ### | ||||
| open: ( thFolder ) -> | ||||
|  | ||||
|   @folder = thFolder | ||||
|   ###* | ||||
|   Open and parse the specified theme. | ||||
|   @method open | ||||
|   ### | ||||
|   open: ( thFolder ) -> | ||||
|  | ||||
|   # Open the [theme-name].json file; should have the same | ||||
|   # name as folder | ||||
|   pathInfo = parsePath thFolder | ||||
|     @folder = 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 | ||||
|     this.name = thPkg.name | ||||
|     this.render = (thApi && thApi.render) || undefined | ||||
|     this.engine = 'jrs' | ||||
|     # Open the [theme-name].json file; should have the same | ||||
|     # name as folder | ||||
|     pathInfo = parsePath thFolder | ||||
|  | ||||
|     # 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' | ||||
|         files: [{ | ||||
|           action: 'transform', | ||||
|           render: this.render, | ||||
|           major: true, | ||||
|           ext: 'html', | ||||
|           css: null | ||||
|         }] | ||||
|       pdf: | ||||
|         outFormat: 'pdf' | ||||
|         files: [{ | ||||
|           action: 'transform', | ||||
|           render: this.render, | ||||
|           major: true, | ||||
|           ext: 'pdf', | ||||
|           css: null | ||||
|         }] | ||||
|   else | ||||
|     throw { fluenterror: HACKMYSTATUS.missingPackageJSON }; | ||||
|   @ | ||||
|     # Open and parse the theme's package.json file. | ||||
|     pkgJsonPath = PATH.join thFolder, 'package.json' | ||||
|     if pathExists pkgJsonPath | ||||
|       thApi = require thFolder | ||||
|       thPkg = require pkgJsonPath | ||||
|       this.name = thPkg.name | ||||
|       this.render = (thApi && thApi.render) || undefined | ||||
|       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' | ||||
|           files: [{ | ||||
|             action: 'transform', | ||||
|             render: this.render, | ||||
|             major: true, | ||||
|             ext: 'html', | ||||
|             css: null | ||||
|           }] | ||||
|         pdf: | ||||
|           outFormat: 'pdf' | ||||
|           files: [{ | ||||
|             action: 'transform', | ||||
|             render: this.render, | ||||
|             major: true, | ||||
|             ext: 'pdf', | ||||
|             css: null | ||||
|           }] | ||||
|     else | ||||
|       throw { fluenterror: HACKMYSTATUS.missingPackageJSON }; | ||||
|     @ | ||||
|  | ||||
|  | ||||
|  | ||||
| ###* | ||||
| Determine if the theme supports the output format. | ||||
| @method hasFormat | ||||
| ### | ||||
| hasFormat: ( fmt ) ->  _.has this.formats, fmt | ||||
|   ###* | ||||
|   Determine if the theme supports the output format. | ||||
|   @method hasFormat | ||||
|   ### | ||||
|   hasFormat: ( fmt ) ->  _.has this.formats, fmt | ||||
|  | ||||
|  | ||||
|  | ||||
| ###* | ||||
| Return the requested output format. | ||||
| @method getFormat | ||||
| ### | ||||
| getFormat = ( fmt ) -> @formats[ fmt ] | ||||
|   ###* | ||||
|   Return the requested output format. | ||||
|   @method getFormat | ||||
|   ### | ||||
|   getFormat: ( fmt ) -> @formats[ fmt ] | ||||
|  | ||||
|  | ||||
| module.exports = JRSTheme; | ||||
|   | ||||
| @@ -25,9 +25,10 @@ JRSGenerator = module.exports = | ||||
|  | ||||
|     # Disable JRS theme chatter (console.log, console.error, etc.) | ||||
|     turnoff = ['log', 'error', 'dir']; | ||||
|     org = turnoff.map(c) -> | ||||
|     org = turnoff.map (c) -> | ||||
|       ret = console[c] | ||||
|       console[c] = () -> | ||||
|       ret | ||||
|  | ||||
|     # Freeze and render | ||||
|     rezHtml = theme.render json.harden() | ||||
|   | ||||
| @@ -19,12 +19,7 @@ chalk         = require('chalk') | ||||
|  | ||||
| AnalyzeVerb = module.exports = Verb.extend | ||||
|  | ||||
|   init: -> @._super 'analyze' | ||||
|  | ||||
|   invoke: -> | ||||
|     @.stat HMEVENT.begin, { cmd: 'analyze' } | ||||
|     analyze.apply @, arguments | ||||
|     @.stat HMEVENT.end | ||||
|   init: -> @_super 'analyze', analyze | ||||
|  | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -42,14 +42,7 @@ loadTheme = null | ||||
| BuildVerb = module.exports = Verb.extend | ||||
|  | ||||
|   ###* Create a new build verb. ### | ||||
|   init: () -> @._super 'build' | ||||
|  | ||||
|   ###* Invoke the Build command. ### | ||||
|   invoke: -> | ||||
|     @stat HMEVENT.begin, { cmd: 'build' } | ||||
|     ret = build.apply @, arguments | ||||
|     @stat HMEVENT.end | ||||
|     return ret | ||||
|   init: () -> @_super 'build', build | ||||
|  | ||||
|  | ||||
|  | ||||
| @@ -58,13 +51,13 @@ Given a source resume in FRESH or JRS format, a destination resume path, and a | ||||
| theme file, generate 0..N resumes in the desired formats. | ||||
| @param src Path to the source JSON resume file: "rez/resume.json". | ||||
| @param dst An array of paths to the target resume file(s). | ||||
| @param theme Friendly name of the resume theme. Defaults to "modern". | ||||
| @param logger Optional logging override. | ||||
| @param opts Generation options. | ||||
| ### | ||||
| build = ( src, dst, opts ) -> | ||||
|  | ||||
|   if !src || !src.length | ||||
|     @err HMSTATUS.resumeNotFound, { quit: true } | ||||
|     @err HMSTATUS.resumeNotFound, quit: true | ||||
|     return null | ||||
|  | ||||
|   prep src, dst, opts | ||||
|  | ||||
| @@ -74,10 +67,14 @@ build = ( src, dst, opts ) -> | ||||
|   }, @); | ||||
|  | ||||
|   # Explicit check for any resume loading errors... | ||||
|   if !sheetObjects || _.some( sheetObjects, (so) -> return so.fluenterror ) | ||||
|   problemSheets = _.filter( sheetObjects, (so) -> return so.fluenterror ) | ||||
|   if( problemSheets && problemSheets.length ) | ||||
|     problemSheets[0].quit = true | ||||
|     @err problemSheets[0].fluenterror, problemSheets[0] | ||||
|     return null | ||||
|  | ||||
|   sheets = sheetObjects.map((r) -> return r.json ) | ||||
|   # Get the collection of raw JSON sheets | ||||
|   sheets = sheetObjects.map (r) -> r.json | ||||
|  | ||||
|   # Load the theme... | ||||
|   theme = null | ||||
| @@ -85,12 +82,14 @@ build = ( src, dst, opts ) -> | ||||
|   try | ||||
|     tFolder = verifyTheme.call @, _opts.theme | ||||
|     theme = _opts.themeObj = loadTheme tFolder | ||||
|     addFreebieFormats theme | ||||
|   catch ex | ||||
|     newEx = | ||||
|       fluenterror: HMSTATUS.themeLoad | ||||
|       inner: ex | ||||
|       attempted: _opts.theme | ||||
|     this.err HMSTATUS.themeLoad, newEx | ||||
|       quit: true | ||||
|     @err HMSTATUS.themeLoad, newEx | ||||
|     return null | ||||
|  | ||||
|   @stat HMEVENT.afterTheme, { theme: theme } | ||||
| @@ -98,7 +97,8 @@ build = ( src, dst, opts ) -> | ||||
|   # Check for invalid outputs... | ||||
|   inv = verifyOutputs.call @, dst, theme | ||||
|   if inv && inv.length | ||||
|     @err HMSTATUS.invalidFormat, { data: inv, theme: theme } | ||||
|     @err HMSTATUS.invalidFormat, data: inv, theme: theme, quit: true | ||||
|     return null | ||||
|  | ||||
|   ## Merge input resumes, yielding a single source resume. | ||||
|   rez = null | ||||
| @@ -107,7 +107,7 @@ build = ( src, dst, opts ) -> | ||||
|     mixed = _.any sheets, (s) -> return if isFRESH then s.basics else !s.basics | ||||
|     @stat HMEVENT.beforeMerge, { f: _.clone(sheetObjects), mixed: mixed } | ||||
|     if mixed | ||||
|       this.err HMSTATUS.mixedMerge | ||||
|       @err HMSTATUS.mixedMerge | ||||
|  | ||||
|     rez = _.reduceRight sheets, ( a, b, idx ) -> | ||||
|       extend( true, b, a ) | ||||
| @@ -124,8 +124,7 @@ build = ( src, dst, opts ) -> | ||||
|     rez = RConverter[ 'to' + toFormat ]( rez ); | ||||
|     @stat HMEVENT.afterInlineConvert, { file: sheetObjects[0].file, fmt: toFormat } | ||||
|  | ||||
|   # Add freebie formats to the theme | ||||
|   addFreebieFormats theme | ||||
|   # Announce the theme | ||||
|   @stat HMEVENT.applyTheme, { r: rez, theme: theme } | ||||
|  | ||||
|   # Load the resume into a FRESHResume or JRSResume object | ||||
| @@ -195,13 +194,14 @@ single = ( targInfo, theme, finished ) -> | ||||
|       fmt: targInfo.fmt.outFormat | ||||
|       file: PATH.relative(process.cwd(), f) | ||||
|  | ||||
|     _opts.targets = finished; | ||||
|  | ||||
|     # If targInfo.fmt.files exists, this format is backed by a document. | ||||
|     # Fluent/FRESH themes are handled here. | ||||
|     if targInfo.fmt.files && targInfo.fmt.files.length | ||||
|       theFormat = _fmts.filter( | ||||
|         (fmt) -> return fmt.name == targInfo.fmt.outFormat )[0]; | ||||
|       MKDIRP.sync( PATH.dirname( f ) ); # Ensure dest folder exists; | ||||
|       _opts.targets = finished; | ||||
|       ret = theFormat.gen.generate( _rezObj, f, _opts ); | ||||
|  | ||||
|     # Otherwise this is an ad-hoc format (JSON, YML, or PNG) that every theme | ||||
| @@ -235,11 +235,9 @@ single = ( targInfo, theme, finished ) -> | ||||
|  | ||||
|  | ||||
|  | ||||
| ###* | ||||
| Ensure that user-specified outputs/targets are valid. | ||||
| ### | ||||
| ###* Ensure that user-specified outputs/targets are valid. ### | ||||
| verifyOutputs = ( targets, theme ) -> | ||||
|   @.stat HMEVENT.verifyOutputs, { targets: targets, theme: theme } | ||||
|   @stat HMEVENT.verifyOutputs, { targets: targets, theme: theme } | ||||
|   _.reject targets.map( ( t ) -> | ||||
|     pathInfo = parsePath t | ||||
|     { | ||||
| @@ -247,6 +245,8 @@ verifyOutputs = ( targets, theme ) -> | ||||
|     }), | ||||
|     (t) -> t.format == 'all' || theme.hasFormat( t.format ) | ||||
|  | ||||
|  | ||||
|  | ||||
| ###* | ||||
| Reinforce the chosen theme with "freebie" formats provided by HackMyResume. | ||||
| A "freebie" format is an output format such as JSON, YML, or PNG that can be | ||||
|   | ||||
| @@ -17,12 +17,7 @@ HMEVENT = require('../core/event-codes'); | ||||
|  | ||||
| ConvertVerb = module.exports = Verb.extend | ||||
|  | ||||
|   init: -> @._super 'convert' | ||||
|  | ||||
|   invoke: -> | ||||
|     @.stat HMEVENT.begin, { cmd: 'convert' } | ||||
|     convert.apply @, arguments | ||||
|     @.stat HMEVENT.end | ||||
|   init: -> @_super 'convert', convert | ||||
|  | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -17,13 +17,8 @@ HMEVENT = require('../core/event-codes') | ||||
|  | ||||
| CreateVerb = module.exports = Verb.extend | ||||
|  | ||||
|   init: -> @._super('new') | ||||
|   init: -> @_super 'new', create | ||||
|  | ||||
|   invoke: -> | ||||
|     @.stat HMEVENT.begin, { cmd: 'create' } | ||||
|     create.apply @, arguments | ||||
|     @.stat HMEVENT.begin, { cmd: 'convert' } | ||||
|     return | ||||
|  | ||||
|  | ||||
| ###* | ||||
|   | ||||
| @@ -17,12 +17,7 @@ HMEVENT = require('../core/event-codes') | ||||
|  | ||||
| PeekVerb = module.exports = Verb.extend | ||||
|  | ||||
|   init: -> @._super('peek') | ||||
|  | ||||
|   invoke: -> | ||||
|     @.stat HMEVENT.begin, { cmd: 'peek' } | ||||
|     peek.apply @, arguments | ||||
|     @.stat HMEVENT.end | ||||
|   init: -> @_super 'peek', peek | ||||
|  | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -21,13 +21,7 @@ safeLoadJSON = require '../utils/safe-json-loader' | ||||
| ###* An invokable resume validation command. ### | ||||
| ValidateVerb = module.exports = Verb.extend | ||||
|  | ||||
|   init: -> @_super 'validate' | ||||
|  | ||||
|   invoke: -> | ||||
|     @stat HMEVENT.begin, { cmd: 'validate' } | ||||
|     ret = validate.apply @, arguments | ||||
|     @stat HMEVENT.end | ||||
|     return ret | ||||
|   init: -> @_super 'validate', validate | ||||
|  | ||||
|  | ||||
|  | ||||
| @@ -85,8 +79,6 @@ validate = (sources, unused, opts)  -> | ||||
|     if opts.assert and !ret.isValid | ||||
|       throw fluenterror: HMSTATUS.invalid, shouldExit: true | ||||
|  | ||||
|     console.log '1111' | ||||
|  | ||||
|     return ret | ||||
|     ret | ||||
|  | ||||
|   , @ | ||||
|   | ||||
| @@ -6,10 +6,10 @@ Definition of the Verb class. | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| # Use J. Resig's nifty class implementation | ||||
| Class = require '../utils/class' | ||||
| EVENTS = require 'events' | ||||
| HMEVENT = require '../core/event-codes' | ||||
|  | ||||
|  | ||||
|  | ||||
| @@ -22,24 +22,34 @@ Verb = module.exports = Class.extend | ||||
|  | ||||
|  | ||||
|   ###* Constructor. Automatically called at creation. ### | ||||
|   init: ( moniker ) -> | ||||
|     @.moniker = moniker | ||||
|     @.emitter = new EVENTS.EventEmitter() | ||||
|   init: ( moniker, workhorse ) -> | ||||
|     @moniker = moniker | ||||
|     @emitter = new EVENTS.EventEmitter() | ||||
|     @workhorse = workhorse | ||||
|     return | ||||
|  | ||||
|  | ||||
|  | ||||
|   ###* Invoke the command. ### | ||||
|   invoke: -> | ||||
|     @stat HMEVENT.begin, cmd: @moniker | ||||
|     ret = @workhorse.apply @, arguments | ||||
|     @stat HMEVENT.end | ||||
|     ret | ||||
|  | ||||
|  | ||||
|  | ||||
|   ###* Forward subscriptions to the event emitter. ### | ||||
|   on: -> | ||||
|     this.emitter.on.apply @.emitter, arguments | ||||
|     @emitter.on.apply @emitter, arguments | ||||
|  | ||||
|  | ||||
|  | ||||
|   ###* Fire an arbitrary event, scoped to "hmr:". ### | ||||
|   fire: (evtName, payload) -> | ||||
|     payload = payload || { } | ||||
|     payload.cmd = this.moniker | ||||
|     this.emitter.emit 'hmr:' + evtName, payload | ||||
|     payload.cmd = @moniker | ||||
|     @emitter.emit 'hmr:' + evtName, payload | ||||
|     true | ||||
|  | ||||
|  | ||||
| @@ -60,10 +70,11 @@ Verb = module.exports = Class.extend | ||||
|   stat: ( subEvent, payload ) -> | ||||
|     payload = payload || { } | ||||
|     payload.sub = subEvent | ||||
|     this.fire 'status', payload | ||||
|     @fire 'status', payload | ||||
|     true | ||||
|  | ||||
|  | ||||
|  | ||||
|   ###* Associate error info with the invocation. ### | ||||
|   setError: ( code, obj ) -> | ||||
|     @errorCode = code | ||||
|   | ||||
| @@ -9,3 +9,4 @@ require('./scripts/test-jrs-sheet'); | ||||
| require('./scripts/test-themes'); | ||||
| require('./scripts/test-api'); | ||||
| require('./scripts/test-output'); | ||||
| require('./scripts/test-dates'); | ||||
|   | ||||
							
								
								
									
										81
									
								
								test/scripts/test-dates.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								test/scripts/test-dates.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,81 @@ | ||||
| /** | ||||
| @module test-dates.js | ||||
| */ | ||||
|  | ||||
| var chai = require('chai') | ||||
|   , expect = chai.expect | ||||
|   , should = chai.should() | ||||
|   , path = require('path') | ||||
|   , _ = require('underscore') | ||||
| 	, FRESHResume = require('../../dist/core/fresh-resume') | ||||
|   , FCMD = require( '../../dist/index') | ||||
|   , validator = require('is-my-json-valid') | ||||
|   , EXTEND = require('extend'); | ||||
|  | ||||
| chai.config.includeStack = true; | ||||
|  | ||||
| var gig = { | ||||
|   employer: 'E1' | ||||
| }; | ||||
|  | ||||
| var r = { | ||||
|   name: 'John Doe', | ||||
|   meta: { | ||||
|     format: 'FRESH@0.6.0' | ||||
|   }, | ||||
|   employment: { | ||||
|     history: [ null ] | ||||
|   } | ||||
| }; | ||||
|  | ||||
| var tests = [ | ||||
|   // single job, concrete start, no end | ||||
|   [ { start: '2010-01-01' } , { val: 6, unit: 'year' } ], | ||||
|   [ { start: '2010-01' } , { val: 6, unit: 'year' } ], | ||||
|   [ { start: '2010' } , { val: 6, unit: 'year' } ], | ||||
|  | ||||
|   // single job, concrete start, concrete end | ||||
|   [ { start: '2010-01-01', end: '2015-01-01' } , { val: 5, unit: 'year' } ], | ||||
|   [ { start: '2010-01', end: '2015-01' } , { val: 5, unit: 'year' } ], | ||||
|   [ { start: '2010', end: '2015' } , { val: 5, unit: 'year' } ], | ||||
|  | ||||
|   // single job, falsy start, falsy end | ||||
|   [ { } , { val: 0, unit: 'year' } ], | ||||
|   [ { start: null } , { val: 0, unit: 'year' } ], | ||||
|   [ { end: null } , { val: 0, unit: 'year' } ], | ||||
|   [ { start: undefined } , { val: 0, unit: 'year' } ], | ||||
|   [ { end: undefined } , { val: 0, unit: 'year' } ], | ||||
|   [ { start: null, end: null } , { val: 0, unit: 'year' } ], | ||||
|   [ { start: '', end: '' } , { val: 0, unit: 'year' } ], | ||||
|   [ { start: ' ', end: ' ' } , { val: 0, unit: 'year' } ], | ||||
|   [ { start: undefined, end: undefined } , { val: 0, unit: 'year' } ], | ||||
|  | ||||
|   // two jobs (concrete start + end) -> ( concrete start ) | ||||
|   [ { start: '2000-01', end: '2013-01' }, { start: '2013-01' }, { val: 16, unit: 'year' } ], | ||||
|   [ { start: '2000-01', end: '2013-01' }, { start: '2013-01', end: '' }, { val: 16, unit: 'year' } ], | ||||
|   [ { start: '2000-01', end: '2013-01' }, { start: '2013-01', end: null }, { val: 16, unit: 'year' } ], | ||||
|   [ { start: '2000-01', end: '2013-01' }, { start: '2013-01', end: 'current' }, { val: 16, unit: 'year' } ] | ||||
|  | ||||
| ]; | ||||
|  | ||||
| tests.forEach(function(t){ | ||||
|    _.initial( t ).forEach(function(t){ t.employer = 'E1' }); | ||||
| }) | ||||
|  | ||||
| describe('Testing DATES', function () { | ||||
|  | ||||
|   tests.forEach( function(t) { | ||||
|  | ||||
|     it( JSON.stringify( _.initial(t) ), function () { | ||||
|       r.employment.history = _.initial( t ); | ||||
|       var rObj = new FRESHResume(); | ||||
|       rObj.parseJSON( r ); | ||||
|       var dur = rObj.duration(); | ||||
|       expect( dur ).to.equal( _.last(t).val ); | ||||
|     }); | ||||
|  | ||||
|   }); | ||||
|  | ||||
|  | ||||
|  | ||||
| }); | ||||
		Reference in New Issue
	
	Block a user