mirror of
				https://github.com/JuanCanham/HackMyResume.git
				synced 2025-10-30 20:57:26 +00:00 
			
		
		
		
	Compare commits
	
		
			69 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 3cf850ea0e | ||
|  | 1b0bc87b60 | ||
|  | 5d3b993737 | ||
|  | 917fd8e3f3 | ||
|  | 6ac2cd490b | ||
|  | 8100190978 | ||
|  | 7c36ff8331 | ||
|  | 255a518565 | ||
|  | 2d595350c6 | ||
|  | ca92d41d9e | ||
|  | 3f8e795c61 | ||
|  | 9927e79900 | ||
|  | dbef9f0a35 | ||
|  | c889664c31 | ||
|  | 7a60cd0bab | ||
|  | 964350d3c7 | ||
|  | b57d9f05af | ||
|  | b26799f9fc | ||
|  | daeffd27b5 | ||
|  | f87eb46549 | ||
|  | da7cd28734 | ||
|  | 31e0bb69cc | ||
|  | 5c248cca2a | ||
|  | f83eb018e8 | ||
|  | 317a250917 | ||
|  | aaa5e1fc1f | ||
|  | 1bc4263a46 | ||
|  | e191af1fb0 | ||
|  | 7c0a9bcc02 | ||
|  | d894f62607 | ||
|  | 2758038858 | ||
|  | 661fb91861 | ||
|  | 3c551eb923 | ||
|  | 5bf4bda6de | ||
|  | 49ae016f08 | ||
|  | 89957aed76 | ||
|  | 233025ddcc | ||
|  | 11dd8952d8 | ||
|  | d7c83613df | ||
|  | a456093f13 | ||
|  | dd4851498a | ||
|  | f72b02a0f4 | ||
|  | 63a0c78fc5 | ||
|  | fd39cc9fd9 | ||
|  | 70f45d468d | ||
|  | 212b01092c | ||
|  | 36d641801b | ||
|  | bd278268f6 | ||
|  | abe31e30e0 | ||
|  | 314d8d8763 | ||
|  | ed0792e8f8 | ||
|  | 90765bf90b | ||
|  | f1ba7765ee | ||
|  | 27c7a0264a | ||
|  | 8e806dc04f | ||
|  | 8ec6b5ed6a | ||
|  | 4ef4ec5d42 | ||
|  | 2f523b845b | ||
|  | 1c416f39d3 | ||
|  | 1de0eff7b3 | ||
|  | f8a39b0908 | ||
|  | d69e4635be | ||
|  | 4b7d594502 | ||
|  | 896b7055c1 | ||
|  | 0f65e4c9f3 | ||
|  | e9971eb882 | ||
|  | beb60d4074 | ||
|  | 4440d23584 | ||
|  | aca67cec29 | 
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -4,6 +4,7 @@ doc/ | |||||||
| docs/ | docs/ | ||||||
| local/ | local/ | ||||||
| npm-debug.log | npm-debug.log | ||||||
|  | *.map | ||||||
|  |  | ||||||
| # Emacs detritus | # Emacs detritus | ||||||
| # -*- mode: gitignore; -*- | # -*- mode: gitignore; -*- | ||||||
|   | |||||||
							
								
								
									
										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 | ||||||
							
								
								
									
										105
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										105
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -1,5 +1,108 @@ | |||||||
| CHANGELOG | CHANGELOG | ||||||
| ========= | ========= | ||||||
|  | ## v1.8.0 | ||||||
|  |  | ||||||
|  | ### Added | ||||||
|  |  | ||||||
|  | - Updated `Awesome` theme to latest version of [Awesome-CV][acv]. | ||||||
|  |  | ||||||
|  | - Introduced new theme helpers: `pad`, `date`. | ||||||
|  |  | ||||||
|  | ### Fixed | ||||||
|  |  | ||||||
|  | - Fixed an issue where the `Awesome` theme wouldn't correctly generate LaTeX | ||||||
|  | outputs (#138). | ||||||
|  |  | ||||||
|  | - Emit a line number for syntax errors around embedded newlines in JSON strings | ||||||
|  | (#137). | ||||||
|  |  | ||||||
|  | - Fix several PDF / PNG generation errors (#132, others). | ||||||
|  |  | ||||||
|  | - Display a more helpful error message when attempting to generate a PDF or PNG | ||||||
|  | on a machine where PhantomJS and/or wkhtmltopdf are either not installed or | ||||||
|  | not path-accessible. | ||||||
|  |  | ||||||
|  | - Fixed an issue that would cause long-running PDF/PNG generation to fail in | ||||||
|  | certain environments. | ||||||
|  |  | ||||||
|  | - Fixed an issue involving an unhelpful spawn-related exception (#136). | ||||||
|  |  | ||||||
|  | ### Internal | ||||||
|  |  | ||||||
|  | - JSHint will no longer gripe at the use of `== null` and `!= null` in | ||||||
|  | CoffeeScript transpilation. | ||||||
|  |  | ||||||
|  | - Introduced [template-friendly Awesome-CV fork][awefork] to isolate template | ||||||
|  | expansion logic & provide better durability for HackMyResume's `awesome` theme. | ||||||
|  |  | ||||||
|  | - Fixed a couple temporary regressions (#139, #140) on the dev branch. | ||||||
|  |  | ||||||
|  | - Additional tests. | ||||||
|  |  | ||||||
|  | - Minor breaking HackMyResume API changes. | ||||||
|  |  | ||||||
|  | ## v1.7.4 | ||||||
|  |  | ||||||
|  | ### Added | ||||||
|  |  | ||||||
|  | - [Build instructions](https://github.com/hacksalot/HackMyResume/blob/master/BUILDING.md). | ||||||
|  |  | ||||||
|  | ### Changed | ||||||
|  |  | ||||||
|  | - More precise date handling. | ||||||
|  |  | ||||||
|  | ### Fixed | ||||||
|  |  | ||||||
|  | - Issue with incomplete PDF generation (#127). | ||||||
|  |  | ||||||
|  | - Issue with building JSON Resume themes (#128). | ||||||
|  |  | ||||||
|  | - Issue with generating `.json` output format by itself (#97). | ||||||
|  |  | ||||||
|  | ## v1.7.3 | ||||||
|  |  | ||||||
|  | ### Fixed | ||||||
|  |  | ||||||
|  | - Issue with generated PDFs being chopped off and displaying a mysterious sequence of numbers of unknown and possibly alien origin (#127). | ||||||
|  |  | ||||||
|  | - Unsightly border on Modern:PDF. | ||||||
|  |  | ||||||
|  | - Modern|Positive:PDF formats now correctly reference their PDF-specific CSS files. | ||||||
|  |  | ||||||
|  | - `Incorrect helper use` warning in Positive:DOC. | ||||||
|  |  | ||||||
|  | ## v1.7.2 | ||||||
|  |  | ||||||
|  | ### Changed | ||||||
|  |  | ||||||
|  | - Interim release supporting FluentCV Desktop. | ||||||
|  |  | ||||||
|  | ### Internal | ||||||
|  |  | ||||||
|  | - Moved [HackMyCore](https://github.com/hacksalot/HackMyCore) dependency to | ||||||
|  | submodule. | ||||||
|  |  | ||||||
|  | ## v1.7.1 | ||||||
|  |  | ||||||
|  | ### Changed | ||||||
|  |  | ||||||
|  | - Caffeinate. CoffeeScript now used throughout | ||||||
|  | [HackMyResume](https://github.com/hacksalot/HackMyResume) and | ||||||
|  | [HackMyCore](https://github.com/hacksalot/HackMyCore); generated JavaScript | ||||||
|  | lives in `/dist`. | ||||||
|  |  | ||||||
|  | ### Fixed | ||||||
|  |  | ||||||
|  | - Issue with generating a single PDF with the `.pdf` extension (#99). | ||||||
|  |  | ||||||
|  | ## v1.7.0 | ||||||
|  |  | ||||||
|  | ### Changed | ||||||
|  |  | ||||||
|  | - [Internal] Relocated HMR processing code to the | ||||||
|  | [HackMyCore](https://github.com/hacksalot/HackMyCore) project. Shouldn't affect | ||||||
|  | normal use. | ||||||
|  |  | ||||||
| ## v1.6.0 | ## v1.6.0 | ||||||
|  |  | ||||||
| ### Major Improvements | ### Major Improvements | ||||||
| @@ -304,3 +407,5 @@ theme. | |||||||
| [i111]: https://github.com/hacksalot/HackMyResume/issues/111 | [i111]: https://github.com/hacksalot/HackMyResume/issues/111 | ||||||
| [fresca]: https://github.com/fluentdesk/FRESCA | [fresca]: https://github.com/fluentdesk/FRESCA | ||||||
| [themes]: https://github.com/fluentdesk/fresh-themes | [themes]: https://github.com/fluentdesk/fresh-themes | ||||||
|  | [awefork]: https://github.com/fluentdesk/Awesome-CV | ||||||
|  | [acv]: https://github.com/posquit0/Awesome-CV | ||||||
|   | |||||||
| @@ -4,17 +4,11 @@ Contributing | |||||||
| *Note: HackMyResume is also available as [FluentCV][fcv]. Contributors are | *Note: HackMyResume is also available as [FluentCV][fcv]. Contributors are | ||||||
| credited in both.* | 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 | ## 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 | 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 | 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 | 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 | 4. **Install dependencies** by running `npm install` in the top-level | ||||||
| HackMyResume folder. | HackMyResume folder. | ||||||
| 5. Make your **commits** as usual. | 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. **Push** your commits. | ||||||
| 7. **Submit a pull request** from your feature branch to the HackMyResume `dev` | 7. **Submit a pull request** from your feature branch to the HackMyResume `dev` | ||||||
| branch. | branch. | ||||||
| @@ -48,7 +42,7 @@ You can reach hacksalot directly at: | |||||||
| hacksalot@indevious.com | 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 | [fcv]: https://github.com/fluentdesk/fluentcv | ||||||
| [flow]: https://guides.github.com/introduction/flow/ | [flow]: https://guides.github.com/introduction/flow/ | ||||||
| @@ -56,3 +50,4 @@ Thanks! See you out there in the trenches. | |||||||
| [ha]: https://github.com/hacksalot | [ha]: https://github.com/hacksalot | ||||||
| [th]: https://github.com/tomheon | [th]: https://github.com/tomheon | ||||||
| [awesome]: https://github.com/hacksalot/HackMyResume/graphs/contributors | [awesome]: https://github.com/hacksalot/HackMyResume/graphs/contributors | ||||||
|  | [building]: https://github.com/hacksalot/HackMyResume/blob/master/BUILDING.md | ||||||
|   | |||||||
							
								
								
									
										10
									
								
								Gruntfile.js
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								Gruntfile.js
									
									
									
									
									
								
							| @@ -12,11 +12,14 @@ module.exports = function (grunt) { | |||||||
|         cwd: 'src', |         cwd: 'src', | ||||||
|         src: ['**/*','!**/*.coffee'], |         src: ['**/*','!**/*.coffee'], | ||||||
|         dest: 'dist/', |         dest: 'dist/', | ||||||
|       }, |       } | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     coffee: { |     coffee: { | ||||||
|       main: { |       main: { | ||||||
|  |         options: { | ||||||
|  |           sourceMap: true | ||||||
|  |         }, | ||||||
|         expand: true, |         expand: true, | ||||||
|         cwd: 'src', |         cwd: 'src', | ||||||
|         src: ['**/*.coffee'], |         src: ['**/*.coffee'], | ||||||
| @@ -67,9 +70,10 @@ module.exports = function (grunt) { | |||||||
|     jshint: { |     jshint: { | ||||||
|       options: { |       options: { | ||||||
|         laxcomma: true, |         laxcomma: true, | ||||||
|         expr: true |         expr: true, | ||||||
|  |         eqnull: true | ||||||
|       }, |       }, | ||||||
|       all: ['Gruntfile.js', 'src/**/*.js', 'test/*.js'] |       all: ['Gruntfile.js', 'dist/cli/**/*.js', 'test/*.js'] | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   }; |   }; | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| The MIT License | 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 | Permission is hereby granted, free of charge, to any person obtaining a copy | ||||||
| of this software and associated documentation files (the "Software"), to deal | of this software and associated documentation files (the "Software"), to deal | ||||||
|   | |||||||
| @@ -4,6 +4,7 @@ HackMyResume | |||||||
| [![Latest release][img-release]][latest-release] | [![Latest release][img-release]][latest-release] | ||||||
| [![Build status (MASTER)][img-master]][travis-url-master] | [![Build status (MASTER)][img-master]][travis-url-master] | ||||||
| [![Build status (DEV)][img-dev]][travis-url-dev] | [![Build status (DEV)][img-dev]][travis-url-dev] | ||||||
|  | [](https://gitter.im/hacksalot/HackMyResume?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) | ||||||
|  |  | ||||||
| *Create polished résumés and CVs in multiple formats from your command line or | *Create polished résumés and CVs in multiple formats from your command line or | ||||||
| shell. Author in clean Markdown and JSON, export to Word, HTML, PDF, LaTeX, | shell. Author in clean Markdown and JSON, export to Word, HTML, PDF, LaTeX, | ||||||
|   | |||||||
							
								
								
									
										36
									
								
								dist/cli/error.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										36
									
								
								dist/cli/error.js
									
									
									
									
										vendored
									
									
								
							| @@ -8,19 +8,19 @@ Error-handling routines for HackMyResume. | |||||||
| (function() { | (function() { | ||||||
|   var ErrorHandler, FCMD, FS, HMSTATUS, M2C, PATH, PKG, SyntaxErrorEx, WRAP, YAML, _defaultLog, assembleError, chalk, extend, printf; |   var ErrorHandler, FCMD, FS, HMSTATUS, M2C, PATH, PKG, SyntaxErrorEx, WRAP, YAML, _defaultLog, assembleError, chalk, extend, printf; | ||||||
|  |  | ||||||
|   HMSTATUS = require('hackmycore/dist/core/status-codes'); |   HMSTATUS = require('../core/status-codes'); | ||||||
|  |  | ||||||
|   PKG = require('../../package.json'); |   PKG = require('../../package.json'); | ||||||
|  |  | ||||||
|   FS = require('fs'); |   FS = require('fs'); | ||||||
|  |  | ||||||
|   FCMD = require('hackmycore'); |   FCMD = require('../index'); | ||||||
|  |  | ||||||
|   PATH = require('path'); |   PATH = require('path'); | ||||||
|  |  | ||||||
|   WRAP = require('word-wrap'); |   WRAP = require('word-wrap'); | ||||||
|  |  | ||||||
|   M2C = require('hackmycore/dist/utils/md2chalk.js'); |   M2C = require('../utils/md2chalk'); | ||||||
|  |  | ||||||
|   chalk = require('chalk'); |   chalk = require('chalk'); | ||||||
|  |  | ||||||
| @@ -30,13 +30,12 @@ Error-handling routines for HackMyResume. | |||||||
|  |  | ||||||
|   printf = require('printf'); |   printf = require('printf'); | ||||||
|  |  | ||||||
|   SyntaxErrorEx = require('hackmycore/dist/utils/syntax-error-ex'); |   SyntaxErrorEx = require('../utils/syntax-error-ex'); | ||||||
|  |  | ||||||
|   require('string.prototype.startswith'); |   require('string.prototype.startswith'); | ||||||
|  |  | ||||||
|  |  | ||||||
|   /** |   /** Error handler for HackMyResume. All errors are handled here. | ||||||
|   Error handler for HackMyResume. All errors are handled here. |  | ||||||
|   @class ErrorHandler |   @class ErrorHandler | ||||||
|    */ |    */ | ||||||
|  |  | ||||||
| @@ -62,7 +61,7 @@ Error-handling routines for HackMyResume. | |||||||
|           stack = ex.stack || (ex.inner && ex.inner.stack); |           stack = ex.stack || (ex.inner && ex.inner.stack); | ||||||
|           stack && o(chalk.gray(stack)); |           stack && o(chalk.gray(stack)); | ||||||
|         } |         } | ||||||
|         if (ex.quit || objError.quit) { |         if (shouldExit) { | ||||||
|           if (this.debug) { |           if (this.debug) { | ||||||
|             o(chalk.cyan('Exiting with error code ' + ex.fluenterror.toString())); |             o(chalk.cyan('Exiting with error code ' + ex.fluenterror.toString())); | ||||||
|           } |           } | ||||||
| @@ -140,7 +139,6 @@ Error-handling routines for HackMyResume. | |||||||
|         if (ex.inner) { |         if (ex.inner) { | ||||||
|           msg += chalk.red('\n' + ex.inner); |           msg += chalk.red('\n' + ex.inner); | ||||||
|         } |         } | ||||||
|         withStack = true; |  | ||||||
|         quit = false; |         quit = false; | ||||||
|         etype = 'error'; |         etype = 'error'; | ||||||
|         break; |         break; | ||||||
| @@ -215,13 +213,27 @@ Error-handling routines for HackMyResume. | |||||||
|         if (SyntaxErrorEx.is(ex.inner)) { |         if (SyntaxErrorEx.is(ex.inner)) { | ||||||
|           console.error(printf(M2C(this.msgs.readError.msg, 'red'), ex.file)); |           console.error(printf(M2C(this.msgs.readError.msg, 'red'), ex.file)); | ||||||
|           se = new SyntaxErrorEx(ex, ex.raw); |           se = new SyntaxErrorEx(ex, ex.raw); | ||||||
|           msg = printf(M2C(this.msgs.parseError.msg, 'red'), se.line, se.col); |           if ((se.line != null) && (se.col != null)) { | ||||||
|         } else if (ex.inner && ex.inner.line !== void 0 && ex.inner.col !== void 0) { |             msg = printf(M2C(this.msgs.parseError.msg[0], 'red'), se.line, se.col); | ||||||
|           msg = printf(M2C(this.msgs.parseError.msg, 'red'), ex.inner.line, ex.inner.col); |           } else if (se.line != null) { | ||||||
|  |             msg = printf(M2C(this.msgs.parseError.msg[1], 'red'), se.line); | ||||||
|  |           } else { | ||||||
|  |             msg = M2C(this.msgs.parseError.msg[2], 'red'); | ||||||
|  |           } | ||||||
|  |         } else if (ex.inner && (ex.inner.line != null) && (ex.inner.col != null)) { | ||||||
|  |           msg = printf(M2C(this.msgs.parseError.msg[0], 'red'), ex.inner.line, ex.inner.col); | ||||||
|         } else { |         } else { | ||||||
|           msg = ex; |           msg = ex; | ||||||
|         } |         } | ||||||
|         etype = 'error'; |         etype = 'error'; | ||||||
|  |         break; | ||||||
|  |       case HMSTATUS.createError: | ||||||
|  |         msg = printf(M2C(this.msgs.createError.msg), ex.inner.path); | ||||||
|  |         etype = 'error'; | ||||||
|  |         break; | ||||||
|  |       case HMSTATUS.validateError: | ||||||
|  |         msg = printf(M2C(this.msgs.validateError.msg), ex.inner.toString()); | ||||||
|  |         etype = 'error'; | ||||||
|     } |     } | ||||||
|     return { |     return { | ||||||
|       msg: msg, |       msg: msg, | ||||||
| @@ -232,3 +244,5 @@ Error-handling routines for HackMyResume. | |||||||
|   }; |   }; | ||||||
|  |  | ||||||
| }).call(this); | }).call(this); | ||||||
|  |  | ||||||
|  | //# sourceMappingURL=error.js.map | ||||||
|   | |||||||
							
								
								
									
										4
									
								
								src/index.js → dist/cli/index.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								src/index.js → dist/cli/index.js
									
									
									
									
										vendored
									
									
								
							| @@ -12,11 +12,11 @@ Command-line interface (CLI) for HackMyResume. | |||||||
| 
 | 
 | ||||||
| try { | try { | ||||||
| 
 | 
 | ||||||
|   require('./cli/main')( process.argv ); |   require('./main')( process.argv ); | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
| catch( ex ) { | catch( ex ) { | ||||||
| 
 | 
 | ||||||
|   require('./cli/error').err( ex, true ); |   require('./error').err( ex, true ); | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
							
								
								
									
										85
									
								
								dist/cli/main.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										85
									
								
								dist/cli/main.js
									
									
									
									
										vendored
									
									
								
							| @@ -6,9 +6,9 @@ Definition of the `main` function. | |||||||
|  */ |  */ | ||||||
|  |  | ||||||
| (function() { | (function() { | ||||||
|   var Command, EXTEND, FS, HME, HMR, HMSTATUS, OUTPUT, PAD, PATH, PKG, StringUtils, _, _opts, _out, _title, chalk, execute, initOptions, initialize, loadOptions, logMsg, main, safeLoadJSON, splitSrcDest; |   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('hackmycore'); |   HMR = require('../index'); | ||||||
|  |  | ||||||
|   PKG = require('../../package.json'); |   PKG = require('../../package.json'); | ||||||
|  |  | ||||||
| @@ -20,13 +20,13 @@ Definition of the `main` function. | |||||||
|  |  | ||||||
|   PATH = require('path'); |   PATH = require('path'); | ||||||
|  |  | ||||||
|   HMSTATUS = require('hackmycore/dist/core/status-codes'); |   HMSTATUS = require('../core/status-codes'); | ||||||
|  |  | ||||||
|   HME = require('hackmycore/dist/core/event-codes'); |   HME = require('../core/event-codes'); | ||||||
|  |  | ||||||
|   safeLoadJSON = require('hackmycore/dist/utils/safe-json-loader'); |   safeLoadJSON = require('../utils/safe-json-loader'); | ||||||
|  |  | ||||||
|   StringUtils = require('hackmycore/dist/utils/string.js'); |   StringUtils = require('../utils/string.js'); | ||||||
|  |  | ||||||
|   _ = require('underscore'); |   _ = require('underscore'); | ||||||
|  |  | ||||||
| @@ -36,12 +36,20 @@ Definition of the `main` function. | |||||||
|  |  | ||||||
|   Command = require('commander').Command; |   Command = require('commander').Command; | ||||||
|  |  | ||||||
|  |   M2C = require('../utils/md2chalk'); | ||||||
|  |  | ||||||
|  |   printf = require('printf'); | ||||||
|  |  | ||||||
|   _opts = {}; |   _opts = {}; | ||||||
|  |  | ||||||
|   _title = chalk.white.bold('\n*** HackMyResume v' + PKG.version + ' ***'); |   _title = chalk.white.bold('\n*** HackMyResume v' + PKG.version + ' ***'); | ||||||
|  |  | ||||||
|   _out = new OUTPUT(_opts); |   _out = new OUTPUT(_opts); | ||||||
|  |  | ||||||
|  |   _err = require('./error'); | ||||||
|  |  | ||||||
|  |   _exitCallback = null; | ||||||
|  |  | ||||||
|  |  | ||||||
|   /* |   /* | ||||||
|   A callable implementation of the HackMyResume CLI. Encapsulates the command |   A callable implementation of the HackMyResume CLI. Encapsulates the command | ||||||
| @@ -51,9 +59,9 @@ Definition of the `main` function. | |||||||
|   process.argv (in production) or custom parameters (in test). |   process.argv (in production) or custom parameters (in test). | ||||||
|    */ |    */ | ||||||
|  |  | ||||||
|   main = module.exports = function(rawArgs) { |   main = module.exports = function(rawArgs, exitCallback) { | ||||||
|     var args, initInfo, program; |     var args, initInfo, program; | ||||||
|     initInfo = initialize(rawArgs); |     initInfo = initialize(rawArgs, exitCallback); | ||||||
|     args = initInfo.args; |     args = initInfo.args; | ||||||
|     program = new Command('hackmyresume').version(PKG.version).description(chalk.yellow.bold('*** HackMyResume ***')).option('-s --silent', 'Run in silent mode').option('--no-color', 'Disable colors').option('--color', 'Enable colors').option('-d --debug', 'Enable diagnostics', false).option('-a --assert', 'Treat warnings as errors', false).option('-v --version', 'Show the version').allowUnknownOption(); |     program = 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.jsonArgs = initInfo.options; | ||||||
| @@ -92,8 +100,9 @@ Definition of the `main` function. | |||||||
|  |  | ||||||
|   /* Massage command-line args and setup Commander.js. */ |   /* Massage command-line args and setup Commander.js. */ | ||||||
|  |  | ||||||
|   initialize = function(ar) { |   initialize = function(ar, exitCallback) { | ||||||
|     var o; |     var o; | ||||||
|  |     _exitCallback = exitCallback || process.exit; | ||||||
|     o = initOptions(ar); |     o = initOptions(ar); | ||||||
|     o.silent || logMsg(_title); |     o.silent || logMsg(_title); | ||||||
|     if (o.debug) { |     if (o.debug) { | ||||||
| @@ -105,20 +114,18 @@ Definition of the `main` function. | |||||||
|       _out.log(chalk.cyan(PAD('  FRESCA:', 25, null, PAD.RIGHT)) + chalk.cyan.bold(PKG.dependencies.fresca)); |       _out.log(chalk.cyan(PAD('  FRESCA:', 25, null, PAD.RIGHT)) + chalk.cyan.bold(PKG.dependencies.fresca)); | ||||||
|       _out.log(''); |       _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]) { | ||||||
|       throw { |       _err.err({ | ||||||
|         fluenterror: HMSTATUS.invalidCommand, |         fluenterror: HMSTATUS.invalidCommand, | ||||||
|         quit: true, |         quit: true, | ||||||
|         attempted: o.orgVerb |         attempted: o.orgVerb | ||||||
|       }; |       }, true); | ||||||
|     } |     } | ||||||
|     Command.prototype.missingArgument = function(name) { |     Command.prototype.missingArgument = function(name) { | ||||||
|       if (this.name() !== 'new') { |       _err.err({ | ||||||
|         throw { |         fluenterror: this.name() !== 'new' ? HMSTATUS.resumeNotFound : HMSTATUS.createNameMissing | ||||||
|           fluenterror: HMSTATUS.resumeNotFound, |       }, true); | ||||||
|           quit: true |  | ||||||
|         }; |  | ||||||
|       } |  | ||||||
|     }; |     }; | ||||||
|     Command.prototype.helpInformation = function() { |     Command.prototype.helpInformation = function() { | ||||||
|       var manPage; |       var manPage; | ||||||
| @@ -136,7 +143,7 @@ Definition of the `main` function. | |||||||
|  |  | ||||||
|   initOptions = function(ar) { |   initOptions = function(ar) { | ||||||
|     oVerb; |     oVerb; | ||||||
|     var args, cleanArgs, inf, isDebug, isMono, isSilent, oJSON, oVerb, optStr, optsIdx, verb, vidx; |     var args, cleanArgs, inf, isAssert, isDebug, isMono, isSilent, oJSON, oVerb, optStr, optsIdx, verb, vidx; | ||||||
|     verb = ''; |     verb = ''; | ||||||
|     args = ar.slice(); |     args = ar.slice(); | ||||||
|     cleanArgs = args.slice(2); |     cleanArgs = args.slice(2); | ||||||
| @@ -177,6 +184,9 @@ Definition of the `main` function. | |||||||
|     isSilent = _.some(args, function(v) { |     isSilent = _.some(args, function(v) { | ||||||
|       return v === '-s' || v === '--silent'; |       return v === '-s' || v === '--silent'; | ||||||
|     }); |     }); | ||||||
|  |     isAssert = _.some(args, function(v) { | ||||||
|  |       return v === '-a' || v === '--assert'; | ||||||
|  |     }); | ||||||
|     isMono = _.some(args, function(v) { |     isMono = _.some(args, function(v) { | ||||||
|       return v === '--no-color'; |       return v === '--no-color'; | ||||||
|     }); |     }); | ||||||
| @@ -184,6 +194,7 @@ Definition of the `main` function. | |||||||
|       color: !isMono, |       color: !isMono, | ||||||
|       debug: isDebug, |       debug: isDebug, | ||||||
|       silent: isSilent, |       silent: isSilent, | ||||||
|  |       assert: isAssert, | ||||||
|       orgVerb: oVerb, |       orgVerb: oVerb, | ||||||
|       verb: verb, |       verb: verb, | ||||||
|       json: oJSON, |       json: oJSON, | ||||||
| @@ -195,23 +206,43 @@ Definition of the `main` function. | |||||||
|   /* Invoke a HackMyResume verb. */ |   /* Invoke a HackMyResume verb. */ | ||||||
|  |  | ||||||
|   execute = function(src, dst, opts, log) { |   execute = function(src, dst, opts, log) { | ||||||
|     var hand, v; |     var prom, v; | ||||||
|     loadOptions.call(this, opts, this.parent.jsonArgs); |  | ||||||
|     hand = require('./error'); |  | ||||||
|     hand.init(_opts.debug, _opts.assert, _opts.silent); |  | ||||||
|     v = new HMR.verbs[this.name()](); |     v = new HMR.verbs[this.name()](); | ||||||
|  |     loadOptions.call(this, opts, this.parent.jsonArgs); | ||||||
|     _opts.errHandler = v; |     _opts.errHandler = v; | ||||||
|     _out.init(_opts); |     _out.init(_opts); | ||||||
|     v.on('hmr:status', function() { |     v.on('hmr:status', function() { | ||||||
|       return _out["do"].apply(_out, arguments); |       return _out["do"].apply(_out, arguments); | ||||||
|     }); |     }); | ||||||
|     v.on('hmr:error', function() { |     v.on('hmr:error', function() { | ||||||
|       return hand.err.apply(hand, arguments); |       return _err.err.apply(_err, arguments); | ||||||
|     }); |     }); | ||||||
|     v.invoke.call(v, src, dst, _opts, log); |     prom = v.invoke.call(v, src, dst, _opts, log); | ||||||
|     if (v.errorCode) { |     prom.then(executeSuccess, executeFail); | ||||||
|       return process.exit(v.errorCode); |   }; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   /* Success handler for verb invocations. Calls process.exit by default */ | ||||||
|  |  | ||||||
|  |   executeSuccess = function(obj) {}; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   /* Failure handler for verb invocations. Calls process.exit by default */ | ||||||
|  |  | ||||||
|  |   executeFail = function(err) { | ||||||
|  |     var finalErrorCode, msgs; | ||||||
|  |     finalErrorCode = -1; | ||||||
|  |     if (err) { | ||||||
|  |       finalErrorCode = err.fluenterror ? err.fluenterror : err; | ||||||
|     } |     } | ||||||
|  |     if (_opts.debug) { | ||||||
|  |       msgs = require('./msg').errors; | ||||||
|  |       logMsg(printf(M2C(msgs.exiting.msg, 'cyan'), finalErrorCode)); | ||||||
|  |       if (err.stack) { | ||||||
|  |         logMsg(err.stack); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     _exitCallback(finalErrorCode); | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -281,3 +312,5 @@ Definition of the `main` function. | |||||||
|   }; |   }; | ||||||
|  |  | ||||||
| }).call(this); | }).call(this); | ||||||
|  |  | ||||||
|  | //# sourceMappingURL=main.js.map | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								dist/cli/msg.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								dist/cli/msg.js
									
									
									
									
										vendored
									
									
								
							| @@ -15,3 +15,5 @@ Message-handling routines for HackMyResume. | |||||||
|   module.exports = YAML.load(PATH.join(__dirname, 'msg.yml')); |   module.exports = YAML.load(PATH.join(__dirname, 'msg.yml')); | ||||||
|  |  | ||||||
| }).call(this); | }).call(this); | ||||||
|  |  | ||||||
|  | //# sourceMappingURL=msg.js.map | ||||||
|   | |||||||
							
								
								
									
										15
									
								
								dist/cli/msg.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										15
									
								
								dist/cli/msg.yml
									
									
									
									
										vendored
									
									
								
							| @@ -3,6 +3,8 @@ events: | |||||||
|     msg: Invoking **%s** command. |     msg: Invoking **%s** command. | ||||||
|   beforeCreate: |   beforeCreate: | ||||||
|     msg: Creating new **%s** resume: **%s** |     msg: Creating new **%s** resume: **%s** | ||||||
|  |   afterCreate: | ||||||
|  |     msg: Creating new **%s** resume: **%s** | ||||||
|   afterRead: |   afterRead: | ||||||
|     msg: Reading **%s** resume: **%s** |     msg: Reading **%s** resume: **%s** | ||||||
|   beforeTheme: |   beforeTheme: | ||||||
| @@ -41,6 +43,8 @@ events: | |||||||
|       - "VALID!" |       - "VALID!" | ||||||
|       - "INVALID" |       - "INVALID" | ||||||
|       - "BROKEN" |       - "BROKEN" | ||||||
|  |       - "MISSING" | ||||||
|  |       - "ERROR" | ||||||
|   beforePeek: |   beforePeek: | ||||||
|     msg: |     msg: | ||||||
|       - Peeking at **%s** in **%s** |       - Peeking at **%s** in **%s** | ||||||
| @@ -79,7 +83,10 @@ errors: | |||||||
|   readError: |   readError: | ||||||
|     msg: Reading **???** resume: **%s** |     msg: Reading **???** resume: **%s** | ||||||
|   parseError: |   parseError: | ||||||
|     msg: Invalid or corrupt JSON on line %s column %s. |     msg: | ||||||
|  |       - Invalid or corrupt JSON on line %s column %s. | ||||||
|  |       - Invalid or corrupt JSON on line %s. | ||||||
|  |       - Invalid or corrupt JSON. | ||||||
|   invalidHelperUse: |   invalidHelperUse: | ||||||
|     msg: "**Warning**: Incorrect use of the **%s** theme helper." |     msg: "**Warning**: Incorrect use of the **%s** theme helper." | ||||||
|   fileSaveError: |   fileSaveError: | ||||||
| @@ -96,3 +103,9 @@ errors: | |||||||
|     msg: "Invalid number of parameters. Expected: **%s**." |     msg: "Invalid number of parameters. Expected: **%s**." | ||||||
|   missingParam: |   missingParam: | ||||||
|     msg: The '**%s**' parameter was needed but not supplied. |     msg: The '**%s**' parameter was needed but not supplied. | ||||||
|  |   createError: | ||||||
|  |     msg: Failed to create **'%s'**. | ||||||
|  |   exiting: | ||||||
|  |     msg: Exiting with status code **%s**. | ||||||
|  |   validateError: | ||||||
|  |     msg: "An error occurred during validation:\n%s" | ||||||
|   | |||||||
							
								
								
									
										84
									
								
								dist/cli/out.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										84
									
								
								dist/cli/out.js
									
									
									
									
										vendored
									
									
								
							| @@ -6,17 +6,15 @@ Output routines for HackMyResume. | |||||||
|  */ |  */ | ||||||
|  |  | ||||||
| (function() { | (function() { | ||||||
|   var Class, EXTEND, FS, HANDLEBARS, HME, LO, M2C, OutputHandler, PATH, YAML, _, chalk, dbgStyle, pad, printf; |   var EXTEND, FS, HANDLEBARS, HME, LO, M2C, OutputHandler, PATH, YAML, _, chalk, dbgStyle, pad, printf; | ||||||
|  |  | ||||||
|   chalk = require('chalk'); |   chalk = require('chalk'); | ||||||
|  |  | ||||||
|   HME = require('hackmycore/dist/core/event-codes'); |   HME = require('../core/event-codes'); | ||||||
|  |  | ||||||
|   _ = require('underscore'); |   _ = require('underscore'); | ||||||
|  |  | ||||||
|   Class = require('hackmycore/dist/utils/class.js'); |   M2C = require('../utils/md2chalk.js'); | ||||||
|  |  | ||||||
|   M2C = require('hackmycore/dist/utils/md2chalk.js'); |  | ||||||
|  |  | ||||||
|   PATH = require('path'); |   PATH = require('path'); | ||||||
|  |  | ||||||
| @@ -39,20 +37,27 @@ Output routines for HackMyResume. | |||||||
|  |  | ||||||
|   /** A stateful output module. All HMR console output handled here. */ |   /** A stateful output module. All HMR console output handled here. */ | ||||||
|  |  | ||||||
|   OutputHandler = module.exports = Class.extend({ |   module.exports = OutputHandler = (function() { | ||||||
|     init: function(opts) { |     function OutputHandler(opts) { | ||||||
|  |       this.init(opts); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     OutputHandler.prototype.init = function(opts) { | ||||||
|       this.opts = EXTEND(true, this.opts || {}, opts); |       this.opts = EXTEND(true, this.opts || {}, opts); | ||||||
|       return this.msgs = YAML.load(PATH.join(__dirname, 'msg.yml')).events; |       this.msgs = YAML.load(PATH.join(__dirname, 'msg.yml')).events; | ||||||
|     }, |     }; | ||||||
|     log: function(msg) { |  | ||||||
|  |     OutputHandler.prototype.log = function(msg) { | ||||||
|       var finished; |       var finished; | ||||||
|       msg = msg || ''; |       msg = msg || ''; | ||||||
|       printf = require('printf'); |       printf = require('printf'); | ||||||
|       finished = printf.apply(printf, arguments); |       finished = printf.apply(printf, arguments); | ||||||
|       return this.opts.silent || console.log(finished); |       return this.opts.silent || console.log(finished); | ||||||
|     }, |     }; | ||||||
|     "do": function(evt) { |  | ||||||
|       var L, WRAP, info, msg, numFormats, output, rawTpl, sty, style, suffix, template, that, themeName, tot; |     OutputHandler.prototype["do"] = function(evt) { | ||||||
|  |       var L, WRAP, adj, info, msg, msgs, numFormats, output, rawTpl, sty, style, suffix, template, that, themeName, tot; | ||||||
|       that = this; |       that = this; | ||||||
|       L = function() { |       L = function() { | ||||||
|         return that.log.apply(that, arguments); |         return that.log.apply(that, arguments); | ||||||
| @@ -60,8 +65,8 @@ Output routines for HackMyResume. | |||||||
|       switch (evt.sub) { |       switch (evt.sub) { | ||||||
|         case HME.begin: |         case HME.begin: | ||||||
|           return this.opts.debug && L(M2C(this.msgs.begin.msg, dbgStyle), evt.cmd.toUpperCase()); |           return this.opts.debug && L(M2C(this.msgs.begin.msg, dbgStyle), evt.cmd.toUpperCase()); | ||||||
|         case HME.beforeCreate: |         case HME.afterCreate: | ||||||
|           L(M2C(this.msgs.beforeCreate.msg, 'green'), evt.fmt, evt.file); |           L(M2C(this.msgs.beforeCreate.msg, evt.isError ? 'red' : 'green'), evt.fmt, evt.file); | ||||||
|           break; |           break; | ||||||
|         case HME.beforeTheme: |         case HME.beforeTheme: | ||||||
|           return this.opts.debug && L(M2C(this.msgs.beforeTheme.msg, dbgStyle), evt.theme.toUpperCase()); |           return this.opts.debug && L(M2C(this.msgs.beforeTheme.msg, dbgStyle), evt.theme.toUpperCase()); | ||||||
| @@ -110,7 +115,7 @@ Output routines for HackMyResume. | |||||||
|         case HME.afterAnalyze: |         case HME.afterAnalyze: | ||||||
|           info = evt.info; |           info = evt.info; | ||||||
|           rawTpl = FS.readFileSync(PATH.join(__dirname, 'analyze.hbs'), 'utf8'); |           rawTpl = FS.readFileSync(PATH.join(__dirname, 'analyze.hbs'), 'utf8'); | ||||||
|           HANDLEBARS.registerHelper(require('hackmycore/dist/helpers/console-helpers')); |           HANDLEBARS.registerHelper(require('../helpers/console-helpers')); | ||||||
|           template = HANDLEBARS.compile(rawTpl, { |           template = HANDLEBARS.compile(rawTpl, { | ||||||
|             strict: false, |             strict: false, | ||||||
|             assumeObjects: false |             assumeObjects: false | ||||||
| @@ -127,11 +132,35 @@ Output routines for HackMyResume. | |||||||
|         case HME.afterInlineConvert: |         case HME.afterInlineConvert: | ||||||
|           return L(M2C(this.msgs.afterInlineConvert.msg, 'gray', 'white.dim'), evt.file, evt.fmt); |           return L(M2C(this.msgs.afterInlineConvert.msg, 'gray', 'white.dim'), evt.file, evt.fmt); | ||||||
|         case HME.afterValidate: |         case HME.afterValidate: | ||||||
|           style = evt.isValid ? 'green' : 'yellow'; |           style = 'red'; | ||||||
|           L(M2C(this.msgs.afterValidate.msg[0], 'white') + chalk[style].bold(evt.isValid ? this.msgs.afterValidate.msg[1] : this.msgs.afterValidate.msg[2]), evt.file, evt.fmt); |           adj = ''; | ||||||
|           if (evt.errors) { |           msgs = this.msgs.afterValidate.msg; | ||||||
|             return _.each(evt.errors, function(err, idx) { |           switch (evt.status) { | ||||||
|               return L(chalk.yellow.bold('--> ') + chalk.yellow(err.field.replace('data.', 'resume.').toUpperCase() + ' ' + err.message)); |             case 'valid': | ||||||
|  |               style = 'green'; | ||||||
|  |               adj = msgs[1]; | ||||||
|  |               break; | ||||||
|  |             case 'invalid': | ||||||
|  |               style = 'yellow'; | ||||||
|  |               adj = msgs[2]; | ||||||
|  |               break; | ||||||
|  |             case 'broken': | ||||||
|  |               style = 'red'; | ||||||
|  |               adj = msgs[3]; | ||||||
|  |               break; | ||||||
|  |             case 'missing': | ||||||
|  |               style = 'red'; | ||||||
|  |               adj = msgs[4]; | ||||||
|  |               break; | ||||||
|  |             case 'unknown': | ||||||
|  |               style = 'red'; | ||||||
|  |               adj = msgs[5]; | ||||||
|  |           } | ||||||
|  |           evt.schema = evt.schema.replace('jars', 'JSON Resume').toUpperCase(); | ||||||
|  |           L(M2C(msgs[0], 'white') + chalk[style].bold(adj), evt.file, evt.schema); | ||||||
|  |           if (evt.violations) { | ||||||
|  |             _.each(evt.violations, function(err, idx) { | ||||||
|  |               L(chalk.yellow.bold('--> ') + chalk.yellow(err.field.replace('data.', 'resume.').toUpperCase() + ' ' + err.message)); | ||||||
|             }, this); |             }, this); | ||||||
|           } |           } | ||||||
|           break; |           break; | ||||||
| @@ -142,16 +171,23 @@ Output routines for HackMyResume. | |||||||
|           } else { |           } else { | ||||||
|             L(M2C(this.msgs.beforePeek.msg[1], sty), evt.file); |             L(M2C(this.msgs.beforePeek.msg[1], sty), evt.file); | ||||||
|           } |           } | ||||||
|           if (evt.target !== void 0) { |           if (evt.target !== void 0 && !evt.error) { | ||||||
|             return console.dir(evt.target, { |             return console.dir(evt.target, { | ||||||
|               depth: null, |               depth: null, | ||||||
|               colors: true |               colors: true | ||||||
|             }); |             }); | ||||||
|           } else if (!evt.error) { |           } else if (!evt.error) { | ||||||
|             return L(M2C(this.msgs.afterPeek.msg, 'yellow'), evt.requested, evt.file); |             return L(M2C(this.msgs.afterPeek.msg, 'yellow'), evt.requested, evt.file); | ||||||
|  |           } else if (evt.error) { | ||||||
|  |             return L(chalk.red(evt.error.inner.inner)); | ||||||
|           } |           } | ||||||
|       } |       } | ||||||
|     } |     }; | ||||||
|   }); |  | ||||||
|  |     return OutputHandler; | ||||||
|  |  | ||||||
|  |   })(); | ||||||
|  |  | ||||||
| }).call(this); | }).call(this); | ||||||
|  |  | ||||||
|  | //# sourceMappingURL=out.js.map | ||||||
|   | |||||||
							
								
								
									
										73
									
								
								dist/core/abstract-resume.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								dist/core/abstract-resume.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,73 @@ | |||||||
|  |  | ||||||
|  | /** | ||||||
|  | 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); | ||||||
|  |  | ||||||
|  | //# sourceMappingURL=abstract-resume.js.map | ||||||
							
								
								
									
										62
									
								
								dist/core/default-formats.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								dist/core/default-formats.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,62 @@ | |||||||
|  |  | ||||||
|  | /* | ||||||
|  | Event code definitions. | ||||||
|  | @module core/default-formats | ||||||
|  | @license MIT. See LICENSE.md for details. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /** Supported resume formats. */ | ||||||
|  |  | ||||||
|  | (function() { | ||||||
|  |   module.exports = [ | ||||||
|  |     { | ||||||
|  |       name: 'html', | ||||||
|  |       ext: 'html', | ||||||
|  |       gen: new (require('../generators/html-generator'))() | ||||||
|  |     }, { | ||||||
|  |       name: 'txt', | ||||||
|  |       ext: 'txt', | ||||||
|  |       gen: new (require('../generators/text-generator'))() | ||||||
|  |     }, { | ||||||
|  |       name: 'doc', | ||||||
|  |       ext: 'doc', | ||||||
|  |       fmt: 'xml', | ||||||
|  |       gen: new (require('../generators/word-generator'))() | ||||||
|  |     }, { | ||||||
|  |       name: 'pdf', | ||||||
|  |       ext: 'pdf', | ||||||
|  |       fmt: 'html', | ||||||
|  |       is: false, | ||||||
|  |       gen: new (require('../generators/html-pdf-cli-generator'))() | ||||||
|  |     }, { | ||||||
|  |       name: 'png', | ||||||
|  |       ext: 'png', | ||||||
|  |       fmt: 'html', | ||||||
|  |       is: false, | ||||||
|  |       gen: new (require('../generators/html-png-generator'))() | ||||||
|  |     }, { | ||||||
|  |       name: 'md', | ||||||
|  |       ext: 'md', | ||||||
|  |       fmt: 'txt', | ||||||
|  |       gen: new (require('../generators/markdown-generator'))() | ||||||
|  |     }, { | ||||||
|  |       name: 'json', | ||||||
|  |       ext: 'json', | ||||||
|  |       gen: new (require('../generators/json-generator'))() | ||||||
|  |     }, { | ||||||
|  |       name: 'yml', | ||||||
|  |       ext: 'yml', | ||||||
|  |       fmt: 'yml', | ||||||
|  |       gen: new (require('../generators/json-yaml-generator'))() | ||||||
|  |     }, { | ||||||
|  |       name: 'latex', | ||||||
|  |       ext: 'tex', | ||||||
|  |       fmt: 'latex', | ||||||
|  |       gen: new (require('../generators/latex-generator'))() | ||||||
|  |     } | ||||||
|  |   ]; | ||||||
|  |  | ||||||
|  | }).call(this); | ||||||
|  |  | ||||||
|  | //# sourceMappingURL=default-formats.js.map | ||||||
							
								
								
									
										20
									
								
								dist/core/default-options.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								dist/core/default-options.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | |||||||
|  |  | ||||||
|  | /* | ||||||
|  | Event code definitions. | ||||||
|  | @module core/default-options | ||||||
|  | @license MIT. See LICENSE.md for details. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | (function() { | ||||||
|  |   module.exports = { | ||||||
|  |     theme: 'modern', | ||||||
|  |     prettify: { | ||||||
|  |       indent_size: 2, | ||||||
|  |       unformatted: ['em', 'strong'], | ||||||
|  |       max_char: 80 | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  | }).call(this); | ||||||
|  |  | ||||||
|  | //# sourceMappingURL=default-options.js.map | ||||||
							
								
								
									
										77
									
								
								dist/core/empty-jrs.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								dist/core/empty-jrs.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,77 @@ | |||||||
|  | { | ||||||
|  |   "basics": { | ||||||
|  |     "name": "", | ||||||
|  |     "label": "", | ||||||
|  |     "picture": "", | ||||||
|  |     "email": "", | ||||||
|  |     "phone": "", | ||||||
|  |     "degree": "", | ||||||
|  |     "website": "", | ||||||
|  |     "summary": "", | ||||||
|  |     "location": { | ||||||
|  |       "address": "", | ||||||
|  |       "postalCode": "", | ||||||
|  |       "city": "", | ||||||
|  |       "countryCode": "", | ||||||
|  |       "region": "" | ||||||
|  |     }, | ||||||
|  |     "profiles": [{ | ||||||
|  |       "network": "", | ||||||
|  |       "username": "", | ||||||
|  |       "url": "" | ||||||
|  |     }] | ||||||
|  |   }, | ||||||
|  |  | ||||||
|  |   "work": [{ | ||||||
|  |     "company": "", | ||||||
|  |     "position": "", | ||||||
|  |     "website": "", | ||||||
|  |     "startDate": "", | ||||||
|  |     "endDate": "", | ||||||
|  |     "summary": "", | ||||||
|  |     "highlights": [ | ||||||
|  |       "" | ||||||
|  |     ] | ||||||
|  |   }], | ||||||
|  |  | ||||||
|  |   "awards": [{ | ||||||
|  |     "title": "", | ||||||
|  |     "date": "", | ||||||
|  |     "awarder": "", | ||||||
|  |     "summary": "" | ||||||
|  |   }], | ||||||
|  |  | ||||||
|  |   "education": [{ | ||||||
|  |     "institution": "", | ||||||
|  |     "area": "", | ||||||
|  |     "studyType": "", | ||||||
|  |     "startDate": "", | ||||||
|  |     "endDate": "", | ||||||
|  |     "gpa": "", | ||||||
|  |     "courses": [ "" ] | ||||||
|  |   }], | ||||||
|  |  | ||||||
|  |   "publications": [{ | ||||||
|  |     "name": "", | ||||||
|  |     "publisher": "", | ||||||
|  |     "releaseDate": "", | ||||||
|  |     "website": "", | ||||||
|  |     "summary": "" | ||||||
|  |   }], | ||||||
|  |  | ||||||
|  |   "volunteer": [{ | ||||||
|  |     "organization": "", | ||||||
|  |     "position": "", | ||||||
|  |     "website": "", | ||||||
|  |     "startDate": "", | ||||||
|  |     "endDate": "", | ||||||
|  |     "summary": "", | ||||||
|  |     "highlights": [ "" ] | ||||||
|  |   }], | ||||||
|  |  | ||||||
|  |   "skills": [{ | ||||||
|  |       "name": "", | ||||||
|  |       "level": "", | ||||||
|  |       "keywords": [""] | ||||||
|  |   }] | ||||||
|  | } | ||||||
							
								
								
									
										44
									
								
								dist/core/event-codes.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								dist/core/event-codes.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | |||||||
|  |  | ||||||
|  | /* | ||||||
|  | Event code definitions. | ||||||
|  | @module core/event-codes | ||||||
|  | @license MIT. See LICENSE.md for details. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | (function() { | ||||||
|  |   module.exports = { | ||||||
|  |     error: -1, | ||||||
|  |     success: 0, | ||||||
|  |     begin: 1, | ||||||
|  |     end: 2, | ||||||
|  |     beforeRead: 3, | ||||||
|  |     afterRead: 4, | ||||||
|  |     beforeCreate: 5, | ||||||
|  |     afterCreate: 6, | ||||||
|  |     beforeTheme: 7, | ||||||
|  |     afterTheme: 8, | ||||||
|  |     beforeMerge: 9, | ||||||
|  |     afterMerge: 10, | ||||||
|  |     beforeGenerate: 11, | ||||||
|  |     afterGenerate: 12, | ||||||
|  |     beforeAnalyze: 13, | ||||||
|  |     afterAnalyze: 14, | ||||||
|  |     beforeConvert: 15, | ||||||
|  |     afterConvert: 16, | ||||||
|  |     verifyOutputs: 17, | ||||||
|  |     beforeParse: 18, | ||||||
|  |     afterParse: 19, | ||||||
|  |     beforePeek: 20, | ||||||
|  |     afterPeek: 21, | ||||||
|  |     beforeInlineConvert: 22, | ||||||
|  |     afterInlineConvert: 23, | ||||||
|  |     beforeValidate: 24, | ||||||
|  |     afterValidate: 25, | ||||||
|  |     beforeWrite: 26, | ||||||
|  |     afterWrite: 27, | ||||||
|  |     applyTheme: 28 | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  | }).call(this); | ||||||
|  |  | ||||||
|  | //# sourceMappingURL=event-codes.js.map | ||||||
							
								
								
									
										107
									
								
								dist/core/fluent-date.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								dist/core/fluent-date.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,107 @@ | |||||||
|  |  | ||||||
|  | /** | ||||||
|  | The HackMyResume date representation. | ||||||
|  | @license MIT. See LICENSE.md for details. | ||||||
|  | @module core/fluent-date | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | (function() { | ||||||
|  |   var FluentDate, abbr, moment, months; | ||||||
|  |  | ||||||
|  |   moment = require('moment'); | ||||||
|  |  | ||||||
|  |   require('../utils/string'); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |   Create a FluentDate from a string or Moment date object. There are a few date | ||||||
|  |   formats to be aware of here. | ||||||
|  |   1. The words "Present" and "Now", referring to the current date | ||||||
|  |   2. The default "YYYY-MM-DD" format used in JSON Resume ("2015-02-10") | ||||||
|  |   3. Year-and-month only ("2015-04") | ||||||
|  |   4. Year-only "YYYY" ("2015") | ||||||
|  |   5. The friendly HackMyResume "mmm YYYY" format ("Mar 2015" or "Dec 2008") | ||||||
|  |   6. Empty dates ("", " ") | ||||||
|  |   7. Any other date format that Moment.js can parse from | ||||||
|  |   Note: Moment can transparently parse all or most of these, without requiring us | ||||||
|  |   to specify a date format...but for maximum parsing safety and to avoid Moment | ||||||
|  |   deprecation warnings, it's recommended to either a) explicitly specify the date | ||||||
|  |   format or b) use an ISO format. For clarity, we handle these cases explicitly. | ||||||
|  |   @class FluentDate | ||||||
|  |    */ | ||||||
|  |  | ||||||
|  |   FluentDate = (function() { | ||||||
|  |     function FluentDate(dt) { | ||||||
|  |       this.rep = this.fmt(dt); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     FluentDate.isCurrent = function(dt) { | ||||||
|  |       return !dt || (String.is(dt) && /^(present|now|current)$/.test(dt)); | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     return FluentDate; | ||||||
|  |  | ||||||
|  |   })(); | ||||||
|  |  | ||||||
|  |   months = {}; | ||||||
|  |  | ||||||
|  |   abbr = {}; | ||||||
|  |  | ||||||
|  |   moment.months().forEach(function(m, idx) { | ||||||
|  |     return months[m.toLowerCase()] = idx + 1; | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   moment.monthsShort().forEach(function(m, idx) { | ||||||
|  |     return abbr[m.toLowerCase()] = idx + 1; | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   abbr.sept = 9; | ||||||
|  |  | ||||||
|  |   module.exports = FluentDate; | ||||||
|  |  | ||||||
|  |   FluentDate.fmt = function(dt, throws) { | ||||||
|  |     var month, mt, parts, ref, temp; | ||||||
|  |     throws = (throws === void 0 || throws === null) || throws; | ||||||
|  |     if (typeof dt === 'string' || dt instanceof String) { | ||||||
|  |       dt = dt.toLowerCase().trim(); | ||||||
|  |       if (/^(present|now|current)$/.test(dt)) { | ||||||
|  |         return moment(); | ||||||
|  |       } else if (/^\D+\s+\d{4}$/.test(dt)) { | ||||||
|  |         parts = dt.split(' '); | ||||||
|  |         month = months[parts[0]] || abbr[parts[0]]; | ||||||
|  |         temp = parts[1] + '-' + ((ref = month < 10) != null ? ref : '0' + { | ||||||
|  |           month: month.toString() | ||||||
|  |         }); | ||||||
|  |         return moment(temp, 'YYYY-MM'); | ||||||
|  |       } else if (/^\d{4}-\d{1,2}$/.test(dt)) { | ||||||
|  |         return moment(dt, 'YYYY-MM'); | ||||||
|  |       } else if (/^\s*\d{4}\s*$/.test(dt)) { | ||||||
|  |         return moment(dt, 'YYYY'); | ||||||
|  |       } else if (/^\s*$/.test(dt)) { | ||||||
|  |         return moment(); | ||||||
|  |       } else { | ||||||
|  |         mt = moment(dt); | ||||||
|  |         if (mt.isValid()) { | ||||||
|  |           return mt; | ||||||
|  |         } | ||||||
|  |         if (throws) { | ||||||
|  |           throw 'Invalid date format encountered.'; | ||||||
|  |         } | ||||||
|  |         return null; | ||||||
|  |       } | ||||||
|  |     } else { | ||||||
|  |       if (!dt) { | ||||||
|  |         return moment(); | ||||||
|  |       } else if (dt.isValid && dt.isValid()) { | ||||||
|  |         return dt; | ||||||
|  |       } | ||||||
|  |       if (throws) { | ||||||
|  |         throw 'Unknown date object encountered.'; | ||||||
|  |       } | ||||||
|  |       return null; | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  | }).call(this); | ||||||
|  |  | ||||||
|  | //# sourceMappingURL=fluent-date.js.map | ||||||
							
								
								
									
										510
									
								
								dist/core/fresh-resume.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										510
									
								
								dist/core/fresh-resume.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,510 @@ | |||||||
|  |  | ||||||
|  | /** | ||||||
|  | Definition of the FRESHResume class. | ||||||
|  | @license MIT. See LICENSE.md for details. | ||||||
|  | @module core/fresh-resume | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | (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; | ||||||
|  |  | ||||||
|  |   FS = require('fs'); | ||||||
|  |  | ||||||
|  |   extend = require('extend'); | ||||||
|  |  | ||||||
|  |   validator = require('is-my-json-valid'); | ||||||
|  |  | ||||||
|  |   _ = require('underscore'); | ||||||
|  |  | ||||||
|  |   __ = require('lodash'); | ||||||
|  |  | ||||||
|  |   PATH = require('path'); | ||||||
|  |  | ||||||
|  |   moment = require('moment'); | ||||||
|  |  | ||||||
|  |   XML = require('xml-escape'); | ||||||
|  |  | ||||||
|  |   MD = require('marked'); | ||||||
|  |  | ||||||
|  |   CONVERTER = require('fresh-jrs-converter'); | ||||||
|  |  | ||||||
|  |   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 | ||||||
|  |   object is an instantiation of that JSON decorated with utility methods. | ||||||
|  |   @constructor | ||||||
|  |    */ | ||||||
|  |  | ||||||
|  |   FreshResume = (function(superClass) { | ||||||
|  |     extend1(FreshResume, superClass); | ||||||
|  |  | ||||||
|  |     function FreshResume() { | ||||||
|  |       return FreshResume.__super__.constructor.apply(this, arguments); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     /** Initialize the the FreshResume from JSON string data. */ | ||||||
|  |  | ||||||
|  |     FreshResume.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 FreshResume from JSON. | ||||||
|  |     Open and parse the specified FRESH 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. | ||||||
|  |     } | ||||||
|  |      */ | ||||||
|  |  | ||||||
|  |     FreshResume.prototype.parseJSON = function(rep, opts) { | ||||||
|  |       var ignoreList, ref, scrubbed, that, traverse; | ||||||
|  |       that = this; | ||||||
|  |       traverse = require('traverse'); | ||||||
|  |       ignoreList = []; | ||||||
|  |       scrubbed = traverse(rep).map(function(x) { | ||||||
|  |         if (!this.isLeaf && this.node.ignore) { | ||||||
|  |           if (this.node.ignore === true || this.node.ignore === 'true') { | ||||||
|  |             ignoreList.push(this.node); | ||||||
|  |             return this.remove(); | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       }); | ||||||
|  |       extend(true, this, scrubbed); | ||||||
|  |       if (!((ref = this.imp) != null ? ref.processed : void 0)) { | ||||||
|  |         opts = opts || {}; | ||||||
|  |         if (opts.imp === void 0 || opts.imp) { | ||||||
|  |           this.imp = this.imp || {}; | ||||||
|  |           this.imp.title = (opts.title || this.imp.title) || this.name; | ||||||
|  |           if (!this.imp.raw) { | ||||||
|  |             this.imp.raw = JSON.stringify(rep); | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |         this.imp.processed = true; | ||||||
|  |         (opts.date === void 0 || opts.date) && _parseDates.call(this); | ||||||
|  |         (opts.sort === void 0 || opts.sort) && this.sort(); | ||||||
|  |         (opts.compute === void 0 || opts.compute) && (this.computed = { | ||||||
|  |           numYears: this.duration(), | ||||||
|  |           keywords: this.keywords() | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  |       return this; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     /** Save the sheet to disk (for environments that have disk access). */ | ||||||
|  |  | ||||||
|  |     FreshResume.prototype.save = function(filename) { | ||||||
|  |       this.imp.file = filename || this.imp.file; | ||||||
|  |       FS.writeFileSync(this.imp.file, this.stringify(), 'utf8'); | ||||||
|  |       return this; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |     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') { | ||||||
|  |         this.imp.file = filename || this.imp.file; | ||||||
|  |         FS.writeFileSync(this.imp.file, this.stringify(), 'utf8'); | ||||||
|  |       } else { | ||||||
|  |         newRep = CONVERTER.toJRS(this); | ||||||
|  |         FS.writeFileSync(filename, JRSResume.stringify(newRep), 'utf8'); | ||||||
|  |       } | ||||||
|  |       return this; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |     Duplicate this FreshResume instance. | ||||||
|  |     This method first extend()s this object onto an empty, creating a deep copy, | ||||||
|  |     and then passes the result into a new FreshResume instance via .parseJSON. | ||||||
|  |     We do it this way to create a true clone of the object without re-running any | ||||||
|  |     of the associated processing. | ||||||
|  |      */ | ||||||
|  |  | ||||||
|  |     FreshResume.prototype.dupe = function() { | ||||||
|  |       var jso, rnew; | ||||||
|  |       jso = extend(true, {}, this); | ||||||
|  |       rnew = new FreshResume(); | ||||||
|  |       rnew.parseJSON(jso, {}); | ||||||
|  |       return rnew; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |     Convert this object to a JSON string, sanitizing meta-properties along the | ||||||
|  |     way. | ||||||
|  |      */ | ||||||
|  |  | ||||||
|  |     FreshResume.prototype.stringify = function() { | ||||||
|  |       return FreshResume.stringify(this); | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |     Create a copy of this resume in which all string fields have been run through | ||||||
|  |     a transformation function (such as a Markdown filter or XML encoder). | ||||||
|  |     TODO: Move this out of FRESHResume. | ||||||
|  |      */ | ||||||
|  |  | ||||||
|  |     FreshResume.prototype.transformStrings = function(filt, transformer) { | ||||||
|  |       var ret, trx; | ||||||
|  |       ret = this.dupe(); | ||||||
|  |       trx = require('../utils/string-transformer'); | ||||||
|  |       return trx(ret, filt, transformer); | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |     Create a copy of this resume in which all fields have been interpreted as | ||||||
|  |     Markdown. | ||||||
|  |      */ | ||||||
|  |  | ||||||
|  |     FreshResume.prototype.markdownify = function() { | ||||||
|  |       var MDIN, trx; | ||||||
|  |       MDIN = function(txt) { | ||||||
|  |         return MD(txt || '').replace(/^\s*<p>|<\/p>\s*$/gi, ''); | ||||||
|  |       }; | ||||||
|  |       trx = function(key, val) { | ||||||
|  |         if (key === 'summary') { | ||||||
|  |           return MD(val); | ||||||
|  |         } | ||||||
|  |         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() { | ||||||
|  |       var trx; | ||||||
|  |       trx = function(key, val) { | ||||||
|  |         return XML(val); | ||||||
|  |       }; | ||||||
|  |       return this.transformStrings([], trx); | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     /** Return the resume format. */ | ||||||
|  |  | ||||||
|  |     FreshResume.prototype.format = function() { | ||||||
|  |       return 'FRESH'; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |     Return internal metadata. Create if it doesn't exist. | ||||||
|  |      */ | ||||||
|  |  | ||||||
|  |     FreshResume.prototype.i = function() { | ||||||
|  |       return this.imp = this.imp || {}; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     /** Return a unique list of all keywords across all skills. */ | ||||||
|  |  | ||||||
|  |     FreshResume.prototype.keywords = function() { | ||||||
|  |       var flatSkills; | ||||||
|  |       flatSkills = []; | ||||||
|  |       if (this.skills) { | ||||||
|  |         if (this.skills.sets) { | ||||||
|  |           flatSkills = this.skills.sets.map(function(sk) { | ||||||
|  |             return sk.skills; | ||||||
|  |           }).reduce(function(a, b) { | ||||||
|  |             return a.concat(b); | ||||||
|  |           }); | ||||||
|  |         } else if (this.skills.list) { | ||||||
|  |           flatSkills = flatSkills.concat(this.skills.list.map(function(sk) { | ||||||
|  |             return sk.name; | ||||||
|  |           })); | ||||||
|  |         } | ||||||
|  |         flatSkills = _.uniq(flatSkills); | ||||||
|  |       } | ||||||
|  |       return flatSkills; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |     Reset the sheet to an empty state. TODO: refactor/review | ||||||
|  |      */ | ||||||
|  |  | ||||||
|  |     FreshResume.prototype.clear = function(clearMeta) { | ||||||
|  |       clearMeta = ((clearMeta === void 0) && true) || clearMeta; | ||||||
|  |       if (clearMeta) { | ||||||
|  |         delete this.imp; | ||||||
|  |       } | ||||||
|  |       delete this.computed; | ||||||
|  |       delete this.employment; | ||||||
|  |       delete this.service; | ||||||
|  |       delete this.education; | ||||||
|  |       delete this.recognition; | ||||||
|  |       delete this.reading; | ||||||
|  |       delete this.writing; | ||||||
|  |       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) { | ||||||
|  |       if (!obj) { | ||||||
|  |         return 0; | ||||||
|  |       } | ||||||
|  |       if (obj.history) { | ||||||
|  |         return obj.history.length; | ||||||
|  |       } | ||||||
|  |       if (obj.sets) { | ||||||
|  |         return obj.sets.length; | ||||||
|  |       } | ||||||
|  |       return obj.length || 0; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     /** Add work experience to the sheet. */ | ||||||
|  |  | ||||||
|  |     FreshResume.prototype.add = function(moniker) { | ||||||
|  |       var defSheet, newObject; | ||||||
|  |       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) { | ||||||
|  |         this[moniker].history.push(newObject); | ||||||
|  |       } else if (moniker === 'skills') { | ||||||
|  |         this.skills.sets.push(newObject); | ||||||
|  |       } else { | ||||||
|  |         this[moniker].push(newObject); | ||||||
|  |       } | ||||||
|  |       return newObject; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |     Determine if the sheet includes a specific social profile (eg, GitHub). | ||||||
|  |      */ | ||||||
|  |  | ||||||
|  |     FreshResume.prototype.hasProfile = function(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) { | ||||||
|  |       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) { | ||||||
|  |       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) { | ||||||
|  |       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) { | ||||||
|  |       var ret, schemaObj, validate; | ||||||
|  |       schemaObj = require('fresca'); | ||||||
|  |       validator = require('is-my-json-valid'); | ||||||
|  |       validate = validator(schemaObj, { | ||||||
|  |         formats: { | ||||||
|  |           date: /^\d{4}(?:-(?:0[0-9]{1}|1[0-2]{1})(?:-[0-9]{2})?)?$/ | ||||||
|  |         } | ||||||
|  |       }); | ||||||
|  |       ret = validate(this); | ||||||
|  |       if (!ret) { | ||||||
|  |         this.imp = this.imp || {}; | ||||||
|  |         this.imp.validationErrors = validate.errors; | ||||||
|  |       } | ||||||
|  |       return ret; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     FreshResume.prototype.duration = function(unit) { | ||||||
|  |       return FreshResume.__super__.duration.call(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() { | ||||||
|  |       var byDateDesc, sortSection; | ||||||
|  |       byDateDesc = function(a, b) { | ||||||
|  |         if (a.safe.start.isBefore(b.safe.start)) { | ||||||
|  |           return 1; | ||||||
|  |         } else { | ||||||
|  |           if (a.safe.start.isAfter(b.safe.start)) { | ||||||
|  |             return -1; | ||||||
|  |           } else { | ||||||
|  |             return 0; | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       }; | ||||||
|  |       sortSection = function(key) { | ||||||
|  |         var ar, datedThings; | ||||||
|  |         ar = __.get(this, key); | ||||||
|  |         if (ar && ar.length) { | ||||||
|  |           datedThings = obj.filter(function(o) { | ||||||
|  |             return o.start; | ||||||
|  |           }); | ||||||
|  |           return datedThings.sort(byDateDesc); | ||||||
|  |         } | ||||||
|  |       }; | ||||||
|  |       sortSection('employment.history'); | ||||||
|  |       sortSection('education.history'); | ||||||
|  |       sortSection('service.history'); | ||||||
|  |       sortSection('projects'); | ||||||
|  |       return this.writing && this.writing.sort(function(a, b) { | ||||||
|  |         if (a.safe.date.isBefore(b.safe.date)) { | ||||||
|  |           return 1; | ||||||
|  |         } else { | ||||||
|  |           return (a.safe.date.isAfter(b.safe.date) && -1) || 0; | ||||||
|  |         } | ||||||
|  |       }); | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     return FreshResume; | ||||||
|  |  | ||||||
|  |   })(AbstractResume); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |   Get the default (starter) sheet. | ||||||
|  |    */ | ||||||
|  |  | ||||||
|  |   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) { | ||||||
|  |       var exKeys; | ||||||
|  |       exKeys = ['imp', 'warnings', 'computed', 'filt', 'ctrl', 'index', 'safe', 'result', 'isModified', 'htmlPreview', 'display_progress_bar']; | ||||||
|  |       if (_.some(exKeys, function(val) { | ||||||
|  |         return key.trim() === val; | ||||||
|  |       })) { | ||||||
|  |         return void 0; | ||||||
|  |       } else { | ||||||
|  |         return value; | ||||||
|  |       } | ||||||
|  |     }; | ||||||
|  |     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; | ||||||
|  |     replaceDatesInObject = function(obj) { | ||||||
|  |       if (!obj) { | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |       if (Object.prototype.toString.call(obj) === '[object Array]') { | ||||||
|  |         obj.forEach(function(elem) { | ||||||
|  |           return replaceDatesInObject(elem); | ||||||
|  |         }); | ||||||
|  |       } else if (typeof obj === 'object') { | ||||||
|  |         if (obj._isAMomentObject || obj.safe) { | ||||||
|  |           return; | ||||||
|  |         } | ||||||
|  |         Object.keys(obj).forEach(function(key) { | ||||||
|  |           return replaceDatesInObject(obj[key]); | ||||||
|  |         }); | ||||||
|  |         ['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) { | ||||||
|  |               obj.safe.end = _fmt('current'); | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  |     }; | ||||||
|  |     Object.keys(this).forEach(function(member) { | ||||||
|  |       replaceDatesInObject(that[member]); | ||||||
|  |     }); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   /** Export the Sheet function/ctor. */ | ||||||
|  |  | ||||||
|  |   module.exports = FreshResume; | ||||||
|  |  | ||||||
|  | }).call(this); | ||||||
|  |  | ||||||
|  | //# sourceMappingURL=fresh-resume.js.map | ||||||
							
								
								
									
										230
									
								
								dist/core/fresh-theme.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										230
									
								
								dist/core/fresh-theme.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,230 @@ | |||||||
|  |  | ||||||
|  | /** | ||||||
|  | Definition of the FRESHTheme class. | ||||||
|  | @module core/fresh-theme | ||||||
|  | @license MIT. See LICENSE.md for details. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | (function() { | ||||||
|  |   var EXTEND, FRESHTheme, FS, HMSTATUS, PATH, READFILES, _, _load, _loadOne, friendlyName, loadSafeJson, moment, parsePath, pathExists, validator; | ||||||
|  |  | ||||||
|  |   FS = require('fs'); | ||||||
|  |  | ||||||
|  |   validator = require('is-my-json-valid'); | ||||||
|  |  | ||||||
|  |   _ = require('underscore'); | ||||||
|  |  | ||||||
|  |   PATH = require('path'); | ||||||
|  |  | ||||||
|  |   parsePath = require('parse-filepath'); | ||||||
|  |  | ||||||
|  |   pathExists = require('path-exists').sync; | ||||||
|  |  | ||||||
|  |   EXTEND = require('extend'); | ||||||
|  |  | ||||||
|  |   HMSTATUS = require('./status-codes'); | ||||||
|  |  | ||||||
|  |   moment = require('moment'); | ||||||
|  |  | ||||||
|  |   loadSafeJson = require('../utils/safe-json-loader'); | ||||||
|  |  | ||||||
|  |   READFILES = require('recursive-readdir-sync'); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   /* A representation of a FRESH theme asset. | ||||||
|  |   @class FRESHTheme | ||||||
|  |    */ | ||||||
|  |  | ||||||
|  |   FRESHTheme = (function() { | ||||||
|  |     function FRESHTheme() {} | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     /* Open and parse the specified theme. */ | ||||||
|  |  | ||||||
|  |     FRESHTheme.prototype.open = function(themeFolder) { | ||||||
|  |       var cached, formatsHash, pathInfo, that, themeFile, themeInfo; | ||||||
|  |       this.folder = themeFolder; | ||||||
|  |       pathInfo = parsePath(themeFolder); | ||||||
|  |       formatsHash = {}; | ||||||
|  |       themeFile = PATH.join(themeFolder, 'theme.json'); | ||||||
|  |       themeInfo = loadSafeJson(themeFile); | ||||||
|  |       if (themeInfo.ex) { | ||||||
|  |         throw { | ||||||
|  |           fluenterror: themeInfo.ex.operation === 'parse' ? HMSTATUS.parseError : HMSTATUS.readError, | ||||||
|  |           inner: themeInfo.ex.inner | ||||||
|  |         }; | ||||||
|  |       } | ||||||
|  |       that = this; | ||||||
|  |       EXTEND(true, this, themeInfo.json); | ||||||
|  |       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); | ||||||
|  |           cached[th] = cached[th] || new FRESHTheme().open(themePath); | ||||||
|  |           return formatsHash[key] = cached[th].getFormat(key); | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  |       formatsHash = _load.call(this, formatsHash); | ||||||
|  |       this.formats = formatsHash; | ||||||
|  |       this.name = parsePath(this.folder).name; | ||||||
|  |       return this; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     /* Determine if the theme supports the specified output format. */ | ||||||
|  |  | ||||||
|  |     FRESHTheme.prototype.hasFormat = function(fmt) { | ||||||
|  |       return _.has(this.formats, fmt); | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     /* Determine if the theme supports the specified output format. */ | ||||||
|  |  | ||||||
|  |     FRESHTheme.prototype.getFormat = function(fmt) { | ||||||
|  |       return this.formats[fmt]; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     return FRESHTheme; | ||||||
|  |  | ||||||
|  |   })(); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   /* Load and parse theme source files. */ | ||||||
|  |  | ||||||
|  |   _load = function(formatsHash) { | ||||||
|  |     var copyOnly, fmts, major, that, tplFolder; | ||||||
|  |     that = this; | ||||||
|  |     major = false; | ||||||
|  |     tplFolder = PATH.join(this.folder, 'src'); | ||||||
|  |     copyOnly = ['.ttf', '.otf', '.png', '.jpg', '.jpeg', '.pdf']; | ||||||
|  |     fmts = READFILES(tplFolder).map(function(absPath) { | ||||||
|  |       return _loadOne.call(this, absPath, formatsHash, tplFolder); | ||||||
|  |     }, this); | ||||||
|  |     this.cssFiles = fmts.filter(function(fmt) { | ||||||
|  |       return fmt && (fmt.ext === 'css'); | ||||||
|  |     }); | ||||||
|  |     this.cssFiles.forEach(function(cssf) { | ||||||
|  |       var idx; | ||||||
|  |       idx = _.findIndex(fmts, function(fmt) { | ||||||
|  |         return fmt && fmt.pre === cssf.pre && fmt.ext === 'html'; | ||||||
|  |       }); | ||||||
|  |       cssf.major = false; | ||||||
|  |       if (idx > -1) { | ||||||
|  |         fmts[idx].css = cssf.data; | ||||||
|  |         return fmts[idx].cssPath = cssf.path; | ||||||
|  |       } else { | ||||||
|  |         if (that.inherits) { | ||||||
|  |           return that.overrides = { | ||||||
|  |             file: cssf.path, | ||||||
|  |             data: cssf.data | ||||||
|  |           }; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  |     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); | ||||||
|  |     absPathSafe = absPath.trim().toLowerCase(); | ||||||
|  |     outFmt = ''; | ||||||
|  |     act = 'copy'; | ||||||
|  |     isPrimary = false; | ||||||
|  |     if (this.explicit) { | ||||||
|  |       outFmt = _.find(Object.keys(this.formats), function(fmtKey) { | ||||||
|  |         var fmtVal; | ||||||
|  |         fmtVal = this.formats[fmtKey]; | ||||||
|  |         return _.some(fmtVal.transform, function(fpath) { | ||||||
|  |           var absPathB; | ||||||
|  |           absPathB = PATH.join(this.folder, fpath).trim().toLowerCase(); | ||||||
|  |           return absPathB === absPathSafe; | ||||||
|  |         }, this); | ||||||
|  |       }, this); | ||||||
|  |       if (outFmt) { | ||||||
|  |         act = 'transform'; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     if (!outFmt) { | ||||||
|  |       portion = pathInfo.dirname.replace(tplFolder, ''); | ||||||
|  |       if (portion && portion.trim()) { | ||||||
|  |         if (portion[1] === '_') { | ||||||
|  |           return; | ||||||
|  |         } | ||||||
|  |         reg = /^(?:\/|\\)(html|latex|doc|pdf|png|partials)(?:\/|\\)?/ig; | ||||||
|  |         res = reg.exec(portion); | ||||||
|  |         if (res) { | ||||||
|  |           if (res[1] !== 'partials') { | ||||||
|  |             outFmt = res[1]; | ||||||
|  |             if (!this.explicit) { | ||||||
|  |               act = 'transform'; | ||||||
|  |             } | ||||||
|  |           } else { | ||||||
|  |             this.partials = this.partials || []; | ||||||
|  |             this.partials.push({ | ||||||
|  |               name: pathInfo.name, | ||||||
|  |               path: absPath | ||||||
|  |             }); | ||||||
|  |             return null; | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     if (!outFmt) { | ||||||
|  |       idx = pathInfo.name.lastIndexOf('-'); | ||||||
|  |       outFmt = idx === -1 ? pathInfo.name : pathInfo.name.substr(idx + 1); | ||||||
|  |       if (!this.explicit) { | ||||||
|  |         act = 'transform'; | ||||||
|  |       } | ||||||
|  |       defFormats = require('./default-formats'); | ||||||
|  |       isPrimary = _.some(defFormats, function(form) { | ||||||
|  |         return form.name === outFmt && pathInfo.extname !== '.css'; | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |     formatsHash[outFmt] = formatsHash[outFmt] || { | ||||||
|  |       outFormat: outFmt, | ||||||
|  |       files: [] | ||||||
|  |     }; | ||||||
|  |     if ((ref = this.formats) != null ? (ref1 = ref[outFmt]) != null ? ref1.symLinks : void 0 : void 0) { | ||||||
|  |       formatsHash[outFmt].symLinks = this.formats[outFmt].symLinks; | ||||||
|  |     } | ||||||
|  |     obj = { | ||||||
|  |       action: act, | ||||||
|  |       primary: isPrimary, | ||||||
|  |       path: absPath, | ||||||
|  |       orgPath: PATH.relative(tplFolder, absPath), | ||||||
|  |       ext: pathInfo.extname.slice(1), | ||||||
|  |       title: friendlyName(outFmt), | ||||||
|  |       pre: outFmt, | ||||||
|  |       data: FS.readFileSync(absPath, 'utf8'), | ||||||
|  |       css: null | ||||||
|  |     }; | ||||||
|  |     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()) || ''; | ||||||
|  |     friendly = { | ||||||
|  |       yml: 'yaml', | ||||||
|  |       md: 'markdown', | ||||||
|  |       txt: 'text' | ||||||
|  |     }; | ||||||
|  |     return friendly[val] || val; | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   module.exports = FRESHTheme; | ||||||
|  |  | ||||||
|  | }).call(this); | ||||||
|  |  | ||||||
|  | //# sourceMappingURL=fresh-theme.js.map | ||||||
							
								
								
									
										422
									
								
								dist/core/jrs-resume.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										422
									
								
								dist/core/jrs-resume.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,422 @@ | |||||||
|  |  | ||||||
|  | /** | ||||||
|  | Definition of the JRSResume class. | ||||||
|  | @license MIT. See LICENSE.md for details. | ||||||
|  | @module core/jrs-resume | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | (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; | ||||||
|  |  | ||||||
|  |   FS = require('fs'); | ||||||
|  |  | ||||||
|  |   extend = require('extend'); | ||||||
|  |  | ||||||
|  |   validator = require('is-my-json-valid'); | ||||||
|  |  | ||||||
|  |   _ = require('underscore'); | ||||||
|  |  | ||||||
|  |   PATH = require('path'); | ||||||
|  |  | ||||||
|  |   MD = require('marked'); | ||||||
|  |  | ||||||
|  |   CONVERTER = require('fresh-jrs-converter'); | ||||||
|  |  | ||||||
|  |   moment = require('moment'); | ||||||
|  |  | ||||||
|  |   AbstractResume = require('./abstract-resume'); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |   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(superClass) { | ||||||
|  |     var clear; | ||||||
|  |  | ||||||
|  |     extend1(JRSResume, superClass); | ||||||
|  |  | ||||||
|  |     function JRSResume() { | ||||||
|  |       return JRSResume.__super__.constructor.apply(this, arguments); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     /** 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. | ||||||
|  |     { | ||||||
|  |       date: Perform safe date conversion. | ||||||
|  |       sort: Sort resume items by date. | ||||||
|  |       compute: Prepare computed resume totals. | ||||||
|  |     } | ||||||
|  |      */ | ||||||
|  |  | ||||||
|  |     JRSResume.prototype.parseJSON = function(rep, opts) { | ||||||
|  |       var ignoreList, ref, scrubbed, that, traverse; | ||||||
|  |       opts = opts || {}; | ||||||
|  |       that = this; | ||||||
|  |       traverse = require('traverse'); | ||||||
|  |       ignoreList = []; | ||||||
|  |       scrubbed = traverse(rep).map(function(x) { | ||||||
|  |         if (!this.isLeaf && this.node.ignore) { | ||||||
|  |           if (this.node.ignore === true || this.node.ignore === 'true') { | ||||||
|  |             ignoreList.push(this.node); | ||||||
|  |             return this.remove(); | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       }); | ||||||
|  |       extend(true, this, scrubbed); | ||||||
|  |       if (!((ref = this.imp) != null ? ref.processed : void 0)) { | ||||||
|  |         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; | ||||||
|  |       } | ||||||
|  |       (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') { | ||||||
|  |         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. */ | ||||||
|  |  | ||||||
|  |     JRSResume.prototype.format = function() { | ||||||
|  |       return 'JRS'; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     JRSResume.prototype.stringify = function() { | ||||||
|  |       return JRSResume.stringify(this); | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     /** Return a unique list of all keywords across all skills. */ | ||||||
|  |  | ||||||
|  |     JRSResume.prototype.keywords = function() { | ||||||
|  |       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. | ||||||
|  |      */ | ||||||
|  |  | ||||||
|  |     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.work; | ||||||
|  |       delete this.volunteer; | ||||||
|  |       delete this.education; | ||||||
|  |       delete this.awards; | ||||||
|  |       delete this.publications; | ||||||
|  |       delete this.interests; | ||||||
|  |       delete this.skills; | ||||||
|  |       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) { | ||||||
|  |       return JRSResume.__super__.duration.call(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, hardenStringsInObject, ret, that; | ||||||
|  |       that = this; | ||||||
|  |       ret = this.dupe(); | ||||||
|  |       HD = function(txt) { | ||||||
|  |         return '@@@@~' + txt + '~@@@@'; | ||||||
|  |       }; | ||||||
|  |       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]); | ||||||
|  |       }); | ||||||
|  |       return ret; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     return JRSResume; | ||||||
|  |  | ||||||
|  |   })(AbstractResume); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   /** Get the default (empty) sheet. */ | ||||||
|  |  | ||||||
|  |   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) { | ||||||
|  |       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; | ||||||
|  |       }); | ||||||
|  |       if (temp) { | ||||||
|  |         return void 0; | ||||||
|  |       } else { | ||||||
|  |         return value; | ||||||
|  |       } | ||||||
|  |     }; | ||||||
|  |     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; | ||||||
|  |     this.work && this.work.forEach(function(job) { | ||||||
|  |       job.safeStartDate = _fmt(job.startDate); | ||||||
|  |       return job.safeEndDate = _fmt(job.endDate); | ||||||
|  |     }); | ||||||
|  |     this.education && this.education.forEach(function(edu) { | ||||||
|  |       edu.safeStartDate = _fmt(edu.startDate); | ||||||
|  |       return edu.safeEndDate = _fmt(edu.endDate); | ||||||
|  |     }); | ||||||
|  |     this.volunteer && this.volunteer.forEach(function(vol) { | ||||||
|  |       vol.safeStartDate = _fmt(vol.startDate); | ||||||
|  |       return vol.safeEndDate = _fmt(vol.endDate); | ||||||
|  |     }); | ||||||
|  |     this.awards && this.awards.forEach(function(awd) { | ||||||
|  |       return awd.safeDate = _fmt(awd.date); | ||||||
|  |     }); | ||||||
|  |     return this.publications && this.publications.forEach(function(pub) { | ||||||
|  |       return pub.safeReleaseDate = _fmt(pub.releaseDate); | ||||||
|  |     }); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |   Export the JRSResume function/ctor. | ||||||
|  |    */ | ||||||
|  |  | ||||||
|  |   module.exports = JRSResume; | ||||||
|  |  | ||||||
|  | }).call(this); | ||||||
|  |  | ||||||
|  | //# sourceMappingURL=jrs-resume.js.map | ||||||
							
								
								
									
										107
									
								
								dist/core/jrs-theme.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								dist/core/jrs-theme.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,107 @@ | |||||||
|  |  | ||||||
|  | /** | ||||||
|  | Definition of the JRSTheme class. | ||||||
|  | @module core/jrs-theme | ||||||
|  | @license MIT. See LICENSE.MD for details. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | (function() { | ||||||
|  |   var JRSTheme, PATH, _, parsePath, pathExists; | ||||||
|  |  | ||||||
|  |   _ = require('underscore'); | ||||||
|  |  | ||||||
|  |   PATH = require('path'); | ||||||
|  |  | ||||||
|  |   parsePath = require('parse-filepath'); | ||||||
|  |  | ||||||
|  |   pathExists = require('path-exists').sync; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |   The JRSTheme class is a representation of a JSON Resume theme asset. | ||||||
|  |   @class JRSTheme | ||||||
|  |    */ | ||||||
|  |  | ||||||
|  |   JRSTheme = (function() { | ||||||
|  |     function JRSTheme() {} | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |     Open and parse the specified theme. | ||||||
|  |     @method open | ||||||
|  |      */ | ||||||
|  |  | ||||||
|  |     JRSTheme.prototype.open = function(thFolder) { | ||||||
|  |       var pathInfo, pkgJsonPath, thApi, thPkg; | ||||||
|  |       this.folder = thFolder; | ||||||
|  |       pathInfo = parsePath(thFolder); | ||||||
|  |       pkgJsonPath = PATH.join(thFolder, 'package.json'); | ||||||
|  |       if (pathExists(pkgJsonPath)) { | ||||||
|  |         thApi = require(thFolder); | ||||||
|  |         thPkg = require(pkgJsonPath); | ||||||
|  |         this.name = thPkg.name; | ||||||
|  |         this.render = (thApi && thApi.render) || void 0; | ||||||
|  |         this.engine = 'jrs'; | ||||||
|  |         this.formats = { | ||||||
|  |           html: { | ||||||
|  |             outFormat: 'html', | ||||||
|  |             files: [ | ||||||
|  |               { | ||||||
|  |                 action: 'transform', | ||||||
|  |                 render: this.render, | ||||||
|  |                 primary: true, | ||||||
|  |                 ext: 'html', | ||||||
|  |                 css: null | ||||||
|  |               } | ||||||
|  |             ] | ||||||
|  |           }, | ||||||
|  |           pdf: { | ||||||
|  |             outFormat: 'pdf', | ||||||
|  |             files: [ | ||||||
|  |               { | ||||||
|  |                 action: 'transform', | ||||||
|  |                 render: this.render, | ||||||
|  |                 primary: true, | ||||||
|  |                 ext: 'pdf', | ||||||
|  |                 css: null | ||||||
|  |               } | ||||||
|  |             ] | ||||||
|  |           } | ||||||
|  |         }; | ||||||
|  |       } else { | ||||||
|  |         throw { | ||||||
|  |           fluenterror: HACKMYSTATUS.missingPackageJSON | ||||||
|  |         }; | ||||||
|  |       } | ||||||
|  |       return this; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |     Determine if the theme supports the output format. | ||||||
|  |     @method hasFormat | ||||||
|  |      */ | ||||||
|  |  | ||||||
|  |     JRSTheme.prototype.hasFormat = function(fmt) { | ||||||
|  |       return _.has(this.formats, fmt); | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |     Return the requested output format. | ||||||
|  |     @method getFormat | ||||||
|  |      */ | ||||||
|  |  | ||||||
|  |     JRSTheme.prototype.getFormat = function(fmt) { | ||||||
|  |       return this.formats[fmt]; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     return JRSTheme; | ||||||
|  |  | ||||||
|  |   })(); | ||||||
|  |  | ||||||
|  |   module.exports = JRSTheme; | ||||||
|  |  | ||||||
|  | }).call(this); | ||||||
|  |  | ||||||
|  | //# sourceMappingURL=jrs-theme.js.map | ||||||
							
								
								
									
										122
									
								
								dist/core/resume-factory.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								dist/core/resume-factory.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,122 @@ | |||||||
|  |  | ||||||
|  | /** | ||||||
|  | Definition of the ResumeFactory class. | ||||||
|  | @license MIT. See LICENSE.md for details. | ||||||
|  | @module core/resume-factory | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | (function() { | ||||||
|  |   var FS, HACKMYSTATUS, HME, ResumeConverter, ResumeFactory, SyntaxErrorEx, _, _parse, chalk; | ||||||
|  |  | ||||||
|  |   FS = require('fs'); | ||||||
|  |  | ||||||
|  |   HACKMYSTATUS = require('./status-codes'); | ||||||
|  |  | ||||||
|  |   HME = require('./event-codes'); | ||||||
|  |  | ||||||
|  |   ResumeConverter = require('fresh-jrs-converter'); | ||||||
|  |  | ||||||
|  |   chalk = require('chalk'); | ||||||
|  |  | ||||||
|  |   SyntaxErrorEx = require('../utils/syntax-error-ex'); | ||||||
|  |  | ||||||
|  |   _ = require('underscore'); | ||||||
|  |  | ||||||
|  |   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? | ||||||
|  |           inner: {            // Passthru options for FRESH/JRSResume | ||||||
|  |             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. */ | ||||||
|  |     loadOne: function(src, opts, emitter) { | ||||||
|  |       var ResumeClass, info, isFRESH, json, objectify, orgFormat, rez, toFormat; | ||||||
|  |       toFormat = opts.format; | ||||||
|  |       objectify = opts.objectify; | ||||||
|  |       toFormat && (toFormat = toFormat.toLowerCase().trim()); | ||||||
|  |       info = _parse(src, opts, emitter); | ||||||
|  |       if (info.fluenterror) { | ||||||
|  |         return info; | ||||||
|  |       } | ||||||
|  |       json = info.json; | ||||||
|  |       isFRESH = json.meta && json.meta.format && json.meta.format.startsWith('FRESH@'); | ||||||
|  |       orgFormat = isFRESH ? 'fresh' : 'jrs'; | ||||||
|  |       if (toFormat && (orgFormat !== toFormat)) { | ||||||
|  |         json = ResumeConverter['to' + toFormat.toUpperCase()](json); | ||||||
|  |       } | ||||||
|  |       rez = null; | ||||||
|  |       if (objectify) { | ||||||
|  |         ResumeClass = require('../core/' + (toFormat || orgFormat) + '-resume'); | ||||||
|  |         rez = new ResumeClass().parseJSON(json, opts.inner); | ||||||
|  |         rez.i().file = src; | ||||||
|  |       } | ||||||
|  |       return { | ||||||
|  |         file: src, | ||||||
|  |         json: info.json, | ||||||
|  |         rez: rez | ||||||
|  |       }; | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   _parse = function(fileName, opts, eve) { | ||||||
|  |     var orgFormat, rawData, ret; | ||||||
|  |     rawData = null; | ||||||
|  |     try { | ||||||
|  |       eve && eve.stat(HME.beforeRead, { | ||||||
|  |         file: fileName | ||||||
|  |       }); | ||||||
|  |       rawData = FS.readFileSync(fileName, 'utf8'); | ||||||
|  |       eve && eve.stat(HME.afterRead, { | ||||||
|  |         file: fileName, | ||||||
|  |         data: rawData | ||||||
|  |       }); | ||||||
|  |       eve && eve.stat(HME.beforeParse, { | ||||||
|  |         data: rawData | ||||||
|  |       }); | ||||||
|  |       ret = { | ||||||
|  |         json: JSON.parse(rawData) | ||||||
|  |       }; | ||||||
|  |       orgFormat = ret.json.meta && ret.json.meta.format && ret.json.meta.format.startsWith('FRESH@') ? 'fresh' : 'jrs'; | ||||||
|  |       eve && eve.stat(HME.afterParse, { | ||||||
|  |         file: fileName, | ||||||
|  |         data: ret.json, | ||||||
|  |         fmt: orgFormat | ||||||
|  |       }); | ||||||
|  |       return ret; | ||||||
|  |     } catch (_error) { | ||||||
|  |       return { | ||||||
|  |         fluenterror: rawData ? HACKMYSTATUS.parseError : HACKMYSTATUS.readError, | ||||||
|  |         inner: _error, | ||||||
|  |         raw: rawData, | ||||||
|  |         file: fileName | ||||||
|  |       }; | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  | }).call(this); | ||||||
|  |  | ||||||
|  | //# sourceMappingURL=resume-factory.js.map | ||||||
							
								
								
									
										380
									
								
								dist/core/resume.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										380
									
								
								dist/core/resume.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,380 @@ | |||||||
|  | { | ||||||
|  |   "$schema": "http://json-schema.org/draft-04/schema#", | ||||||
|  |   "title": "Resume Schema", | ||||||
|  |   "type": "object", | ||||||
|  |   "additionalProperties": false, | ||||||
|  |   "properties": { | ||||||
|  |     "basics": { | ||||||
|  |       "type": "object", | ||||||
|  |       "additionalProperties": true, | ||||||
|  |       "properties": { | ||||||
|  |         "name": { | ||||||
|  |           "type": "string" | ||||||
|  |         }, | ||||||
|  |         "label": { | ||||||
|  |           "type": "string", | ||||||
|  |           "description": "e.g. Web Developer" | ||||||
|  |         }, | ||||||
|  |         "picture": { | ||||||
|  |           "type": "string", | ||||||
|  |           "description": "URL (as per RFC 3986) to a picture in JPEG or PNG format" | ||||||
|  |         }, | ||||||
|  |         "email": { | ||||||
|  |           "type": "string", | ||||||
|  |           "description": "e.g. thomas@gmail.com", | ||||||
|  |           "format": "email" | ||||||
|  |         }, | ||||||
|  |         "phone": { | ||||||
|  |           "type": "string", | ||||||
|  |           "description": "Phone numbers are stored as strings so use any format you like, e.g. 712-117-2923" | ||||||
|  |         }, | ||||||
|  |         "website": { | ||||||
|  |           "type": "string", | ||||||
|  |           "description": "URL (as per RFC 3986) to your website, e.g. personal homepage", | ||||||
|  |           "format": "uri" | ||||||
|  |         }, | ||||||
|  |         "summary": { | ||||||
|  |           "type": "string", | ||||||
|  |           "description": "Write a short 2-3 sentence biography about yourself" | ||||||
|  |         }, | ||||||
|  |         "location": { | ||||||
|  |           "type": "object", | ||||||
|  |           "additionalProperties": true, | ||||||
|  |           "properties": { | ||||||
|  |             "address": { | ||||||
|  |               "type": "string", | ||||||
|  |               "description": "To add multiple address lines, use \n. For example, 1234 Glücklichkeit Straße\nHinterhaus 5. Etage li." | ||||||
|  |             }, | ||||||
|  |             "postalCode": { | ||||||
|  |               "type": "string" | ||||||
|  |             }, | ||||||
|  |             "city": { | ||||||
|  |               "type": "string" | ||||||
|  |             }, | ||||||
|  |             "countryCode": { | ||||||
|  |               "type": "string", | ||||||
|  |               "description": "code as per ISO-3166-1 ALPHA-2, e.g. US, AU, IN" | ||||||
|  |             }, | ||||||
|  |             "region": { | ||||||
|  |               "type": "string", | ||||||
|  |               "description": "The general region where you live. Can be a US state, or a province, for instance." | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         }, | ||||||
|  |         "profiles": { | ||||||
|  |           "type": "array", | ||||||
|  |           "description": "Specify any number of social networks that you participate in", | ||||||
|  |           "additionalItems": false, | ||||||
|  |           "items": { | ||||||
|  |             "type": "object", | ||||||
|  |             "additionalProperties": true, | ||||||
|  |             "properties": { | ||||||
|  |               "network": { | ||||||
|  |                 "type": "string", | ||||||
|  |                 "description": "e.g. Facebook or Twitter" | ||||||
|  |               }, | ||||||
|  |               "username": { | ||||||
|  |                 "type": "string", | ||||||
|  |                 "description": "e.g. neutralthoughts" | ||||||
|  |               }, | ||||||
|  |               "url": { | ||||||
|  |                 "type": "string", | ||||||
|  |                 "description": "e.g. http://twitter.com/neutralthoughts" | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "work": { | ||||||
|  |       "type": "array", | ||||||
|  |       "additionalItems": false, | ||||||
|  |       "items": { | ||||||
|  |         "type": "object", | ||||||
|  |         "additionalProperties": true, | ||||||
|  |       "properties": { | ||||||
|  |           "company": { | ||||||
|  |             "type": "string", | ||||||
|  |             "description": "e.g. Facebook" | ||||||
|  |           }, | ||||||
|  |           "position": { | ||||||
|  |             "type": "string", | ||||||
|  |             "description": "e.g. Software Engineer" | ||||||
|  |           }, | ||||||
|  |           "website": { | ||||||
|  |             "type": "string", | ||||||
|  |             "description": "e.g. http://facebook.com", | ||||||
|  |             "format": "uri" | ||||||
|  |           }, | ||||||
|  |           "startDate": { | ||||||
|  |             "type": "string", | ||||||
|  |             "description": "resume.json uses the ISO 8601 date standard e.g. 2014-06-29", | ||||||
|  |             "format": "date" | ||||||
|  |           }, | ||||||
|  |           "endDate": { | ||||||
|  |             "type": "string", | ||||||
|  |             "description": "e.g. 2012-06-29", | ||||||
|  |             "format": "date" | ||||||
|  |           }, | ||||||
|  |           "summary": { | ||||||
|  |             "type": "string", | ||||||
|  |             "description": "Give an overview of your responsibilities at the company" | ||||||
|  |           }, | ||||||
|  |           "highlights": { | ||||||
|  |             "type": "array", | ||||||
|  |             "description": "Specify multiple accomplishments", | ||||||
|  |             "additionalItems": false, | ||||||
|  |             "items": { | ||||||
|  |               "type": "string", | ||||||
|  |               "description": "e.g. Increased profits by 20% from 2011-2012 through viral advertising" | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "volunteer": { | ||||||
|  |       "type": "array", | ||||||
|  |       "additionalItems": false, | ||||||
|  |       "items": { | ||||||
|  |         "type": "object", | ||||||
|  |         "additionalProperties": true, | ||||||
|  |       "properties": { | ||||||
|  |           "organization": { | ||||||
|  |             "type": "string", | ||||||
|  |             "description": "e.g. Facebook" | ||||||
|  |           }, | ||||||
|  |           "position": { | ||||||
|  |             "type": "string", | ||||||
|  |             "description": "e.g. Software Engineer" | ||||||
|  |           }, | ||||||
|  |           "website": { | ||||||
|  |             "type": "string", | ||||||
|  |             "description": "e.g. http://facebook.com", | ||||||
|  |             "format": "uri" | ||||||
|  |           }, | ||||||
|  |           "startDate": { | ||||||
|  |             "type": "string", | ||||||
|  |             "description": "resume.json uses the ISO 8601 date standard e.g. 2014-06-29", | ||||||
|  |             "format": "date" | ||||||
|  |           }, | ||||||
|  |           "endDate": { | ||||||
|  |             "type": "string", | ||||||
|  |             "description": "e.g. 2012-06-29", | ||||||
|  |             "format": "date" | ||||||
|  |           }, | ||||||
|  |           "summary": { | ||||||
|  |             "type": "string", | ||||||
|  |             "description": "Give an overview of your responsibilities at the company" | ||||||
|  |           }, | ||||||
|  |           "highlights": { | ||||||
|  |             "type": "array", | ||||||
|  |             "description": "Specify multiple accomplishments", | ||||||
|  |             "additionalItems": false, | ||||||
|  |             "items": { | ||||||
|  |               "type": "string", | ||||||
|  |               "description": "e.g. Increased profits by 20% from 2011-2012 through viral advertising" | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "education": { | ||||||
|  |       "type": "array", | ||||||
|  |       "additionalItems": false, | ||||||
|  |       "items": { | ||||||
|  |         "type": "object", | ||||||
|  |         "additionalProperties": true, | ||||||
|  |       "properties": { | ||||||
|  |             "institution": { | ||||||
|  |               "type": "string", | ||||||
|  |               "description": "e.g. Massachusetts Institute of Technology" | ||||||
|  |             }, | ||||||
|  |             "area": { | ||||||
|  |               "type": "string", | ||||||
|  |               "description": "e.g. Arts" | ||||||
|  |             }, | ||||||
|  |             "studyType": { | ||||||
|  |               "type": "string", | ||||||
|  |               "description": "e.g. Bachelor" | ||||||
|  |             }, | ||||||
|  |             "startDate": { | ||||||
|  |               "type": "string", | ||||||
|  |               "description": "e.g. 2014-06-29", | ||||||
|  |               "format": "date" | ||||||
|  |             }, | ||||||
|  |             "endDate": { | ||||||
|  |               "type": "string", | ||||||
|  |               "description": "e.g. 2012-06-29", | ||||||
|  |               "format": "date" | ||||||
|  |             }, | ||||||
|  |             "gpa": { | ||||||
|  |               "type": "string", | ||||||
|  |               "description": "grade point average, e.g. 3.67/4.0" | ||||||
|  |             }, | ||||||
|  |             "courses": { | ||||||
|  |               "type": "array", | ||||||
|  |               "description": "List notable courses/subjects", | ||||||
|  |               "additionalItems": false, | ||||||
|  |               "items": { | ||||||
|  |                 "type": "string", | ||||||
|  |                 "description": "e.g. H1302 - Introduction to American history" | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "awards": { | ||||||
|  |       "type": "array", | ||||||
|  |       "description": "Specify any awards you have received throughout your professional career", | ||||||
|  |       "additionalItems": false, | ||||||
|  |       "items": { | ||||||
|  |           "type": "object", | ||||||
|  |           "additionalProperties": true, | ||||||
|  |         "properties": { | ||||||
|  |             "title": { | ||||||
|  |               "type": "string", | ||||||
|  |               "description": "e.g. One of the 100 greatest minds of the century" | ||||||
|  |             }, | ||||||
|  |             "date": { | ||||||
|  |               "type": "string", | ||||||
|  |               "description": "e.g. 1989-06-12", | ||||||
|  |               "format": "date" | ||||||
|  |             }, | ||||||
|  |             "awarder": { | ||||||
|  |               "type": "string", | ||||||
|  |               "description": "e.g. Time Magazine" | ||||||
|  |             }, | ||||||
|  |             "summary": { | ||||||
|  |               "type": "string", | ||||||
|  |               "description": "e.g. Received for my work with Quantum Physics" | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "publications": { | ||||||
|  |       "type": "array", | ||||||
|  |       "description": "Specify your publications through your career", | ||||||
|  |       "additionalItems": false, | ||||||
|  |       "items": { | ||||||
|  |           "type": "object", | ||||||
|  |           "additionalProperties": true, | ||||||
|  |         "properties": { | ||||||
|  |             "name": { | ||||||
|  |               "type": "string", | ||||||
|  |               "description": "e.g. The World Wide Web" | ||||||
|  |             }, | ||||||
|  |             "publisher": { | ||||||
|  |               "type": "string", | ||||||
|  |               "description": "e.g. IEEE, Computer Magazine" | ||||||
|  |             }, | ||||||
|  |             "releaseDate": { | ||||||
|  |               "type": "string", | ||||||
|  |               "description": "e.g. 1990-08-01" | ||||||
|  |             }, | ||||||
|  |             "website": { | ||||||
|  |               "type": "string", | ||||||
|  |               "description": "e.g. http://www.computer.org/csdl/mags/co/1996/10/rx069-abs.html" | ||||||
|  |             }, | ||||||
|  |             "summary": { | ||||||
|  |               "type": "string", | ||||||
|  |               "description": "Short summary of publication. e.g. Discussion of the World Wide Web, HTTP, HTML." | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "skills": { | ||||||
|  |       "type": "array", | ||||||
|  |       "description": "List out your professional skill-set", | ||||||
|  |       "additionalItems": false, | ||||||
|  |       "items": { | ||||||
|  |           "type": "object", | ||||||
|  |           "additionalProperties": true, | ||||||
|  |         "properties": { | ||||||
|  |             "name": { | ||||||
|  |               "type": "string", | ||||||
|  |               "description": "e.g. Web Development" | ||||||
|  |             }, | ||||||
|  |             "level": { | ||||||
|  |               "type": "string", | ||||||
|  |               "description": "e.g. Master" | ||||||
|  |             }, | ||||||
|  |             "keywords": { | ||||||
|  |               "type": "array", | ||||||
|  |               "description": "List some keywords pertaining to this skill", | ||||||
|  |               "additionalItems": false, | ||||||
|  |               "items": { | ||||||
|  |                 "type": "string", | ||||||
|  |                 "description": "e.g. HTML" | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "languages": { | ||||||
|  |       "type": "array", | ||||||
|  |       "description": "List any other languages you speak", | ||||||
|  |       "additionalItems": false, | ||||||
|  |       "items": { | ||||||
|  |         "type": "object", | ||||||
|  |         "additionalProperties": true, | ||||||
|  |         "properties": { | ||||||
|  |           "language": { | ||||||
|  |             "type": "string", | ||||||
|  |             "description": "e.g. English, Spanish" | ||||||
|  |           }, | ||||||
|  |           "fluency": { | ||||||
|  |             "type": "string", | ||||||
|  |             "description": "e.g. Fluent, Beginner" | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "interests": { | ||||||
|  |       "type": "array", | ||||||
|  |       "additionalItems": false, | ||||||
|  |       "items": { | ||||||
|  |         "type": "object", | ||||||
|  |         "additionalProperties": true, | ||||||
|  |       "properties": { | ||||||
|  |           "name": { | ||||||
|  |             "type": "string", | ||||||
|  |             "description": "e.g. Philosophy" | ||||||
|  |           }, | ||||||
|  |           "keywords": { | ||||||
|  |             "type": "array", | ||||||
|  |             "additionalItems": false, | ||||||
|  |             "items": { | ||||||
|  |               "type": "string", | ||||||
|  |               "description": "e.g. Friedrich Nietzsche" | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "references": { | ||||||
|  |       "type": "array", | ||||||
|  |       "description": "List references you have received", | ||||||
|  |       "additionalItems": false, | ||||||
|  |       "items": { | ||||||
|  |         "type": "object", | ||||||
|  |         "additionalProperties": true, | ||||||
|  |       "properties": { | ||||||
|  |           "name": { | ||||||
|  |             "type": "string", | ||||||
|  |             "description": "e.g. Timothy Cook" | ||||||
|  |           }, | ||||||
|  |           "reference": { | ||||||
|  |             "type": "string", | ||||||
|  |             "description": "e.g. Joe blogs was a great employee, who turned up to work at least once a week. He exceeded my expectations when it came to doing nothing." | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										41
									
								
								dist/core/status-codes.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								dist/core/status-codes.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | |||||||
|  |  | ||||||
|  | /** | ||||||
|  | Status codes for HackMyResume. | ||||||
|  | @module core/status-codes | ||||||
|  | @license MIT. See LICENSE.MD for details. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | (function() { | ||||||
|  |   module.exports = { | ||||||
|  |     success: 0, | ||||||
|  |     themeNotFound: 1, | ||||||
|  |     copyCss: 2, | ||||||
|  |     resumeNotFound: 3, | ||||||
|  |     missingCommand: 4, | ||||||
|  |     invalidCommand: 5, | ||||||
|  |     resumeNotFoundAlt: 6, | ||||||
|  |     inputOutputParity: 7, | ||||||
|  |     createNameMissing: 8, | ||||||
|  |     pdfGeneration: 9, | ||||||
|  |     missingPackageJSON: 10, | ||||||
|  |     invalid: 11, | ||||||
|  |     invalidFormat: 12, | ||||||
|  |     notOnPath: 13, | ||||||
|  |     readError: 14, | ||||||
|  |     parseError: 15, | ||||||
|  |     fileSaveError: 16, | ||||||
|  |     generateError: 17, | ||||||
|  |     invalidHelperUse: 18, | ||||||
|  |     mixedMerge: 19, | ||||||
|  |     invokeTemplate: 20, | ||||||
|  |     compileTemplate: 21, | ||||||
|  |     themeLoad: 22, | ||||||
|  |     invalidParamCount: 23, | ||||||
|  |     missingParam: 24, | ||||||
|  |     createError: 25, | ||||||
|  |     validateError: 26 | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  | }).call(this); | ||||||
|  |  | ||||||
|  | //# sourceMappingURL=status-codes.js.map | ||||||
							
								
								
									
										40
									
								
								dist/generators/base-generator.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								dist/generators/base-generator.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | |||||||
|  |  | ||||||
|  | /** | ||||||
|  | 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() { | ||||||
|  |   var BaseGenerator; | ||||||
|  |  | ||||||
|  |   module.exports = BaseGenerator = (function() { | ||||||
|  |  | ||||||
|  |     /** 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); | ||||||
|  |  | ||||||
|  | //# sourceMappingURL=base-generator.js.map | ||||||
							
								
								
									
										53
									
								
								dist/generators/html-generator.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								dist/generators/html-generator.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | |||||||
|  |  | ||||||
|  | /** | ||||||
|  | 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; | ||||||
|  |  | ||||||
|  |   TemplateGenerator = require('./template-generator'); | ||||||
|  |  | ||||||
|  |   FS = require('fs-extra'); | ||||||
|  |  | ||||||
|  |   HTML = require('html'); | ||||||
|  |  | ||||||
|  |   PATH = require('path'); | ||||||
|  |  | ||||||
|  |   require('string.prototype.endswith'); | ||||||
|  |  | ||||||
|  |   module.exports = HtmlGenerator = (function(superClass) { | ||||||
|  |     extend(HtmlGenerator, superClass); | ||||||
|  |  | ||||||
|  |     function HtmlGenerator() { | ||||||
|  |       HtmlGenerator.__super__.constructor.call(this, 'html'); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |     Copy satellite CSS files to the destination and optionally pretty-print | ||||||
|  |     the HTML resume prior to saving. | ||||||
|  |      */ | ||||||
|  |  | ||||||
|  |     HtmlGenerator.prototype.onBeforeSave = function(info) { | ||||||
|  |       if (info.outputFile.endsWith('.css')) { | ||||||
|  |         return info.mk; | ||||||
|  |       } | ||||||
|  |       if (this.opts.prettify) { | ||||||
|  |         return HTML.prettyPrint(info.mk, this.opts.prettify); | ||||||
|  |       } else { | ||||||
|  |         return info.mk; | ||||||
|  |       } | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     return HtmlGenerator; | ||||||
|  |  | ||||||
|  |   })(TemplateGenerator); | ||||||
|  |  | ||||||
|  | }).call(this); | ||||||
|  |  | ||||||
|  | //# sourceMappingURL=html-generator.js.map | ||||||
							
								
								
									
										117
									
								
								dist/generators/html-pdf-cli-generator.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								dist/generators/html-pdf-cli-generator.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,117 @@ | |||||||
|  |  | ||||||
|  | /** | ||||||
|  | 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; | ||||||
|  |  | ||||||
|  |   TemplateGenerator = require('./template-generator'); | ||||||
|  |  | ||||||
|  |   FS = require('fs-extra'); | ||||||
|  |  | ||||||
|  |   PATH = require('path'); | ||||||
|  |  | ||||||
|  |   SLASH = require('slash'); | ||||||
|  |  | ||||||
|  |   _ = require('underscore'); | ||||||
|  |  | ||||||
|  |   HMSTATUS = require('../core/status-codes'); | ||||||
|  |  | ||||||
|  |   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'); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     /** Generate the binary PDF. */ | ||||||
|  |  | ||||||
|  |     HtmlPdfCLIGenerator.prototype.onBeforeSave = function(info) { | ||||||
|  |       var safe_eng; | ||||||
|  |       if (info.ext !== 'html' && info.ext !== 'pdf') { | ||||||
|  |         return info.mk; | ||||||
|  |       } | ||||||
|  |       safe_eng = info.opts.pdf || 'wkhtmltopdf'; | ||||||
|  |       if (safe_eng === 'phantom') { | ||||||
|  |         safe_eng = 'phantomjs'; | ||||||
|  |       } | ||||||
|  |       if (_.has(engines, safe_eng)) { | ||||||
|  |         this.errHandler = info.opts.errHandler; | ||||||
|  |         engines[safe_eng].call(this, info.mk, info.outputFile, this.onError); | ||||||
|  |         return null; | ||||||
|  |       } | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     /* 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) { | ||||||
|  |       var ref; | ||||||
|  |       if ((ref = param.errHandler) != null) { | ||||||
|  |         if (typeof ref.err === "function") { | ||||||
|  |           ref.err(HMSTATUS.pdfGeneration, ex); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     return HtmlPdfCLIGenerator; | ||||||
|  |  | ||||||
|  |   })(TemplateGenerator); | ||||||
|  |  | ||||||
|  |   engines = { | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |     Generate a PDF from HTML using wkhtmltopdf's CLI interface. | ||||||
|  |     Spawns a child process with `wkhtmltopdf <source> <target>`. 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, on_error) { | ||||||
|  |       var tempFile; | ||||||
|  |       tempFile = fOut.replace(/\.pdf$/i, '.pdf.html'); | ||||||
|  |       FS.writeFileSync(tempFile, markup, 'utf8'); | ||||||
|  |       SPAWN('wkhtmltopdf', [tempFile, fOut], false, on_error, this); | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |     Generate a PDF from HTML using Phantom's CLI interface. | ||||||
|  |     Spawns a child process with `phantomjs <script> <source> <target>`. Phantom | ||||||
|  |     must be installed and path-accessible. | ||||||
|  |     TODO: If HTML generation has run, reuse that output | ||||||
|  |     TODO: Local web server to ease Phantom rendering | ||||||
|  |      */ | ||||||
|  |     phantomjs: function(markup, fOut, on_error) { | ||||||
|  |       var destPath, scriptPath, sourcePath, tempFile; | ||||||
|  |       tempFile = fOut.replace(/\.pdf$/i, '.pdf.html'); | ||||||
|  |       FS.writeFileSync(tempFile, markup, 'utf8'); | ||||||
|  |       scriptPath = PATH.relative(process.cwd(), PATH.resolve(__dirname, '../utils/rasterize.js')); | ||||||
|  |       scriptPath = SLASH(scriptPath); | ||||||
|  |       sourcePath = SLASH(PATH.relative(process.cwd(), tempFile)); | ||||||
|  |       destPath = SLASH(PATH.relative(process.cwd(), fOut)); | ||||||
|  |       SPAWN('phantomjs', [scriptPath, sourcePath, destPath], false, on_error, this); | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  | }).call(this); | ||||||
|  |  | ||||||
|  | //# sourceMappingURL=html-pdf-cli-generator.js.map | ||||||
							
								
								
									
										75
									
								
								dist/generators/html-png-generator.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								dist/generators/html-png-generator.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,75 @@ | |||||||
|  |  | ||||||
|  | /** | ||||||
|  | Definition of the HtmlPngGenerator class. | ||||||
|  | @module generators/html-png-generator | ||||||
|  | @license MIT. See LICENSE.MD for details. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | (function() { | ||||||
|  |   var FS, HTML, HtmlPngGenerator, PATH, SLASH, SPAWN, TemplateGenerator, phantom, | ||||||
|  |     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; | ||||||
|  |  | ||||||
|  |   TemplateGenerator = require('./template-generator'); | ||||||
|  |  | ||||||
|  |   FS = require('fs-extra'); | ||||||
|  |  | ||||||
|  |   HTML = require('html'); | ||||||
|  |  | ||||||
|  |   SLASH = require('slash'); | ||||||
|  |  | ||||||
|  |   SPAWN = require('../utils/safe-spawn'); | ||||||
|  |  | ||||||
|  |   PATH = require('path'); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |   An HTML-based PNG resume generator for HackMyResume. | ||||||
|  |    */ | ||||||
|  |  | ||||||
|  |   module.exports = HtmlPngGenerator = (function(superClass) { | ||||||
|  |     extend(HtmlPngGenerator, superClass); | ||||||
|  |  | ||||||
|  |     function HtmlPngGenerator() { | ||||||
|  |       HtmlPngGenerator.__super__.constructor.call(this, 'png', 'html'); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     HtmlPngGenerator.prototype.invoke = function(rez, themeMarkup, cssInfo, opts) {}; | ||||||
|  |  | ||||||
|  |     HtmlPngGenerator.prototype.generate = function(rez, f, opts) { | ||||||
|  |       var htmlFile, htmlResults; | ||||||
|  |       htmlResults = opts.targets.filter(function(t) { | ||||||
|  |         return t.fmt.outFormat === 'html'; | ||||||
|  |       }); | ||||||
|  |       htmlFile = htmlResults[0].final.files.filter(function(fl) { | ||||||
|  |         return fl.info.ext === 'html'; | ||||||
|  |       }); | ||||||
|  |       phantom(htmlFile[0].data, f); | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     return HtmlPngGenerator; | ||||||
|  |  | ||||||
|  |   })(TemplateGenerator); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |   Generate a PDF from HTML using Phantom's CLI interface. | ||||||
|  |   Spawns a child process with `phantomjs <script> <source> <target>`. Phantom | ||||||
|  |   must be installed and path-accessible. | ||||||
|  |   TODO: If HTML generation has run, reuse that output | ||||||
|  |   TODO: Local web server to ease Phantom rendering | ||||||
|  |    */ | ||||||
|  |  | ||||||
|  |   phantom = function(markup, fOut) { | ||||||
|  |     var destPath, info, scriptPath, sourcePath, tempFile; | ||||||
|  |     tempFile = fOut.replace(/\.png$/i, '.png.html'); | ||||||
|  |     FS.writeFileSync(tempFile, markup, 'utf8'); | ||||||
|  |     scriptPath = SLASH(PATH.relative(process.cwd(), PATH.resolve(__dirname, '../utils/rasterize.js'))); | ||||||
|  |     sourcePath = SLASH(PATH.relative(process.cwd(), tempFile)); | ||||||
|  |     destPath = SLASH(PATH.relative(process.cwd(), fOut)); | ||||||
|  |     info = SPAWN('phantomjs', [scriptPath, sourcePath, destPath]); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  | }).call(this); | ||||||
|  |  | ||||||
|  | //# sourceMappingURL=html-png-generator.js.map | ||||||
							
								
								
									
										47
									
								
								dist/generators/json-generator.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								dist/generators/json-generator.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | |||||||
|  |  | ||||||
|  | /** | ||||||
|  | Definition of the JsonGenerator class. | ||||||
|  | @module generators/json-generator | ||||||
|  | @license MIT. See LICENSE.md for details. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | (function() { | ||||||
|  |   var BaseGenerator, FJCV, FS, JsonGenerator, _, | ||||||
|  |     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; | ||||||
|  |  | ||||||
|  |   BaseGenerator = require('./base-generator'); | ||||||
|  |  | ||||||
|  |   FS = require('fs'); | ||||||
|  |  | ||||||
|  |   _ = require('underscore'); | ||||||
|  |  | ||||||
|  |   FJCV = require('fresh-jrs-converter'); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   /** The JsonGenerator generates a FRESH or JRS resume as an output. */ | ||||||
|  |  | ||||||
|  |   module.exports = JsonGenerator = (function(superClass) { | ||||||
|  |     extend(JsonGenerator, superClass); | ||||||
|  |  | ||||||
|  |     function JsonGenerator() { | ||||||
|  |       JsonGenerator.__super__.constructor.call(this, 'json'); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     JsonGenerator.prototype.invoke = function(rez) { | ||||||
|  |       var altRez; | ||||||
|  |       altRez = FJCV['to' + (rez.format() === 'FRESH' ? 'JRS' : 'FRESH')](rez); | ||||||
|  |       return altRez = FJCV.toSTRING(altRez); | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     JsonGenerator.prototype.generate = function(rez, f) { | ||||||
|  |       FS.writeFileSync(f, this.invoke(rez), 'utf8'); | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     return JsonGenerator; | ||||||
|  |  | ||||||
|  |   })(BaseGenerator); | ||||||
|  |  | ||||||
|  | }).call(this); | ||||||
|  |  | ||||||
|  | //# sourceMappingURL=json-generator.js.map | ||||||
							
								
								
									
										50
									
								
								dist/generators/json-yaml-generator.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								dist/generators/json-yaml-generator.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,50 @@ | |||||||
|  |  | ||||||
|  | /** | ||||||
|  | Definition of the JsonYamlGenerator class. | ||||||
|  | @module generators/json-yaml-generator | ||||||
|  | @license MIT. See LICENSE.md for details. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | (function() { | ||||||
|  |   var BaseGenerator, FS, JsonYamlGenerator, YAML, | ||||||
|  |     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; | ||||||
|  |  | ||||||
|  |   BaseGenerator = require('./base-generator'); | ||||||
|  |  | ||||||
|  |   FS = require('fs'); | ||||||
|  |  | ||||||
|  |   YAML = require('yamljs'); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |   JsonYamlGenerator takes a JSON resume object and translates it directly to | ||||||
|  |   JSON without a template, producing an equivalent YAML-formatted resume. See | ||||||
|  |   also YamlGenerator (yaml-generator.js). | ||||||
|  |    */ | ||||||
|  |  | ||||||
|  |   module.exports = JsonYamlGenerator = (function(superClass) { | ||||||
|  |     extend(JsonYamlGenerator, superClass); | ||||||
|  |  | ||||||
|  |     function JsonYamlGenerator() { | ||||||
|  |       JsonYamlGenerator.__super__.constructor.call(this, 'yml'); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     JsonYamlGenerator.prototype.invoke = function(rez, themeMarkup, cssInfo, opts) { | ||||||
|  |       return YAML.stringify(JSON.parse(rez.stringify()), Infinity, 2); | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     JsonYamlGenerator.prototype.generate = function(rez, f, opts) { | ||||||
|  |       var data; | ||||||
|  |       data = YAML.stringify(JSON.parse(rez.stringify()), Infinity, 2); | ||||||
|  |       FS.writeFileSync(f, data, 'utf8'); | ||||||
|  |       return data; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     return JsonYamlGenerator; | ||||||
|  |  | ||||||
|  |   })(BaseGenerator); | ||||||
|  |  | ||||||
|  | }).call(this); | ||||||
|  |  | ||||||
|  | //# sourceMappingURL=json-yaml-generator.js.map | ||||||
							
								
								
									
										33
									
								
								dist/generators/latex-generator.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								dist/generators/latex-generator.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | |||||||
|  |  | ||||||
|  | /** | ||||||
|  | Definition of the LaTeXGenerator class. | ||||||
|  | @module generators/latex-generator | ||||||
|  | @license MIT. See LICENSE.md for details. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | (function() { | ||||||
|  |   var LaTeXGenerator, 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; | ||||||
|  |  | ||||||
|  |   TemplateGenerator = require('./template-generator'); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |   LaTeXGenerator generates a LaTeX resume via TemplateGenerator. | ||||||
|  |    */ | ||||||
|  |  | ||||||
|  |   module.exports = LaTeXGenerator = (function(superClass) { | ||||||
|  |     extend(LaTeXGenerator, superClass); | ||||||
|  |  | ||||||
|  |     function LaTeXGenerator() { | ||||||
|  |       LaTeXGenerator.__super__.constructor.call(this, 'latex', 'tex'); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return LaTeXGenerator; | ||||||
|  |  | ||||||
|  |   })(TemplateGenerator); | ||||||
|  |  | ||||||
|  | }).call(this); | ||||||
|  |  | ||||||
|  | //# sourceMappingURL=latex-generator.js.map | ||||||
							
								
								
									
										33
									
								
								dist/generators/markdown-generator.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								dist/generators/markdown-generator.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | |||||||
|  |  | ||||||
|  | /** | ||||||
|  | Definition of the MarkdownGenerator class. | ||||||
|  | @module generators/markdown-generator | ||||||
|  | @license MIT. See LICENSE.md for details. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | (function() { | ||||||
|  |   var MarkdownGenerator, 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; | ||||||
|  |  | ||||||
|  |   TemplateGenerator = require('./template-generator'); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |   MarkdownGenerator generates a Markdown-formatted resume via TemplateGenerator. | ||||||
|  |    */ | ||||||
|  |  | ||||||
|  |   module.exports = MarkdownGenerator = (function(superClass) { | ||||||
|  |     extend(MarkdownGenerator, superClass); | ||||||
|  |  | ||||||
|  |     function MarkdownGenerator() { | ||||||
|  |       MarkdownGenerator.__super__.constructor.call(this, 'md', 'txt'); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return MarkdownGenerator; | ||||||
|  |  | ||||||
|  |   })(TemplateGenerator); | ||||||
|  |  | ||||||
|  | }).call(this); | ||||||
|  |  | ||||||
|  | //# sourceMappingURL=markdown-generator.js.map | ||||||
							
								
								
									
										288
									
								
								dist/generators/template-generator.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										288
									
								
								dist/generators/template-generator.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,288 @@ | |||||||
|  |  | ||||||
|  | /** | ||||||
|  | Definition of the TemplateGenerator class. TODO: Refactor | ||||||
|  | @module generators/template-generator | ||||||
|  | @license MIT. See LICENSE.md for details. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | (function() { | ||||||
|  |   var BaseGenerator, EXTEND, FRESHTheme, FS, JRSTheme, MD, MKDIRP, PATH, TemplateGenerator, XML, _, _defaultOpts, _reg, createSymLinks, freeze, parsePath, unfreeze, | ||||||
|  |     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; | ||||||
|  |  | ||||||
|  |   FS = require('fs-extra'); | ||||||
|  |  | ||||||
|  |   _ = require('underscore'); | ||||||
|  |  | ||||||
|  |   MD = require('marked'); | ||||||
|  |  | ||||||
|  |   XML = require('xml-escape'); | ||||||
|  |  | ||||||
|  |   PATH = require('path'); | ||||||
|  |  | ||||||
|  |   parsePath = require('parse-filepath'); | ||||||
|  |  | ||||||
|  |   MKDIRP = require('mkdirp'); | ||||||
|  |  | ||||||
|  |   BaseGenerator = require('./base-generator'); | ||||||
|  |  | ||||||
|  |   EXTEND = require('extend'); | ||||||
|  |  | ||||||
|  |   FRESHTheme = require('../core/fresh-theme'); | ||||||
|  |  | ||||||
|  |   JRSTheme = require('../core/jrs-theme'); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |   TemplateGenerator performs resume generation via local Handlebar or Underscore | ||||||
|  |   style template expansion and is appropriate for text-based formats like HTML, | ||||||
|  |   plain text, and XML versions of Microsoft Word, Excel, and OpenOffice. | ||||||
|  |   @class TemplateGenerator | ||||||
|  |    */ | ||||||
|  |  | ||||||
|  |   module.exports = TemplateGenerator = (function(superClass) { | ||||||
|  |     extend(TemplateGenerator, superClass); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     /** Constructor. Set the output format and template format for this | ||||||
|  |     generator. Will usually be called by a derived generator such as | ||||||
|  |     HTMLGenerator or MarkdownGenerator. | ||||||
|  |      */ | ||||||
|  |  | ||||||
|  |     function TemplateGenerator(outputFormat, templateFormat, cssFile) { | ||||||
|  |       TemplateGenerator.__super__.constructor.call(this, outputFormat); | ||||||
|  |       this.tplFormat = templateFormat || outputFormat; | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     /** Generate a resume using string-based inputs and outputs without touching | ||||||
|  |     the filesystem. | ||||||
|  |     @method invoke | ||||||
|  |     @param rez A FreshResume object. | ||||||
|  |     @param opts Generator options. | ||||||
|  |     @returns {Array} An array of objects representing the generated output | ||||||
|  |     files. | ||||||
|  |      */ | ||||||
|  |  | ||||||
|  |     TemplateGenerator.prototype.invoke = function(rez, opts) { | ||||||
|  |       var curFmt, results; | ||||||
|  |       opts = opts ? (this.opts = EXTEND(true, {}, _defaultOpts, opts)) : this.opts; | ||||||
|  |       curFmt = opts.themeObj.getFormat(this.format); | ||||||
|  |       curFmt.files = _.sortBy(curFmt.files, function(fi) { | ||||||
|  |         return fi.ext !== 'css'; | ||||||
|  |       }); | ||||||
|  |       results = curFmt.files.map(function(tplInfo, idx) { | ||||||
|  |         var trx; | ||||||
|  |         if (tplInfo.action === 'transform') { | ||||||
|  |           trx = this.transform(rez, tplInfo.data, this.format, opts, opts.themeObj, curFmt); | ||||||
|  |           if (tplInfo.ext === 'css') { | ||||||
|  |             curFmt.files[idx].data = trx; | ||||||
|  |           } else { | ||||||
|  |             tplInfo.ext === 'html'; | ||||||
|  |           } | ||||||
|  |         } else { | ||||||
|  |  | ||||||
|  |         } | ||||||
|  |         if (typeof opts.onTransform === "function") { | ||||||
|  |           opts.onTransform(tplInfo); | ||||||
|  |         } | ||||||
|  |         return { | ||||||
|  |           info: tplInfo, | ||||||
|  |           data: trx | ||||||
|  |         }; | ||||||
|  |       }, this); | ||||||
|  |       return { | ||||||
|  |         files: results | ||||||
|  |       }; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     /** Generate a resume using file-based inputs and outputs. Requires access | ||||||
|  |     to the local filesystem. | ||||||
|  |     @method generate | ||||||
|  |     @param rez A FreshResume object. | ||||||
|  |     @param f Full path to the output resume file to generate. | ||||||
|  |     @param opts Generator options. | ||||||
|  |      */ | ||||||
|  |  | ||||||
|  |     TemplateGenerator.prototype.generate = function(rez, f, opts) { | ||||||
|  |       var curFmt, genInfo, outFolder; | ||||||
|  |       this.opts = EXTEND(true, {}, _defaultOpts, opts); | ||||||
|  |       genInfo = this.invoke(rez, null); | ||||||
|  |       outFolder = parsePath(f).dirname; | ||||||
|  |       curFmt = opts.themeObj.getFormat(this.format); | ||||||
|  |       genInfo.files.forEach(function(file) { | ||||||
|  |         var thisFilePath; | ||||||
|  |         file.info.orgPath = file.info.orgPath || ''; | ||||||
|  |         thisFilePath = file.info.primary ? f : PATH.join(outFolder, file.info.orgPath); | ||||||
|  |         if (file.info.action !== 'copy' && this.onBeforeSave) { | ||||||
|  |           file.data = this.onBeforeSave({ | ||||||
|  |             theme: opts.themeObj, | ||||||
|  |             outputFile: thisFilePath, | ||||||
|  |             mk: file.data, | ||||||
|  |             opts: this.opts, | ||||||
|  |             ext: file.info.ext | ||||||
|  |           }); | ||||||
|  |           if (!file.data) { | ||||||
|  |             return; | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |         if (typeof opts.beforeWrite === "function") { | ||||||
|  |           opts.beforeWrite(thisFilePath); | ||||||
|  |         } | ||||||
|  |         MKDIRP.sync(PATH.dirname(thisFilePath)); | ||||||
|  |         if (file.info.action !== 'copy') { | ||||||
|  |           FS.writeFileSync(thisFilePath, file.data, { | ||||||
|  |             encoding: 'utf8', | ||||||
|  |             flags: 'w' | ||||||
|  |           }); | ||||||
|  |         } else { | ||||||
|  |           FS.copySync(file.info.path, thisFilePath); | ||||||
|  |         } | ||||||
|  |         if (typeof opts.afterWrite === "function") { | ||||||
|  |           opts.afterWrite(thisFilePath); | ||||||
|  |         } | ||||||
|  |         if (this.onAfterSave) { | ||||||
|  |           return this.onAfterSave({ | ||||||
|  |             outputFile: fileName, | ||||||
|  |             mk: file.data, | ||||||
|  |             opts: this.opts | ||||||
|  |           }); | ||||||
|  |         } | ||||||
|  |       }, this); | ||||||
|  |       createSymLinks(curFmt, outFolder); | ||||||
|  |       return genInfo; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     /** Perform a single resume resume transformation using string-based inputs | ||||||
|  |     and outputs without touching the local file system. | ||||||
|  |     @param json A FRESH or JRS resume object. | ||||||
|  |     @param jst The stringified template data | ||||||
|  |     @param format The format name, such as "html" or "latex" | ||||||
|  |     @param cssInfo Needs to be refactored. | ||||||
|  |     @param opts Options and passthrough data. | ||||||
|  |      */ | ||||||
|  |  | ||||||
|  |     TemplateGenerator.prototype.transform = function(json, jst, format, opts, theme, curFmt) { | ||||||
|  |       var eng, result; | ||||||
|  |       if (this.opts.freezeBreaks) { | ||||||
|  |         jst = freeze(jst); | ||||||
|  |       } | ||||||
|  |       eng = require('../renderers/' + theme.engine + '-generator'); | ||||||
|  |       result = eng.generate(json, jst, format, curFmt, opts, theme); | ||||||
|  |       if (this.opts.freezeBreaks) { | ||||||
|  |         result = unfreeze(result); | ||||||
|  |       } | ||||||
|  |       return result; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     return TemplateGenerator; | ||||||
|  |  | ||||||
|  |   })(BaseGenerator); | ||||||
|  |  | ||||||
|  |   createSymLinks = function(curFmt, outFolder) { | ||||||
|  |     if (curFmt.symLinks) { | ||||||
|  |       Object.keys(curFmt.symLinks).forEach(function(loc) { | ||||||
|  |         var absLoc, absTarg, succeeded, type; | ||||||
|  |         absLoc = PATH.join(outFolder, loc); | ||||||
|  |         absTarg = PATH.join(PATH.dirname(absLoc), curFmt.symLinks[loc]); | ||||||
|  |         type = parsePath(absLoc).extname ? 'file' : 'junction'; | ||||||
|  |         try { | ||||||
|  |           return FS.symlinkSync(absTarg, absLoc, type); | ||||||
|  |         } catch (_error) { | ||||||
|  |           succeeded = false; | ||||||
|  |           if (_error.code === 'EEXIST') { | ||||||
|  |             FS.unlinkSync(absLoc); | ||||||
|  |             try { | ||||||
|  |               FS.symlinkSync(absTarg, absLoc, type); | ||||||
|  |               succeeded = true; | ||||||
|  |             } catch (_error) {} | ||||||
|  |           } | ||||||
|  |           if (!succeeded) { | ||||||
|  |             throw ex; | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   /** Freeze newlines for protection against errant JST parsers. */ | ||||||
|  |  | ||||||
|  |   freeze = function(markup) { | ||||||
|  |     markup.replace(_reg.regN, _defaultOpts.nSym); | ||||||
|  |     return markup.replace(_reg.regR, _defaultOpts.rSym); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   /** Unfreeze newlines when the coast is clear. */ | ||||||
|  |  | ||||||
|  |   unfreeze = function(markup) { | ||||||
|  |     markup.replace(_reg.regSymR, '\r'); | ||||||
|  |     return markup.replace(_reg.regSymN, '\n'); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   /** Default template generator options. */ | ||||||
|  |  | ||||||
|  |   _defaultOpts = { | ||||||
|  |     engine: 'underscore', | ||||||
|  |     keepBreaks: true, | ||||||
|  |     freezeBreaks: false, | ||||||
|  |     nSym: '&newl;', | ||||||
|  |     rSym: '&retn;', | ||||||
|  |     template: { | ||||||
|  |       interpolate: /\{\{(.+?)\}\}/g, | ||||||
|  |       escape: /\{\{\=(.+?)\}\}/g, | ||||||
|  |       evaluate: /\{\%(.+?)\%\}/g, | ||||||
|  |       comment: /\{\#(.+?)\#\}/g | ||||||
|  |     }, | ||||||
|  |     filters: { | ||||||
|  |       out: function(txt) { | ||||||
|  |         return txt; | ||||||
|  |       }, | ||||||
|  |       raw: function(txt) { | ||||||
|  |         return txt; | ||||||
|  |       }, | ||||||
|  |       xml: function(txt) { | ||||||
|  |         return XML(txt); | ||||||
|  |       }, | ||||||
|  |       md: function(txt) { | ||||||
|  |         return MD(txt || ''); | ||||||
|  |       }, | ||||||
|  |       mdin: function(txt) { | ||||||
|  |         return MD(txt || '').replace(/^\s*<p>|<\/p>\s*$/gi, ''); | ||||||
|  |       }, | ||||||
|  |       lower: function(txt) { | ||||||
|  |         return txt.toLowerCase(); | ||||||
|  |       }, | ||||||
|  |       link: function(name, url) { | ||||||
|  |         if (url) { | ||||||
|  |           return '<a href="' + url + '">' + name + '</a>'; | ||||||
|  |         } else { | ||||||
|  |           return name; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     prettify: { | ||||||
|  |       indent_size: 2, | ||||||
|  |       unformatted: ['em', 'strong', 'a'], | ||||||
|  |       max_char: 80 | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   /** Regexes for linebreak preservation. */ | ||||||
|  |  | ||||||
|  |   _reg = { | ||||||
|  |     regN: new RegExp('\n', 'g'), | ||||||
|  |     regR: new RegExp('\r', 'g'), | ||||||
|  |     regSymN: new RegExp(_defaultOpts.nSym, 'g'), | ||||||
|  |     regSymR: new RegExp(_defaultOpts.rSym, 'g') | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  | }).call(this); | ||||||
|  |  | ||||||
|  | //# sourceMappingURL=template-generator.js.map | ||||||
							
								
								
									
										33
									
								
								dist/generators/text-generator.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								dist/generators/text-generator.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | |||||||
|  |  | ||||||
|  | /** | ||||||
|  | Definition of the TextGenerator class. | ||||||
|  | @module generators/text-generator | ||||||
|  | @license MIT. See LICENSE.md for details. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | (function() { | ||||||
|  |   var TemplateGenerator, TextGenerator, | ||||||
|  |     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; | ||||||
|  |  | ||||||
|  |   TemplateGenerator = require('./template-generator'); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |   The TextGenerator generates a plain-text resume via the TemplateGenerator. | ||||||
|  |    */ | ||||||
|  |  | ||||||
|  |   module.exports = TextGenerator = (function(superClass) { | ||||||
|  |     extend(TextGenerator, superClass); | ||||||
|  |  | ||||||
|  |     function TextGenerator() { | ||||||
|  |       TextGenerator.__super__.constructor.call(this, 'txt'); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return TextGenerator; | ||||||
|  |  | ||||||
|  |   })(TemplateGenerator); | ||||||
|  |  | ||||||
|  | }).call(this); | ||||||
|  |  | ||||||
|  | //# sourceMappingURL=text-generator.js.map | ||||||
							
								
								
									
										28
									
								
								dist/generators/word-generator.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								dist/generators/word-generator.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | |||||||
|  |  | ||||||
|  | /* | ||||||
|  | Definition of the WordGenerator class. | ||||||
|  | @module generators/word-generator | ||||||
|  | @license MIT. See LICENSE.md for details. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | (function() { | ||||||
|  |   var TemplateGenerator, WordGenerator, | ||||||
|  |     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; | ||||||
|  |  | ||||||
|  |   TemplateGenerator = require('./template-generator'); | ||||||
|  |  | ||||||
|  |   module.exports = WordGenerator = (function(superClass) { | ||||||
|  |     extend(WordGenerator, superClass); | ||||||
|  |  | ||||||
|  |     function WordGenerator() { | ||||||
|  |       WordGenerator.__super__.constructor.call(this, 'doc', 'xml'); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return WordGenerator; | ||||||
|  |  | ||||||
|  |   })(TemplateGenerator); | ||||||
|  |  | ||||||
|  | }).call(this); | ||||||
|  |  | ||||||
|  | //# sourceMappingURL=word-generator.js.map | ||||||
							
								
								
									
										31
									
								
								dist/generators/xml-generator.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								dist/generators/xml-generator.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | |||||||
|  |  | ||||||
|  | /** | ||||||
|  | Definition of the XMLGenerator class. | ||||||
|  | @license MIT. See LICENSE.md for details. | ||||||
|  | @module generatprs/xml-generator | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | (function() { | ||||||
|  |   var BaseGenerator, XMLGenerator, | ||||||
|  |     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; | ||||||
|  |  | ||||||
|  |   BaseGenerator = require('./base-generator'); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   /** The XmlGenerator generates an XML resume via the TemplateGenerator. */ | ||||||
|  |  | ||||||
|  |   module.exports = XMLGenerator = (function(superClass) { | ||||||
|  |     extend(XMLGenerator, superClass); | ||||||
|  |  | ||||||
|  |     function XMLGenerator() { | ||||||
|  |       XMLGenerator.__super__.constructor.call(this, 'xml'); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return XMLGenerator; | ||||||
|  |  | ||||||
|  |   })(BaseGenerator); | ||||||
|  |  | ||||||
|  | }).call(this); | ||||||
|  |  | ||||||
|  | //# sourceMappingURL=xml-generator.js.map | ||||||
							
								
								
									
										33
									
								
								dist/generators/yaml-generator.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								dist/generators/yaml-generator.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | |||||||
|  |  | ||||||
|  | /** | ||||||
|  | Definition of the YAMLGenerator class. | ||||||
|  | @module yaml-generator.js | ||||||
|  | @license MIT. See LICENSE.md for details. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | (function() { | ||||||
|  |   var TemplateGenerator, YAMLGenerator, | ||||||
|  |     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; | ||||||
|  |  | ||||||
|  |   TemplateGenerator = require('./template-generator'); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |   YamlGenerator generates a YAML-formatted resume via TemplateGenerator. | ||||||
|  |    */ | ||||||
|  |  | ||||||
|  |   module.exports = YAMLGenerator = (function(superClass) { | ||||||
|  |     extend(YAMLGenerator, superClass); | ||||||
|  |  | ||||||
|  |     function YAMLGenerator() { | ||||||
|  |       YAMLGenerator.__super__.constructor.call(this, 'yml', 'yml'); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return YAMLGenerator; | ||||||
|  |  | ||||||
|  |   })(TemplateGenerator); | ||||||
|  |  | ||||||
|  | }).call(this); | ||||||
|  |  | ||||||
|  | //# sourceMappingURL=yaml-generator.js.map | ||||||
							
								
								
									
										71
									
								
								dist/helpers/block-helpers.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								dist/helpers/block-helpers.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,71 @@ | |||||||
|  |  | ||||||
|  | /** | ||||||
|  | Block helper definitions for HackMyResume / FluentCV. | ||||||
|  | @license MIT. See LICENSE.md for details. | ||||||
|  | @module helpers/generic-helpers | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | (function() { | ||||||
|  |   var BlockHelpers, HMSTATUS, LO, _, unused; | ||||||
|  |  | ||||||
|  |   HMSTATUS = require('../core/status-codes'); | ||||||
|  |  | ||||||
|  |   LO = require('lodash'); | ||||||
|  |  | ||||||
|  |   _ = require('underscore'); | ||||||
|  |  | ||||||
|  |   unused = require('../utils/string'); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   /** Block helper function definitions. */ | ||||||
|  |  | ||||||
|  |   BlockHelpers = module.exports = { | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |     Emit the enclosed content if the resume has a section with | ||||||
|  |     the specified name. Otherwise, emit an empty string ''. | ||||||
|  |      */ | ||||||
|  |     section: function(title, options) { | ||||||
|  |       var obj, ret; | ||||||
|  |       title = title.trim().toLowerCase(); | ||||||
|  |       obj = LO.get(this.r, title); | ||||||
|  |       ret = ''; | ||||||
|  |       if (obj) { | ||||||
|  |         if (_.isArray(obj)) { | ||||||
|  |           if (obj.length) { | ||||||
|  |             ret = options.fn(this); | ||||||
|  |           } | ||||||
|  |         } else if (_.isObject(obj)) { | ||||||
|  |           if ((obj.history && obj.history.length) || (obj.sets && obj.sets.length)) { | ||||||
|  |             ret = options.fn(this); | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       return ret; | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |     Emit the enclosed content if the resume has the named | ||||||
|  |     property or subproperty. | ||||||
|  |      */ | ||||||
|  |     has: function(title, options) { | ||||||
|  |       title = title && title.trim().toLowerCase(); | ||||||
|  |       if (LO.get(this.r, title)) { | ||||||
|  |         return options.fn(this); | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |     Return true if either value is truthy. | ||||||
|  |     @method either | ||||||
|  |      */ | ||||||
|  |     either: function(lhs, rhs, options) { | ||||||
|  |       if (lhs || rhs) { | ||||||
|  |         return options.fn(this); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  | }).call(this); | ||||||
|  |  | ||||||
|  | //# sourceMappingURL=block-helpers.js.map | ||||||
							
								
								
									
										66
									
								
								dist/helpers/console-helpers.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								dist/helpers/console-helpers.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,66 @@ | |||||||
|  |  | ||||||
|  | /** | ||||||
|  | Generic template helper definitions for command-line output. | ||||||
|  | @module console-helpers.js | ||||||
|  | @license MIT. See LICENSE.md for details. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | (function() { | ||||||
|  |   var CHALK, LO, PAD, _, consoleFormatHelpers; | ||||||
|  |  | ||||||
|  |   PAD = require('string-padding'); | ||||||
|  |  | ||||||
|  |   LO = require('lodash'); | ||||||
|  |  | ||||||
|  |   CHALK = require('chalk'); | ||||||
|  |  | ||||||
|  |   _ = require('underscore'); | ||||||
|  |  | ||||||
|  |   require('../utils/string'); | ||||||
|  |  | ||||||
|  |   consoleFormatHelpers = module.exports = { | ||||||
|  |     v: function(val, defaultVal, padding, style) { | ||||||
|  |       var retVal, spaces; | ||||||
|  |       retVal = val === null || val === void 0 ? defaultVal : val; | ||||||
|  |       spaces = 0; | ||||||
|  |       if (String.is(padding)) { | ||||||
|  |         spaces = parseInt(padding, 10); | ||||||
|  |         if (isNaN(spaces)) { | ||||||
|  |           spaces = 0; | ||||||
|  |         } | ||||||
|  |       } else if (_.isNumber(padding)) { | ||||||
|  |         spaces = padding; | ||||||
|  |       } | ||||||
|  |       if (spaces !== 0) { | ||||||
|  |         retVal = PAD(retVal, Math.abs(spaces), null, spaces > 0 ? PAD.LEFT : PAD.RIGHT); | ||||||
|  |       } | ||||||
|  |       if (style && String.is(style)) { | ||||||
|  |         retVal = LO.get(CHALK, style)(retVal); | ||||||
|  |       } | ||||||
|  |       return retVal; | ||||||
|  |     }, | ||||||
|  |     gapLength: function(val) { | ||||||
|  |       if (val < 35) { | ||||||
|  |         return CHALK.green.bold(val); | ||||||
|  |       } else if (val < 95) { | ||||||
|  |         return CHALK.yellow.bold(val); | ||||||
|  |       } else { | ||||||
|  |         return CHALK.red.bold(val); | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     style: function(val, style) { | ||||||
|  |       return LO.get(CHALK, style)(val); | ||||||
|  |     }, | ||||||
|  |     isPlural: function(val, options) { | ||||||
|  |       if (val > 1) { | ||||||
|  |         return options.fn(this); | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     pad: function(val, spaces) { | ||||||
|  |       return PAD(val, Math.abs(spaces), null, spaces > 0 ? PAD.LEFT : PAD.RIGHT); | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  | }).call(this); | ||||||
|  |  | ||||||
|  | //# sourceMappingURL=console-helpers.js.map | ||||||
							
								
								
									
										633
									
								
								dist/helpers/generic-helpers.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										633
									
								
								dist/helpers/generic-helpers.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,633 @@ | |||||||
|  |  | ||||||
|  | /** | ||||||
|  | Generic template helper definitions for HackMyResume / FluentCV. | ||||||
|  | @license MIT. See LICENSE.md for details. | ||||||
|  | @module helpers/generic-helpers | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | (function() { | ||||||
|  |   var FS, FluentDate, GenericHelpers, H2W, HMSTATUS, LO, MD, PATH, XML, _, _fromTo, _reportError, moment, printf, skillLevelToIndex, unused; | ||||||
|  |  | ||||||
|  |   MD = require('marked'); | ||||||
|  |  | ||||||
|  |   H2W = require('../utils/html-to-wpml'); | ||||||
|  |  | ||||||
|  |   XML = require('xml-escape'); | ||||||
|  |  | ||||||
|  |   FluentDate = require('../core/fluent-date'); | ||||||
|  |  | ||||||
|  |   HMSTATUS = require('../core/status-codes'); | ||||||
|  |  | ||||||
|  |   moment = require('moment'); | ||||||
|  |  | ||||||
|  |   FS = require('fs'); | ||||||
|  |  | ||||||
|  |   LO = require('lodash'); | ||||||
|  |  | ||||||
|  |   PATH = require('path'); | ||||||
|  |  | ||||||
|  |   printf = require('printf'); | ||||||
|  |  | ||||||
|  |   _ = require('underscore'); | ||||||
|  |  | ||||||
|  |   unused = require('../utils/string'); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   /** Generic template helper function definitions. */ | ||||||
|  |  | ||||||
|  |   GenericHelpers = module.exports = { | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |     Emit a formatted string representing the specified datetime. | ||||||
|  |     Convert the input date to the specified format through Moment.js. If date is | ||||||
|  |     valid, return the formatted date string. If date is null, undefined, or other | ||||||
|  |     falsy value, return the value of the 'fallback' parameter, if specified, or | ||||||
|  |     null if no fallback was specified. If date is invalid, but not null/undefined/ | ||||||
|  |     falsy, return it as-is. | ||||||
|  |     @param {string|Moment} datetime A date value. | ||||||
|  |     @param {string} [dtFormat='YYYY-MM'] The desired datetime format. Must be a | ||||||
|  |     Moment.js-compatible datetime format. | ||||||
|  |     @param {string|Moment} fallback A fallback value to use if the specified date | ||||||
|  |     is null, undefined, or falsy. | ||||||
|  |      */ | ||||||
|  |     formatDate: function(datetime, dtFormat, fallback) { | ||||||
|  |       var momentDate; | ||||||
|  |       if (datetime == null) { | ||||||
|  |         datetime = void 0; | ||||||
|  |       } | ||||||
|  |       if (dtFormat == null) { | ||||||
|  |         dtFormat = 'YYYY-MM'; | ||||||
|  |       } | ||||||
|  |       if (datetime && moment.isMoment(datetime)) { | ||||||
|  |         return datetime.format(dtFormat); | ||||||
|  |       } | ||||||
|  |       if (String.is(datetime)) { | ||||||
|  |         momentDate = moment(datetime, dtFormat); | ||||||
|  |         if (momentDate.isValid()) { | ||||||
|  |           return momentDate.format(dtFormat); | ||||||
|  |         } | ||||||
|  |         momentDate = moment(datetime); | ||||||
|  |         if (momentDate.isValid()) { | ||||||
|  |           return momentDate.format(dtFormat); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       return datetime || (typeof fallback === 'string' ? fallback : (fallback === true ? 'Present' : '')); | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |     Emit a formatted string representing the specified datetime. | ||||||
|  |     @param {string} dateValue A raw date value from the FRESH or JRS resume. | ||||||
|  |     @param {string} [dateFormat='YYYY-MM'] The desired datetime format. Must be | ||||||
|  |     compatible with Moment.js datetime formats. | ||||||
|  |     @param {string} [dateDefault=null] The default date value to use if the dateValue | ||||||
|  |     parameter is null, undefined, or falsy. | ||||||
|  |      */ | ||||||
|  |     date: function(dateValue, dateFormat, dateDefault) { | ||||||
|  |       var dateValueMoment, dateValueSafe, reserved; | ||||||
|  |       if (!dateDefault || !String.is(dateDefault)) { | ||||||
|  |         dateDefault = 'Current'; | ||||||
|  |       } | ||||||
|  |       if (!dateFormat || !String.is(dateFormat)) { | ||||||
|  |         dateFormat = 'YYYY-MM'; | ||||||
|  |       } | ||||||
|  |       if (!dateValue || !String.is(dateValue)) { | ||||||
|  |         dateValue = null; | ||||||
|  |       } | ||||||
|  |       if (!dateValue) { | ||||||
|  |         return dateDefault; | ||||||
|  |       } | ||||||
|  |       reserved = ['current', 'present', 'now']; | ||||||
|  |       dateValueSafe = dateValue.trim().toLowerCase(); | ||||||
|  |       if (_.contains(reserved, dateValueSafe)) { | ||||||
|  |         return dateValue; | ||||||
|  |       } | ||||||
|  |       dateValueMoment = moment(dateValue, dateFormat); | ||||||
|  |       if (dateValueMoment.isValid()) { | ||||||
|  |         return dateValueMoment.format(dateFormat); | ||||||
|  |       } | ||||||
|  |       return dateValue; | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |     Given a resume sub-object with a start/end date, format a representation of | ||||||
|  |     the date range. | ||||||
|  |      */ | ||||||
|  |     dateRange: function(obj, fmt, sep, fallback) { | ||||||
|  |       if (!obj) { | ||||||
|  |         return ''; | ||||||
|  |       } | ||||||
|  |       return _fromTo(obj.start, obj.end, fmt, sep, fallback); | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |     Format a from/to date range for display. | ||||||
|  |     @method toFrom | ||||||
|  |      */ | ||||||
|  |     fromTo: function() { | ||||||
|  |       return _fromTo.apply(this, arguments); | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |     Return a named color value as an RRGGBB string. | ||||||
|  |     @method toFrom | ||||||
|  |      */ | ||||||
|  |     color: function(colorName, colorDefault) { | ||||||
|  |       var ret; | ||||||
|  |       if (!(colorName && colorName.trim())) { | ||||||
|  |         return _reportError(HMSTATUS.invalidHelperUse, { | ||||||
|  |           helper: 'fontList', | ||||||
|  |           error: HMSTATUS.missingParam, | ||||||
|  |           expected: 'name' | ||||||
|  |         }); | ||||||
|  |       } else { | ||||||
|  |         if (!GenericHelpers.theme.colors) { | ||||||
|  |           return colorDefault; | ||||||
|  |         } | ||||||
|  |         ret = GenericHelpers.theme.colors[colorName]; | ||||||
|  |         if (!(ret && ret.trim())) { | ||||||
|  |           return colorDefault; | ||||||
|  |         } | ||||||
|  |         return ret; | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |     Emit the size of the specified named font. | ||||||
|  |     @param key {String} A named style from the "fonts" section of the theme's | ||||||
|  |     theme.json file. For example: 'default' or 'heading1'. | ||||||
|  |      */ | ||||||
|  |     fontSize: function(key, defSize, units) { | ||||||
|  |       var fontSpec, hasDef, ret; | ||||||
|  |       ret = ''; | ||||||
|  |       hasDef = defSize && (String.is(defSize) || _.isNumber(defSize)); | ||||||
|  |       if (!(key && key.trim())) { | ||||||
|  |         _reportError(HMSTATUS.invalidHelperUse, { | ||||||
|  |           helper: 'fontSize', | ||||||
|  |           error: HMSTATUS.missingParam, | ||||||
|  |           expected: 'key' | ||||||
|  |         }); | ||||||
|  |         return ret; | ||||||
|  |       } else if (GenericHelpers.theme.fonts) { | ||||||
|  |         fontSpec = LO.get(GenericHelpers.theme.fonts, this.format + '.' + key); | ||||||
|  |         if (!fontSpec) { | ||||||
|  |           if (GenericHelpers.theme.fonts.all) { | ||||||
|  |             fontSpec = GenericHelpers.theme.fonts.all[key]; | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |         if (fontSpec) { | ||||||
|  |           if (String.is(fontSpec)) { | ||||||
|  |  | ||||||
|  |           } else if (_.isArray(fontSpec)) { | ||||||
|  |             if (!String.is(fontSpec[0])) { | ||||||
|  |               ret = fontSpec[0].size; | ||||||
|  |             } | ||||||
|  |           } else { | ||||||
|  |             ret = fontSpec.size; | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       if (!ret) { | ||||||
|  |         if (hasDef) { | ||||||
|  |           ret = defSize; | ||||||
|  |         } else { | ||||||
|  |           _reportError(HMSTATUS.invalidHelperUse, { | ||||||
|  |             helper: 'fontSize', | ||||||
|  |             error: HMSTATUS.missingParam, | ||||||
|  |             expected: 'defSize' | ||||||
|  |           }); | ||||||
|  |           ret = ''; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       return ret; | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |     Emit the font face (such as 'Helvetica' or 'Calibri') associated with the | ||||||
|  |     provided key. | ||||||
|  |     @param key {String} A named style from the "fonts" section of the theme's | ||||||
|  |     theme.json file. For example: 'default' or 'heading1'. | ||||||
|  |     @param defFont {String} The font to use if the specified key isn't present. | ||||||
|  |     Can be any valid font-face name such as 'Helvetica Neue' or 'Calibri'. | ||||||
|  |      */ | ||||||
|  |     fontFace: function(key, defFont) { | ||||||
|  |       var fontSpec, hasDef, ret; | ||||||
|  |       ret = ''; | ||||||
|  |       hasDef = defFont && String.is(defFont); | ||||||
|  |       if (!(key && key.trim())) { | ||||||
|  |         _reportError(HMSTATUS.invalidHelperUse, { | ||||||
|  |           helper: 'fontFace', | ||||||
|  |           error: HMSTATUS.missingParam, | ||||||
|  |           expected: 'key' | ||||||
|  |         }); | ||||||
|  |         return ret; | ||||||
|  |       } else if (GenericHelpers.theme.fonts) { | ||||||
|  |         fontSpec = LO.get(GenericHelpers.theme.fonts, this.format + '.' + key); | ||||||
|  |         if (!fontSpec) { | ||||||
|  |           if (GenericHelpers.theme.fonts.all) { | ||||||
|  |             fontSpec = GenericHelpers.theme.fonts.all[key]; | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |         if (fontSpec) { | ||||||
|  |           if (String.is(fontSpec)) { | ||||||
|  |             ret = fontSpec; | ||||||
|  |           } else if (_.isArray(fontSpec)) { | ||||||
|  |             ret = String.is(fontSpec[0]) ? fontSpec[0] : fontSpec[0].name; | ||||||
|  |           } else { | ||||||
|  |             ret = fontSpec.name; | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       if (!(ret && ret.trim())) { | ||||||
|  |         ret = defFont; | ||||||
|  |         if (!hasDef) { | ||||||
|  |           _reportError(HMSTATUS.invalidHelperUse, { | ||||||
|  |             helper: 'fontFace', | ||||||
|  |             error: HMSTATUS.missingParam, | ||||||
|  |             expected: 'defFont' | ||||||
|  |           }); | ||||||
|  |           ret = ''; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       return ret; | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |     Emit a comma-delimited list of font names suitable associated with the | ||||||
|  |     provided key. | ||||||
|  |     @param key {String} A named style from the "fonts" section of the theme's | ||||||
|  |     theme.json file. For example: 'default' or 'heading1'. | ||||||
|  |     @param defFontList {Array} The font list to use if the specified key isn't | ||||||
|  |     present. Can be an array of valid font-face name such as 'Helvetica Neue' | ||||||
|  |     or 'Calibri'. | ||||||
|  |     @param sep {String} The default separator to use in the rendered output. | ||||||
|  |     Defaults to ", " (comma with a space). | ||||||
|  |      */ | ||||||
|  |     fontList: function(key, defFontList, sep) { | ||||||
|  |       var fontSpec, hasDef, ret; | ||||||
|  |       ret = ''; | ||||||
|  |       hasDef = defFontList && String.is(defFontList); | ||||||
|  |       if (!(key && key.trim())) { | ||||||
|  |         _reportError(HMSTATUS.invalidHelperUse, { | ||||||
|  |           helper: 'fontList', | ||||||
|  |           error: HMSTATUS.missingParam, | ||||||
|  |           expected: 'key' | ||||||
|  |         }); | ||||||
|  |       } else if (GenericHelpers.theme.fonts) { | ||||||
|  |         fontSpec = LO.get(GenericHelpers.theme.fonts, this.format + '.' + key); | ||||||
|  |         if (!fontSpec) { | ||||||
|  |           if (GenericHelpers.theme.fonts.all) { | ||||||
|  |             fontSpec = GenericHelpers.theme.fonts.all[key]; | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |         if (fontSpec) { | ||||||
|  |           if (String.is(fontSpec)) { | ||||||
|  |             ret = fontSpec; | ||||||
|  |           } else if (_.isArray(fontSpec)) { | ||||||
|  |             fontSpec = fontSpec.map(function(ff) { | ||||||
|  |               return "'" + (String.is(ff) ? ff : ff.name) + "'"; | ||||||
|  |             }); | ||||||
|  |             ret = fontSpec.join(sep === void 0 ? ', ' : sep || ''); | ||||||
|  |           } else { | ||||||
|  |             ret = fontSpec.name; | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       if (!(ret && ret.trim())) { | ||||||
|  |         if (!hasDef) { | ||||||
|  |           _reportError(HMSTATUS.invalidHelperUse, { | ||||||
|  |             helper: 'fontList', | ||||||
|  |             error: HMSTATUS.missingParam, | ||||||
|  |             expected: 'defFontList' | ||||||
|  |           }); | ||||||
|  |           ret = ''; | ||||||
|  |         } else { | ||||||
|  |           ret = defFontList; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       return ret; | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |     Capitalize the first letter of the word. TODO: Rename | ||||||
|  |     @method section | ||||||
|  |      */ | ||||||
|  |     camelCase: function(val) { | ||||||
|  |       val = (val && val.trim()) || ''; | ||||||
|  |       if (val) { | ||||||
|  |         return val.charAt(0).toUpperCase() + val.slice(1); | ||||||
|  |       } else { | ||||||
|  |         return val; | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |     Display a user-overridable section title for a FRESH resume theme. Use this in | ||||||
|  |     lieue of hard-coding section titles. | ||||||
|  |      | ||||||
|  |     Usage: | ||||||
|  |      | ||||||
|  |         {{sectionTitle "sectionName"}} | ||||||
|  |         {{sectionTitle "sectionName" "sectionTitle"}} | ||||||
|  |      | ||||||
|  |     Example: | ||||||
|  |      | ||||||
|  |         {{sectionTitle "Education"}} | ||||||
|  |         {{sectionTitle "Employment" "Project History"}} | ||||||
|  |      | ||||||
|  |     @param sect_name The name of the section being title. Must be one of the | ||||||
|  |     top-level FRESH resume sections ("info", "education", "employment", etc.). | ||||||
|  |     @param sect_title The theme-specified section title. May be replaced by the | ||||||
|  |     user. | ||||||
|  |     @method sectionTitle | ||||||
|  |      */ | ||||||
|  |     sectionTitle: function(sname, stitle) { | ||||||
|  |       stitle = (stitle && String.is(stitle) && stitle) || sname; | ||||||
|  |       return (this.opts.stitles && this.opts.stitles[sname.toLowerCase().trim()]) || stitle; | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     /** Convert inline Markdown to inline WordProcessingML. */ | ||||||
|  |     wpml: function(txt, inline) { | ||||||
|  |       if (!txt) { | ||||||
|  |         return ''; | ||||||
|  |       } | ||||||
|  |       inline = (inline && !inline.hash) || false; | ||||||
|  |       txt = XML(txt.trim()); | ||||||
|  |       txt = inline ? MD(txt).replace(/^\s*<p>|<\/p>\s*$/gi, '') : MD(txt); | ||||||
|  |       txt = H2W(txt); | ||||||
|  |       return txt; | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |     Emit a conditional link. | ||||||
|  |     @method link | ||||||
|  |      */ | ||||||
|  |     link: function(text, url) { | ||||||
|  |       if (url && url.trim()) { | ||||||
|  |         return '<a href="' + url + '">' + text + '</a>'; | ||||||
|  |       } else { | ||||||
|  |         return text; | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |     Return the last word of the specified text. | ||||||
|  |     @method lastWord | ||||||
|  |      */ | ||||||
|  |     lastWord: function(txt) { | ||||||
|  |       if (txt && txt.trim()) { | ||||||
|  |         return _.last(txt.split(' ')); | ||||||
|  |       } else { | ||||||
|  |         return ''; | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |     Convert a skill level to an RGB color triplet. TODO: refactor | ||||||
|  |     @method skillColor | ||||||
|  |     @param lvl Input skill level. Skill level can be expressed as a string | ||||||
|  |     ("beginner", "intermediate", etc.), as an integer (1,5,etc), as a string | ||||||
|  |     integer ("1", "5", etc.), or as an RRGGBB color triplet ('#C00000', | ||||||
|  |     '#FFFFAA'). | ||||||
|  |      */ | ||||||
|  |     skillColor: function(lvl) { | ||||||
|  |       var idx, skillColors; | ||||||
|  |       idx = skillLevelToIndex(lvl); | ||||||
|  |       skillColors = (this.theme && this.theme.palette && this.theme.palette.skillLevels) || ['#FFFFFF', '#5CB85C', '#F1C40F', '#428BCA', '#C00000']; | ||||||
|  |       return skillColors[idx]; | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |     Return an appropriate height. TODO: refactor | ||||||
|  |     @method lastWord | ||||||
|  |      */ | ||||||
|  |     skillHeight: function(lvl) { | ||||||
|  |       var idx; | ||||||
|  |       idx = skillLevelToIndex(lvl); | ||||||
|  |       return ['38.25', '30', '16', '8', '0'][idx]; | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |     Return all but the last word of the input text. | ||||||
|  |     @method initialWords | ||||||
|  |      */ | ||||||
|  |     initialWords: function(txt) { | ||||||
|  |       if (txt && txt.trim()) { | ||||||
|  |         return _.initial(txt.split(' ')).join(' '); | ||||||
|  |       } else { | ||||||
|  |         return ''; | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |     Trim the protocol (http or https) from a URL/ | ||||||
|  |     @method trimURL | ||||||
|  |      */ | ||||||
|  |     trimURL: function(url) { | ||||||
|  |       if (url && url.trim()) { | ||||||
|  |         return url.trim().replace(/^https?:\/\//i, ''); | ||||||
|  |       } else { | ||||||
|  |         return ''; | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |     Convert text to lowercase. | ||||||
|  |     @method toLower | ||||||
|  |      */ | ||||||
|  |     toLower: function(txt) { | ||||||
|  |       if (txt && txt.trim()) { | ||||||
|  |         return txt.toLowerCase(); | ||||||
|  |       } else { | ||||||
|  |         return ''; | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |     Convert text to lowercase. | ||||||
|  |     @method toLower | ||||||
|  |      */ | ||||||
|  |     toUpper: function(txt) { | ||||||
|  |       if (txt && txt.trim()) { | ||||||
|  |         return txt.toUpperCase(); | ||||||
|  |       } else { | ||||||
|  |         return ''; | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |     Conditional stylesheet link. Creates a link to the specified stylesheet with | ||||||
|  |     <link> or embeds the styles inline with <style></style>, depending on the | ||||||
|  |     theme author's and user's preferences. | ||||||
|  |     @param url {String} The path to the CSS file. | ||||||
|  |     @param linkage {String} The default link method. Can be either `embed` or | ||||||
|  |     `link`. If omitted, defaults to `embed`. Can be overridden by the `--css` | ||||||
|  |     command-line switch. | ||||||
|  |      */ | ||||||
|  |     styleSheet: function(url, linkage) { | ||||||
|  |       var rawCss, renderedCss, ret; | ||||||
|  |       linkage = this.opts.css || linkage || 'embed'; | ||||||
|  |       ret = ''; | ||||||
|  |       if (linkage === 'link') { | ||||||
|  |         ret = printf('<link href="%s" rel="stylesheet" type="text/css">', url); | ||||||
|  |       } else { | ||||||
|  |         rawCss = FS.readFileSync(PATH.join(this.opts.themeObj.folder, '/src/', url), 'utf8'); | ||||||
|  |         renderedCss = this.engine.generateSimple(this, rawCss); | ||||||
|  |         ret = printf('<style>%s</style>', renderedCss); | ||||||
|  |       } | ||||||
|  |       if (this.opts.themeObj.inherits && this.opts.themeObj.inherits.html && this.format === 'html') { | ||||||
|  |         ret += linkage === 'link' ? '<link href="' + this.opts.themeObj.overrides.path + '" rel="stylesheet" type="text/css">' : '<style>' + this.opts.themeObj.overrides.data + '</style>'; | ||||||
|  |       } | ||||||
|  |       return ret; | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |     Perform a generic comparison. | ||||||
|  |     See: http://doginthehat.com.au/2012/02/comparison-block-helper-for-handlebars-templates | ||||||
|  |     @method compare | ||||||
|  |      */ | ||||||
|  |     compare: function(lvalue, rvalue, options) { | ||||||
|  |       var operator, operators, result; | ||||||
|  |       if (arguments.length < 3) { | ||||||
|  |         throw new Error("Template helper 'compare' needs 2 parameters"); | ||||||
|  |       } | ||||||
|  |       operator = options.hash.operator || "=="; | ||||||
|  |       operators = { | ||||||
|  |         '==': function(l, r) { | ||||||
|  |           return l === r; | ||||||
|  |         }, | ||||||
|  |         '===': function(l, r) { | ||||||
|  |           return l === r; | ||||||
|  |         }, | ||||||
|  |         '!=': function(l, r) { | ||||||
|  |           return l !== r; | ||||||
|  |         }, | ||||||
|  |         '<': function(l, r) { | ||||||
|  |           return l < r; | ||||||
|  |         }, | ||||||
|  |         '>': function(l, r) { | ||||||
|  |           return l > r; | ||||||
|  |         }, | ||||||
|  |         '<=': function(l, r) { | ||||||
|  |           return l <= r; | ||||||
|  |         }, | ||||||
|  |         '>=': function(l, r) { | ||||||
|  |           return l >= r; | ||||||
|  |         }, | ||||||
|  |         'typeof': function(l, r) { | ||||||
|  |           return typeof l === r; | ||||||
|  |         } | ||||||
|  |       }; | ||||||
|  |       if (!operators[operator]) { | ||||||
|  |         throw new Error("Helper 'compare' doesn't know the operator " + operator); | ||||||
|  |       } | ||||||
|  |       result = operators[operator](lvalue, rvalue); | ||||||
|  |       if (result) { | ||||||
|  |         return options.fn(this); | ||||||
|  |       } else { | ||||||
|  |         return options.inverse(this); | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     pad: function(stringOrArray, padAmount, unused) { | ||||||
|  |       var PAD, ret; | ||||||
|  |       stringOrArray = stringOrArray || ''; | ||||||
|  |       padAmount = padAmount || 0; | ||||||
|  |       ret = ''; | ||||||
|  |       PAD = require('string-padding'); | ||||||
|  |       if (!String.is(stringOrArray)) { | ||||||
|  |         ret = stringOrArray.map(function(line) { | ||||||
|  |           return PAD(line, line.length + Math.abs(padAmount), null, padAmount < 0 ? PAD.LEFT : PAD.RIGHT); | ||||||
|  |         }).join('\n'); | ||||||
|  |       } else { | ||||||
|  |         ret = PAD(stringOrArray, stringOrArray.length + Math.abs(padAmount), null, padAmount < 0 ? PAD.LEFT : PAD.RIGHT); | ||||||
|  |       } | ||||||
|  |       return ret; | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |   Report an error to the outside world without throwing an exception. Currently | ||||||
|  |   relies on kludging the running verb into. opts. | ||||||
|  |    */ | ||||||
|  |  | ||||||
|  |   _reportError = function(code, params) { | ||||||
|  |     return GenericHelpers.opts.errHandler.err(code, params); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |   Format a from/to date range for display. | ||||||
|  |    */ | ||||||
|  |  | ||||||
|  |   _fromTo = function(dateA, dateB, fmt, sep, fallback) { | ||||||
|  |     var dateATrim, dateBTrim, dateFrom, dateTemp, dateTo, reserved; | ||||||
|  |     if (moment.isMoment(dateA) || moment.isMoment(dateB)) { | ||||||
|  |       _reportError(HMSTATUS.invalidHelperUse, { | ||||||
|  |         helper: 'dateRange' | ||||||
|  |       }); | ||||||
|  |       return ''; | ||||||
|  |     } | ||||||
|  |     dateFrom = null; | ||||||
|  |     dateTo = null; | ||||||
|  |     dateTemp = null; | ||||||
|  |     dateA = dateA || ''; | ||||||
|  |     dateB = dateB || ''; | ||||||
|  |     dateATrim = dateA.trim().toLowerCase(); | ||||||
|  |     dateBTrim = dateB.trim().toLowerCase(); | ||||||
|  |     reserved = ['current', 'present', 'now', '']; | ||||||
|  |     fmt = (fmt && String.is(fmt) && fmt) || 'YYYY-MM'; | ||||||
|  |     sep = (sep && String.is(sep) && sep) || ' — '; | ||||||
|  |     if (_.contains(reserved, dateATrim)) { | ||||||
|  |       dateFrom = fallback || '???'; | ||||||
|  |     } else { | ||||||
|  |       dateTemp = FluentDate.fmt(dateA); | ||||||
|  |       dateFrom = dateTemp.format(fmt); | ||||||
|  |     } | ||||||
|  |     if (_.contains(reserved, dateBTrim)) { | ||||||
|  |       dateTo = fallback || 'Present'; | ||||||
|  |     } else { | ||||||
|  |       dateTemp = FluentDate.fmt(dateB); | ||||||
|  |       dateTo = dateTemp.format(fmt); | ||||||
|  |     } | ||||||
|  |     if (dateFrom && dateTo) { | ||||||
|  |       return dateFrom + sep + dateTo; | ||||||
|  |     } else if (dateFrom || dateTo) { | ||||||
|  |       return dateFrom || dateTo; | ||||||
|  |     } | ||||||
|  |     return ''; | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   skillLevelToIndex = function(lvl) { | ||||||
|  |     var idx, intVal; | ||||||
|  |     idx = 0; | ||||||
|  |     if (String.is(lvl)) { | ||||||
|  |       lvl = lvl.trim().toLowerCase(); | ||||||
|  |       intVal = parseInt(lvl); | ||||||
|  |       if (isNaN(intVal)) { | ||||||
|  |         switch (lvl) { | ||||||
|  |           case 'beginner': | ||||||
|  |             idx = 1; | ||||||
|  |             break; | ||||||
|  |           case 'intermediate': | ||||||
|  |             idx = 2; | ||||||
|  |             break; | ||||||
|  |           case 'advanced': | ||||||
|  |             idx = 3; | ||||||
|  |             break; | ||||||
|  |           case 'master': | ||||||
|  |             idx = 4; | ||||||
|  |         } | ||||||
|  |       } else { | ||||||
|  |         idx = Math.min(intVal / 2, 4); | ||||||
|  |         idx = Math.max(0, idx); | ||||||
|  |       } | ||||||
|  |     } else { | ||||||
|  |       idx = Math.min(lvl / 2, 4); | ||||||
|  |       idx = Math.max(0, idx); | ||||||
|  |     } | ||||||
|  |     return idx; | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  | }).call(this); | ||||||
|  |  | ||||||
|  | //# sourceMappingURL=generic-helpers.js.map | ||||||
							
								
								
									
										48
									
								
								dist/helpers/handlebars-helpers.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								dist/helpers/handlebars-helpers.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | |||||||
|  |  | ||||||
|  | /** | ||||||
|  | Template helper definitions for Handlebars. | ||||||
|  | @license MIT. See LICENSE.md for details. | ||||||
|  | @module handlebars-helpers.js | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | (function() { | ||||||
|  |   var HANDLEBARS, _, blockHelpers, helpers; | ||||||
|  |  | ||||||
|  |   HANDLEBARS = require('handlebars'); | ||||||
|  |  | ||||||
|  |   _ = require('underscore'); | ||||||
|  |  | ||||||
|  |   helpers = require('./generic-helpers'); | ||||||
|  |  | ||||||
|  |   blockHelpers = require('./block-helpers'); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |   Register useful Handlebars helpers. | ||||||
|  |   @method registerHelpers | ||||||
|  |    */ | ||||||
|  |  | ||||||
|  |   module.exports = function(theme, opts) { | ||||||
|  |     var wrappedHelpers; | ||||||
|  |     helpers.theme = theme; | ||||||
|  |     helpers.opts = opts; | ||||||
|  |     helpers.type = 'handlebars'; | ||||||
|  |     wrappedHelpers = _.mapObject(helpers, function(hVal, hKey) { | ||||||
|  |       if (_.isFunction(hVal)) { | ||||||
|  |         _.wrap(hVal, function(func) { | ||||||
|  |           var args; | ||||||
|  |           args = Array.prototype.slice.call(arguments); | ||||||
|  |           args.shift(); | ||||||
|  |           args.pop(); | ||||||
|  |           return func.apply(this, args); | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  |       return hVal; | ||||||
|  |     }, this); | ||||||
|  |     HANDLEBARS.registerHelper(wrappedHelpers); | ||||||
|  |     HANDLEBARS.registerHelper(blockHelpers); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  | }).call(this); | ||||||
|  |  | ||||||
|  | //# sourceMappingURL=handlebars-helpers.js.map | ||||||
							
								
								
									
										38
									
								
								dist/helpers/underscore-helpers.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								dist/helpers/underscore-helpers.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | |||||||
|  |  | ||||||
|  | /** | ||||||
|  | Template helper definitions for Underscore. | ||||||
|  | @license MIT. See LICENSE.md for details. | ||||||
|  | @module handlebars-helpers.js | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | (function() { | ||||||
|  |   var HANDLEBARS, _, helpers; | ||||||
|  |  | ||||||
|  |   HANDLEBARS = require('handlebars'); | ||||||
|  |  | ||||||
|  |   _ = require('underscore'); | ||||||
|  |  | ||||||
|  |   helpers = require('./generic-helpers'); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |   Register useful Underscore helpers. | ||||||
|  |   @method registerHelpers | ||||||
|  |    */ | ||||||
|  |  | ||||||
|  |   module.exports = function(theme, opts, cssInfo, ctx, eng) { | ||||||
|  |     helpers.theme = theme; | ||||||
|  |     helpers.opts = opts; | ||||||
|  |     helpers.cssInfo = cssInfo; | ||||||
|  |     helpers.engine = eng; | ||||||
|  |     ctx.h = helpers; | ||||||
|  |     _.each(helpers, function(hVal, hKey) { | ||||||
|  |       if (_.isFunction(hVal)) { | ||||||
|  |         return _.bind(hVal, ctx); | ||||||
|  |       } | ||||||
|  |     }, this); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  | }).call(this); | ||||||
|  |  | ||||||
|  | //# sourceMappingURL=underscore-helpers.js.map | ||||||
							
								
								
									
										54
									
								
								dist/index.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										54
									
								
								dist/index.js
									
									
									
									
										vendored
									
									
								
							| @@ -1,22 +1,48 @@ | |||||||
| #! /usr/bin/env node |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| /** | /** | ||||||
| Command-line interface (CLI) for HackMyResume. | External API surface for HackMyResume. | ||||||
| @license MIT. See LICENSE.md for details. | @license MIT. See LICENSE.md for details. | ||||||
| @module index.js | @module hackmycore/index | ||||||
| */ |  */ | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /** API facade for HackMyResume. */ | ||||||
|  |  | ||||||
| try { | (function() { | ||||||
|  |   module.exports = { | ||||||
|  |     verbs: { | ||||||
|  |       build: require('./verbs/build'), | ||||||
|  |       analyze: require('./verbs/analyze'), | ||||||
|  |       validate: require('./verbs/validate'), | ||||||
|  |       convert: require('./verbs/convert'), | ||||||
|  |       "new": require('./verbs/create'), | ||||||
|  |       peek: require('./verbs/peek') | ||||||
|  |     }, | ||||||
|  |     alias: { | ||||||
|  |       generate: require('./verbs/build'), | ||||||
|  |       create: require('./verbs/create') | ||||||
|  |     }, | ||||||
|  |     options: require('./core/default-options'), | ||||||
|  |     formats: require('./core/default-formats'), | ||||||
|  |     Sheet: require('./core/fresh-resume'), | ||||||
|  |     FRESHResume: require('./core/fresh-resume'), | ||||||
|  |     JRSResume: require('./core/jrs-resume'), | ||||||
|  |     FRESHTheme: require('./core/fresh-theme'), | ||||||
|  |     JRSTheme: require('./core/jrs-theme'), | ||||||
|  |     ResumeFactory: require('./core/resume-factory'), | ||||||
|  |     FluentDate: require('./core/fluent-date'), | ||||||
|  |     HtmlGenerator: require('./generators/html-generator'), | ||||||
|  |     TextGenerator: require('./generators/text-generator'), | ||||||
|  |     HtmlPdfCliGenerator: require('./generators/html-pdf-cli-generator'), | ||||||
|  |     WordGenerator: require('./generators/word-generator'), | ||||||
|  |     MarkdownGenerator: require('./generators/markdown-generator'), | ||||||
|  |     JsonGenerator: require('./generators/json-generator'), | ||||||
|  |     YamlGenerator: require('./generators/yaml-generator'), | ||||||
|  |     JsonYamlGenerator: require('./generators/json-yaml-generator'), | ||||||
|  |     LaTeXGenerator: require('./generators/latex-generator'), | ||||||
|  |     HtmlPngGenerator: require('./generators/html-png-generator') | ||||||
|  |   }; | ||||||
|  |  | ||||||
|   require('./cli/main')( process.argv ); | }).call(this); | ||||||
|  |  | ||||||
| } | //# sourceMappingURL=index.js.map | ||||||
| catch( ex ) { |  | ||||||
|  |  | ||||||
|   require('./cli/error').err( ex, true ); |  | ||||||
|  |  | ||||||
| } |  | ||||||
|   | |||||||
							
								
								
									
										140
									
								
								dist/inspectors/gap-inspector.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								dist/inspectors/gap-inspector.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,140 @@ | |||||||
|  |  | ||||||
|  | /** | ||||||
|  | Employment gap analysis for HackMyResume. | ||||||
|  | @license MIT. See LICENSE.md for details. | ||||||
|  | @module inspectors/gap-inspector | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | (function() { | ||||||
|  |   var FluentDate, LO, _, gapInspector, moment; | ||||||
|  |  | ||||||
|  |   _ = require('underscore'); | ||||||
|  |  | ||||||
|  |   FluentDate = require('../core/fluent-date'); | ||||||
|  |  | ||||||
|  |   moment = require('moment'); | ||||||
|  |  | ||||||
|  |   LO = require('lodash'); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |   Identify gaps in the candidate's employment history. | ||||||
|  |    */ | ||||||
|  |  | ||||||
|  |   gapInspector = module.exports = { | ||||||
|  |     moniker: 'gap-inspector', | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |     Run the Gap Analyzer on a resume. | ||||||
|  |     @method run | ||||||
|  |     @return {Array} An array of object representing gaps in the candidate's | ||||||
|  |     employment history. Each object provides the start, end, and duration of the | ||||||
|  |     gap: | ||||||
|  |         { <-- gap | ||||||
|  |           start: // A Moment.js date | ||||||
|  |           end: // A Moment.js date | ||||||
|  |           duration: // Gap length | ||||||
|  |         } | ||||||
|  |      */ | ||||||
|  |     run: function(rez) { | ||||||
|  |       var coverage, dur, g, gap_start, hist, new_e, num_gaps, o, ref_count, tdur, total_gap_days; | ||||||
|  |       coverage = { | ||||||
|  |         gaps: [], | ||||||
|  |         overlaps: [], | ||||||
|  |         pct: '0%', | ||||||
|  |         duration: { | ||||||
|  |           total: 0, | ||||||
|  |           work: 0, | ||||||
|  |           gaps: 0 | ||||||
|  |         } | ||||||
|  |       }; | ||||||
|  |       hist = LO.get(rez, 'employment.history'); | ||||||
|  |       if (!hist || !hist.length) { | ||||||
|  |         return coverage; | ||||||
|  |       } | ||||||
|  |       new_e = hist.map(function(job) { | ||||||
|  |         var obj; | ||||||
|  |         obj = _.pick(job, ['start', 'end']); | ||||||
|  |         if (obj && (obj.start || obj.end)) { | ||||||
|  |           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 coverage; | ||||||
|  |       } | ||||||
|  |       new_e = _.sortBy(new_e, function(elem) { | ||||||
|  |         return elem[1].unix(); | ||||||
|  |       }); | ||||||
|  |       num_gaps = 0; | ||||||
|  |       ref_count = 0; | ||||||
|  |       total_gap_days = 0; | ||||||
|  |       gap_start = null; | ||||||
|  |       new_e.forEach(function(point) { | ||||||
|  |         var inc, lastGap, lastOver; | ||||||
|  |         inc = point[0] === 'start' ? 1 : -1; | ||||||
|  |         ref_count += inc; | ||||||
|  |         if (ref_count === 0) { | ||||||
|  |           return coverage.gaps.push({ | ||||||
|  |             start: point[1], | ||||||
|  |             end: null | ||||||
|  |           }); | ||||||
|  |         } else if (ref_count === 1 && inc === 1) { | ||||||
|  |           lastGap = _.last(coverage.gaps); | ||||||
|  |           if (lastGap) { | ||||||
|  |             lastGap.end = point[1]; | ||||||
|  |             lastGap.duration = lastGap.end.diff(lastGap.start, 'days'); | ||||||
|  |             return total_gap_days += lastGap.duration; | ||||||
|  |           } | ||||||
|  |         } else if (ref_count === 2 && inc === 1) { | ||||||
|  |           return coverage.overlaps.push({ | ||||||
|  |             start: point[1], | ||||||
|  |             end: null | ||||||
|  |           }); | ||||||
|  |         } else if (ref_count === 1 && inc === -1) { | ||||||
|  |           lastOver = _.last(coverage.overlaps); | ||||||
|  |           if (lastOver) { | ||||||
|  |             lastOver.end = point[1]; | ||||||
|  |             lastOver.duration = lastOver.end.diff(lastOver.start, 'days'); | ||||||
|  |             if (lastOver.duration === 0) { | ||||||
|  |               return coverage.overlaps.pop(); | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       }); | ||||||
|  |       if (coverage.overlaps.length) { | ||||||
|  |         o = _.last(coverage.overlaps); | ||||||
|  |         if (o && !o.end) { | ||||||
|  |           o.end = moment(); | ||||||
|  |           o.duration = o.end.diff(o.start, 'days'); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       if (coverage.gaps.length) { | ||||||
|  |         g = _.last(coverage.gaps); | ||||||
|  |         if (g && !g.end) { | ||||||
|  |           g.end = moment(); | ||||||
|  |           g.duration = g.end.diff(g.start, 'days'); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       tdur = rez.duration('days'); | ||||||
|  |       dur = { | ||||||
|  |         total: tdur, | ||||||
|  |         work: tdur - total_gap_days, | ||||||
|  |         gaps: total_gap_days | ||||||
|  |       }; | ||||||
|  |       coverage.pct = dur.total > 0 && dur.work > 0 ? (((dur.total - dur.gaps) / dur.total) * 100).toFixed(1) + '%' : '???'; | ||||||
|  |       coverage.duration = dur; | ||||||
|  |       return coverage; | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  | }).call(this); | ||||||
|  |  | ||||||
|  | //# sourceMappingURL=gap-inspector.js.map | ||||||
							
								
								
									
										63
									
								
								dist/inspectors/keyword-inspector.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								dist/inspectors/keyword-inspector.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,63 @@ | |||||||
|  |  | ||||||
|  | /** | ||||||
|  | Keyword analysis for HackMyResume. | ||||||
|  | @license MIT. See LICENSE.md for details. | ||||||
|  | @module inspectors/keyword-inspector | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | (function() { | ||||||
|  |   var FluentDate, _, keywordInspector; | ||||||
|  |  | ||||||
|  |   _ = require('underscore'); | ||||||
|  |  | ||||||
|  |   FluentDate = require('../core/fluent-date'); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |   Analyze the resume's use of keywords. | ||||||
|  |   TODO: BUG: Keyword search regex is inaccurate, especially for one or two | ||||||
|  |   letter keywords like "C" or "CLI". | ||||||
|  |   @class keywordInspector | ||||||
|  |    */ | ||||||
|  |  | ||||||
|  |   keywordInspector = module.exports = { | ||||||
|  |  | ||||||
|  |     /** A unique name for this inspector. */ | ||||||
|  |     moniker: 'keyword-inspector', | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |     Run the Keyword Inspector on a resume. | ||||||
|  |     @method run | ||||||
|  |     @return An collection of statistical keyword data. | ||||||
|  |      */ | ||||||
|  |     run: function(rez) { | ||||||
|  |       var prefix, regex_quote, searchable, suffix; | ||||||
|  |       regex_quote = function(str) { | ||||||
|  |         return (str + '').replace(/[.?*+^$[\]\\(){}|-]/ig, "\\$&"); | ||||||
|  |       }; | ||||||
|  |       searchable = ''; | ||||||
|  |       rez.transformStrings(['imp', 'computed', 'safe'], function(key, val) { | ||||||
|  |         return searchable += ' ' + val; | ||||||
|  |       }); | ||||||
|  |       prefix = '(?:' + ['^', '\\s+', '[\\.,]+'].join('|') + ')'; | ||||||
|  |       suffix = '(?:' + ['$', '\\s+', '[\\.,]+'].join('|') + ')'; | ||||||
|  |       return rez.keywords().map(function(kw) { | ||||||
|  |         var count, myArray, regex, regex_str; | ||||||
|  |         regex_str = prefix + regex_quote(kw) + suffix; | ||||||
|  |         regex = new RegExp(regex_str, 'ig'); | ||||||
|  |         myArray = null; | ||||||
|  |         count = 0; | ||||||
|  |         while ((myArray = regex.exec(searchable)) !== null) { | ||||||
|  |           count++; | ||||||
|  |         } | ||||||
|  |         return { | ||||||
|  |           name: kw, | ||||||
|  |           count: count | ||||||
|  |         }; | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  | }).call(this); | ||||||
|  |  | ||||||
|  | //# sourceMappingURL=keyword-inspector.js.map | ||||||
							
								
								
									
										51
									
								
								dist/inspectors/totals-inspector.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								dist/inspectors/totals-inspector.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,51 @@ | |||||||
|  |  | ||||||
|  | /** | ||||||
|  | Section analysis for HackMyResume. | ||||||
|  | @license MIT. See LICENSE.md for details. | ||||||
|  | @module inspectors/totals-inspector | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | (function() { | ||||||
|  |   var FluentDate, _, totalsInspector; | ||||||
|  |  | ||||||
|  |   _ = require('underscore'); | ||||||
|  |  | ||||||
|  |   FluentDate = require('../core/fluent-date'); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |   Retrieve sectional overview and summary information. | ||||||
|  |   @class totalsInspector | ||||||
|  |    */ | ||||||
|  |  | ||||||
|  |   totalsInspector = module.exports = { | ||||||
|  |     moniker: 'totals-inspector', | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |     Run the Totals Inspector on a resume. | ||||||
|  |     @method run | ||||||
|  |     @return An object containing summary information for each section on the | ||||||
|  |     resume. | ||||||
|  |      */ | ||||||
|  |     run: function(rez) { | ||||||
|  |       var sectionTotals; | ||||||
|  |       sectionTotals = {}; | ||||||
|  |       _.each(rez, function(val, key) { | ||||||
|  |         if (_.isArray(val) && !_.isString(val)) { | ||||||
|  |           return sectionTotals[key] = val.length; | ||||||
|  |         } else if (val.history && _.isArray(val.history)) { | ||||||
|  |           return sectionTotals[key] = val.history.length; | ||||||
|  |         } else if (val.sets && _.isArray(val.sets)) { | ||||||
|  |           return sectionTotals[key] = val.sets.length; | ||||||
|  |         } | ||||||
|  |       }); | ||||||
|  |       return { | ||||||
|  |         totals: sectionTotals, | ||||||
|  |         numSections: Object.keys(sectionTotals).length | ||||||
|  |       }; | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  | }).call(this); | ||||||
|  |  | ||||||
|  | //# sourceMappingURL=totals-inspector.js.map | ||||||
							
								
								
									
										102
									
								
								dist/renderers/handlebars-generator.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								dist/renderers/handlebars-generator.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,102 @@ | |||||||
|  |  | ||||||
|  | /** | ||||||
|  | Definition of the HandlebarsGenerator class. | ||||||
|  | @license MIT. See LICENSE.md for details. | ||||||
|  | @module renderers/handlebars-generator | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | (function() { | ||||||
|  |   var FS, HANDLEBARS, HMSTATUS, HandlebarsGenerator, PATH, READFILES, SLASH, _, parsePath, registerHelpers, registerPartials; | ||||||
|  |  | ||||||
|  |   _ = require('underscore'); | ||||||
|  |  | ||||||
|  |   HANDLEBARS = require('handlebars'); | ||||||
|  |  | ||||||
|  |   FS = require('fs'); | ||||||
|  |  | ||||||
|  |   registerHelpers = require('../helpers/handlebars-helpers'); | ||||||
|  |  | ||||||
|  |   PATH = require('path'); | ||||||
|  |  | ||||||
|  |   parsePath = require('parse-filepath'); | ||||||
|  |  | ||||||
|  |   READFILES = require('recursive-readdir-sync'); | ||||||
|  |  | ||||||
|  |   HMSTATUS = require('../core/status-codes'); | ||||||
|  |  | ||||||
|  |   SLASH = require('slash'); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |   Perform template-based resume generation using Handlebars.js. | ||||||
|  |   @class HandlebarsGenerator | ||||||
|  |    */ | ||||||
|  |  | ||||||
|  |   HandlebarsGenerator = module.exports = { | ||||||
|  |     generateSimple: function(data, tpl) { | ||||||
|  |       var template; | ||||||
|  |       try { | ||||||
|  |         template = HANDLEBARS.compile(tpl, { | ||||||
|  |           strict: false, | ||||||
|  |           assumeObjects: false | ||||||
|  |         }); | ||||||
|  |         return template(data); | ||||||
|  |       } catch (_error) { | ||||||
|  |         throw { | ||||||
|  |           fluenterror: HMSTATUS[template ? 'invokeTemplate' : 'compileTemplate'], | ||||||
|  |           inner: _error | ||||||
|  |         }; | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     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(); | ||||||
|  |       } | ||||||
|  |       if (format === 'doc') { | ||||||
|  |         encData = json.xmlify(); | ||||||
|  |       } | ||||||
|  |       ctx = { | ||||||
|  |         r: encData, | ||||||
|  |         RAW: json, | ||||||
|  |         filt: opts.filters, | ||||||
|  |         format: format, | ||||||
|  |         opts: opts, | ||||||
|  |         engine: this, | ||||||
|  |         results: curFmt.files, | ||||||
|  |         headFragment: opts.headFragment || '' | ||||||
|  |       }; | ||||||
|  |       return this.generateSimple(ctx, jst); | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   registerPartials = function(format, theme) { | ||||||
|  |     var partialsFolder; | ||||||
|  |     if (_.contains(['html', 'doc', 'md', 'txt', 'pdf'], format)) { | ||||||
|  |       partialsFolder = PATH.join(parsePath(require.resolve('fresh-themes')).dirname, '/partials/', format === 'pdf' ? 'html' : format); | ||||||
|  |       _.each(READFILES(partialsFolder, function(error) { | ||||||
|  |         return {}; | ||||||
|  |       }), function(el) { | ||||||
|  |         var compiledTemplate, name, pathInfo, tplData; | ||||||
|  |         pathInfo = parsePath(el); | ||||||
|  |         name = SLASH(PATH.relative(partialsFolder, el).replace(/\.(?:html|xml|hbs|md|txt)$/i, '')); | ||||||
|  |         tplData = FS.readFileSync(el, 'utf8'); | ||||||
|  |         compiledTemplate = HANDLEBARS.compile(tplData); | ||||||
|  |         HANDLEBARS.registerPartial(name, compiledTemplate); | ||||||
|  |         return theme.partialsInitialized = true; | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |     return _.each(theme.partials, function(el) { | ||||||
|  |       var compiledTemplate, tplData; | ||||||
|  |       tplData = FS.readFileSync(el.path, 'utf8'); | ||||||
|  |       compiledTemplate = HANDLEBARS.compile(tplData); | ||||||
|  |       return HANDLEBARS.registerPartial(el.name, compiledTemplate); | ||||||
|  |     }); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  | }).call(this); | ||||||
|  |  | ||||||
|  | //# sourceMappingURL=handlebars-generator.js.map | ||||||
							
								
								
									
										61
									
								
								dist/renderers/jrs-generator.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								dist/renderers/jrs-generator.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,61 @@ | |||||||
|  |  | ||||||
|  | /** | ||||||
|  | Definition of the JRSGenerator class. | ||||||
|  | @license MIT. See LICENSE.md for details. | ||||||
|  | @module renderers/jrs-generator | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | (function() { | ||||||
|  |   var FS, HANDLEBARS, JRSGenerator, MD, MDIN, PATH, READFILES, SLASH, _, parsePath, registerHelpers; | ||||||
|  |  | ||||||
|  |   _ = require('underscore'); | ||||||
|  |  | ||||||
|  |   HANDLEBARS = require('handlebars'); | ||||||
|  |  | ||||||
|  |   FS = require('fs'); | ||||||
|  |  | ||||||
|  |   registerHelpers = require('../helpers/handlebars-helpers'); | ||||||
|  |  | ||||||
|  |   PATH = require('path'); | ||||||
|  |  | ||||||
|  |   parsePath = require('parse-filepath'); | ||||||
|  |  | ||||||
|  |   READFILES = require('recursive-readdir-sync'); | ||||||
|  |  | ||||||
|  |   SLASH = require('slash'); | ||||||
|  |  | ||||||
|  |   MD = require('marked'); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |   Perform template-based resume generation for JSON Resume themes. | ||||||
|  |   @class JRSGenerator | ||||||
|  |    */ | ||||||
|  |  | ||||||
|  |   JRSGenerator = module.exports = { | ||||||
|  |     generate: function(json, jst, format, cssInfo, opts, theme) { | ||||||
|  |       var org, rezHtml, turnoff; | ||||||
|  |       turnoff = ['log', 'error', 'dir']; | ||||||
|  |       org = turnoff.map(function(c) { | ||||||
|  |         var ret; | ||||||
|  |         ret = console[c]; | ||||||
|  |         console[c] = function() {}; | ||||||
|  |         return ret; | ||||||
|  |       }); | ||||||
|  |       rezHtml = theme.render(json.harden()); | ||||||
|  |       turnoff.forEach(function(c, idx) { | ||||||
|  |         return console[c] = org[idx]; | ||||||
|  |       }); | ||||||
|  |       return rezHtml = rezHtml.replace(/@@@@~.*?~@@@@/gm, function(val) { | ||||||
|  |         return MDIN(val.replace(/~@@@@/gm, '').replace(/@@@@~/gm, '')); | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   MDIN = function(txt) { | ||||||
|  |     return MD(txt || '').replace(/^\s*<p>|<\/p>\s*$/gi, ''); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  | }).call(this); | ||||||
|  |  | ||||||
|  | //# sourceMappingURL=jrs-generator.js.map | ||||||
							
								
								
									
										87
									
								
								dist/renderers/underscore-generator.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								dist/renderers/underscore-generator.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,87 @@ | |||||||
|  |  | ||||||
|  | /** | ||||||
|  | Definition of the UnderscoreGenerator class. | ||||||
|  | @license MIT. See LICENSE.md for details. | ||||||
|  | @module underscore-generator.js | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | (function() { | ||||||
|  |   var UnderscoreGenerator, _, escapeLaTeX, registerHelpers; | ||||||
|  |  | ||||||
|  |   _ = require('underscore'); | ||||||
|  |  | ||||||
|  |   registerHelpers = require('../helpers/underscore-helpers'); | ||||||
|  |  | ||||||
|  |   require('../utils/string'); | ||||||
|  |  | ||||||
|  |   escapeLaTeX = require('escape-latex'); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |   Perform template-based resume generation using Underscore.js. | ||||||
|  |   @class UnderscoreGenerator | ||||||
|  |    */ | ||||||
|  |  | ||||||
|  |   UnderscoreGenerator = module.exports = { | ||||||
|  |     generateSimple: function(data, tpl) { | ||||||
|  |       var HMS, t; | ||||||
|  |       try { | ||||||
|  |         t = _.template(tpl); | ||||||
|  |         return t(data); | ||||||
|  |       } catch (_error) { | ||||||
|  |         HMS = require('../core/status-codes'); | ||||||
|  |         throw { | ||||||
|  |           fluenterror: HMS[t ? 'invokeTemplate' : 'compileTemplate'], | ||||||
|  |           inner: _error | ||||||
|  |         }; | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     generate: function(json, jst, format, cssInfo, opts, theme) { | ||||||
|  |       var ctx, delims, r, traverse; | ||||||
|  |       delims = (opts.themeObj && opts.themeObj.delimeters) || opts.template; | ||||||
|  |       if (opts.themeObj && opts.themeObj.delimeters) { | ||||||
|  |         delims = _.mapObject(delims, function(val, key) { | ||||||
|  |           return new RegExp(val, "ig"); | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  |       _.templateSettings = delims; | ||||||
|  |       r = null; | ||||||
|  |       switch (format) { | ||||||
|  |         case 'html': | ||||||
|  |           r = json.markdownify(); | ||||||
|  |           break; | ||||||
|  |         case 'pdf': | ||||||
|  |           r = json.markdownify(); | ||||||
|  |           break; | ||||||
|  |         case 'png': | ||||||
|  |           r = json.markdownify(); | ||||||
|  |           break; | ||||||
|  |         case 'latex': | ||||||
|  |           traverse = require('traverse'); | ||||||
|  |           r = traverse(json).map(function(x) { | ||||||
|  |             if (this.isLeaf && String.is(this.node)) { | ||||||
|  |               return escapeLaTeX(this.node); | ||||||
|  |             } | ||||||
|  |             return this.node; | ||||||
|  |           }); | ||||||
|  |           break; | ||||||
|  |         default: | ||||||
|  |           r = json; | ||||||
|  |       } | ||||||
|  |       ctx = { | ||||||
|  |         r: r, | ||||||
|  |         filt: opts.filters, | ||||||
|  |         XML: require('xml-escape'), | ||||||
|  |         RAW: json, | ||||||
|  |         cssInfo: cssInfo, | ||||||
|  |         headFragment: opts.headFragment || '', | ||||||
|  |         opts: opts | ||||||
|  |       }; | ||||||
|  |       registerHelpers(theme, opts, cssInfo, ctx, this); | ||||||
|  |       return this.generateSimple(ctx, jst); | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  | }).call(this); | ||||||
|  |  | ||||||
|  | //# sourceMappingURL=underscore-generator.js.map | ||||||
							
								
								
									
										14
									
								
								dist/utils/file-contains.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								dist/utils/file-contains.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | |||||||
|  |  | ||||||
|  | /** | ||||||
|  | Definition of the SyntaxErrorEx class. | ||||||
|  | @module file-contains.js | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | (function() { | ||||||
|  |   module.exports = function(file, needle) { | ||||||
|  |     return require('fs').readFileSync(file, 'utf-8').indexOf(needle) > -1; | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  | }).call(this); | ||||||
|  |  | ||||||
|  | //# sourceMappingURL=file-contains.js.map | ||||||
							
								
								
									
										63
									
								
								dist/utils/html-to-wpml.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								dist/utils/html-to-wpml.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,63 @@ | |||||||
|  |  | ||||||
|  | /** | ||||||
|  | Definition of the Markdown to WordProcessingML conversion routine. | ||||||
|  | @license MIT. Copyright (c) 2015 James Devlin / FluentDesk. | ||||||
|  | @module utils/html-to-wpml | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | (function() { | ||||||
|  |   var HTML5Tokenizer, _; | ||||||
|  |  | ||||||
|  |   _ = require('underscore'); | ||||||
|  |  | ||||||
|  |   HTML5Tokenizer = require('simple-html-tokenizer'); | ||||||
|  |  | ||||||
|  |   module.exports = function(html) { | ||||||
|  |     var final, is_bold, is_italic, is_link, link_url, tokens; | ||||||
|  |     tokens = HTML5Tokenizer.tokenize(html); | ||||||
|  |     final = is_bold = is_italic = is_link = link_url = ''; | ||||||
|  |     _.each(tokens, function(tok) { | ||||||
|  |       var style; | ||||||
|  |       switch (tok.type) { | ||||||
|  |         case 'StartTag': | ||||||
|  |           switch (tok.tagName) { | ||||||
|  |             case 'p': | ||||||
|  |               return final += '<w:p>'; | ||||||
|  |             case 'strong': | ||||||
|  |               return is_bold = true; | ||||||
|  |             case 'em': | ||||||
|  |               return is_italic = true; | ||||||
|  |             case 'a': | ||||||
|  |               is_link = true; | ||||||
|  |               return link_url = tok.attributes.filter(function(attr) { | ||||||
|  |                 return attr[0] === 'href'; | ||||||
|  |               })[0][1]; | ||||||
|  |           } | ||||||
|  |           break; | ||||||
|  |         case 'EndTag': | ||||||
|  |           switch (tok.tagName) { | ||||||
|  |             case 'p': | ||||||
|  |               return final += '</w:p>'; | ||||||
|  |             case 'strong': | ||||||
|  |               return is_bold = false; | ||||||
|  |             case 'em': | ||||||
|  |               return is_italic = false; | ||||||
|  |             case 'a': | ||||||
|  |               return is_link = false; | ||||||
|  |           } | ||||||
|  |           break; | ||||||
|  |         case 'Chars': | ||||||
|  |           if ((tok.chars.trim().length)) { | ||||||
|  |             style = is_bold ? '<w:b/>' : ''; | ||||||
|  |             style += is_italic ? '<w:i/>' : ''; | ||||||
|  |             style += is_link ? '<w:rStyle w:val="Hyperlink"/>' : ''; | ||||||
|  |             return final += (is_link ? '<w:hlink w:dest="' + link_url + '">' : '') + '<w:r><w:rPr>' + style + '</w:rPr><w:t>' + tok.chars + '</w:t></w:r>' + (is_link ? '</w:hlink>' : ''); | ||||||
|  |           } | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  |     return final; | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  | }).call(this); | ||||||
|  |  | ||||||
|  | //# sourceMappingURL=html-to-wpml.js.map | ||||||
							
								
								
									
										30
									
								
								dist/utils/md2chalk.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								dist/utils/md2chalk.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | |||||||
|  |  | ||||||
|  | /** | ||||||
|  | Inline Markdown-to-Chalk conversion routines. | ||||||
|  | @license MIT. See LICENSE.md for details. | ||||||
|  | @module utils/md2chalk | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | (function() { | ||||||
|  |   var CHALK, LO, MD; | ||||||
|  |  | ||||||
|  |   MD = require('marked'); | ||||||
|  |  | ||||||
|  |   CHALK = require('chalk'); | ||||||
|  |  | ||||||
|  |   LO = require('lodash'); | ||||||
|  |  | ||||||
|  |   module.exports = function(v, style, boldStyle) { | ||||||
|  |     var temp; | ||||||
|  |     boldStyle = boldStyle || 'bold'; | ||||||
|  |     temp = v.replace(/\*\*(.*?)\*\*/g, LO.get(CHALK, boldStyle)('$1')); | ||||||
|  |     if (style) { | ||||||
|  |       return LO.get(CHALK, style)(temp); | ||||||
|  |     } else { | ||||||
|  |       return temp; | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  | }).call(this); | ||||||
|  |  | ||||||
|  | //# sourceMappingURL=md2chalk.js.map | ||||||
							
								
								
									
										79
									
								
								dist/utils/rasterize.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								dist/utils/rasterize.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,79 @@ | |||||||
|  | (function() { | ||||||
|  |   "use strict"; | ||||||
|  |   var address, output, page, pageHeight, pageWidth, size, system; | ||||||
|  |  | ||||||
|  |   page = require('webpage').create(); | ||||||
|  |  | ||||||
|  |   system = require('system'); | ||||||
|  |  | ||||||
|  |   address = output = size = null; | ||||||
|  |  | ||||||
|  |   if (system.args.length < 3 || system.args.length > 5) { | ||||||
|  |     console.log('Usage: rasterize.js URL filename [paperwidth*paperheight|paperformat] [zoom]'); | ||||||
|  |     console.log('  paper (pdf output) examples: "5in*7.5in", "10cm*20cm", "A4", "Letter"'); | ||||||
|  |     console.log('  image (png/jpg output) examples: "1920px" entire page, window width 1920px'); | ||||||
|  |     console.log('                                   "800px*600px" window, clipped to 800x600'); | ||||||
|  |     phantom.exit(1); | ||||||
|  |   } else { | ||||||
|  |     address = system.args[1]; | ||||||
|  |     output = system.args[2]; | ||||||
|  |     page.viewportSize = { | ||||||
|  |       width: 600, | ||||||
|  |       height: 600 | ||||||
|  |     }; | ||||||
|  |     if (system.args.length > 3 && system.args[2].substr(-4) === ".pdf") { | ||||||
|  |       size = system.args[3].split('*'); | ||||||
|  |       page.paperSize = size.length === 2 ? { | ||||||
|  |         width: size[0], | ||||||
|  |         height: size[1], | ||||||
|  |         margin: '0px' | ||||||
|  |       } : { | ||||||
|  |         format: system.args[3], | ||||||
|  |         orientation: 'portrait', | ||||||
|  |         margin: '1cm' | ||||||
|  |       }; | ||||||
|  |     } else if (system.args.length > 3 && system.args[3].substr(-2) === "px") { | ||||||
|  |       size = system.args[3].split('*'); | ||||||
|  |       if (size.length === 2) { | ||||||
|  |         pageWidth = parseInt(size[0], 10); | ||||||
|  |         pageHeight = parseInt(size[1], 10); | ||||||
|  |         page.viewportSize = { | ||||||
|  |           width: pageWidth, | ||||||
|  |           height: pageHeight | ||||||
|  |         }; | ||||||
|  |         page.clipRect = { | ||||||
|  |           top: 0, | ||||||
|  |           left: 0, | ||||||
|  |           width: pageWidth, | ||||||
|  |           height: pageHeight | ||||||
|  |         }; | ||||||
|  |       } else { | ||||||
|  |         console.log("size:", system.args[3]); | ||||||
|  |         pageWidth = parseInt(system.args[3], 10); | ||||||
|  |         pageHeight = parseInt(pageWidth * 3 / 4, 10); | ||||||
|  |         console.log("pageHeight:", pageHeight); | ||||||
|  |         page.viewportSize = { | ||||||
|  |           width: pageWidth, | ||||||
|  |           height: pageHeight | ||||||
|  |         }; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     if (system.args.length > 4) { | ||||||
|  |       page.zoomFactor = system.args[4]; | ||||||
|  |     } | ||||||
|  |     page.open(address, function(status) { | ||||||
|  |       if (status !== 'success') { | ||||||
|  |         console.log('Unable to load the address!'); | ||||||
|  |         phantom.exit(1); | ||||||
|  |       } else { | ||||||
|  |         return window.setTimeout(function() { | ||||||
|  |           page.render(output); | ||||||
|  |           phantom.exit(); | ||||||
|  |         }, 200); | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  | }).call(this); | ||||||
|  |  | ||||||
|  | //# sourceMappingURL=rasterize.js.map | ||||||
							
								
								
									
										34
									
								
								dist/utils/safe-json-loader.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								dist/utils/safe-json-loader.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | |||||||
|  |  | ||||||
|  | /** | ||||||
|  | Definition of the SafeJsonLoader class. | ||||||
|  | @module utils/safe-json-loader | ||||||
|  | @license MIT. See LICENSE.md for details. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | (function() { | ||||||
|  |   var FS, SyntaxErrorEx; | ||||||
|  |  | ||||||
|  |   FS = require('fs'); | ||||||
|  |  | ||||||
|  |   SyntaxErrorEx = require('./syntax-error-ex'); | ||||||
|  |  | ||||||
|  |   module.exports = function(file) { | ||||||
|  |     var ret, retRaw; | ||||||
|  |     ret = {}; | ||||||
|  |     try { | ||||||
|  |       ret.raw = FS.readFileSync(file, 'utf8'); | ||||||
|  |       ret.json = JSON.parse(ret.raw); | ||||||
|  |     } catch (_error) { | ||||||
|  |       retRaw = ret.raw && ret.raw.trim(); | ||||||
|  |       ret.ex = { | ||||||
|  |         operation: retRaw ? 'parse' : 'read', | ||||||
|  |         inner: SyntaxErrorEx.is(_error) ? new SyntaxErrorEx(_error, retRaw) : _error, | ||||||
|  |         file: file | ||||||
|  |       }; | ||||||
|  |     } | ||||||
|  |     return ret; | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  | }).call(this); | ||||||
|  |  | ||||||
|  | //# sourceMappingURL=safe-json-loader.js.map | ||||||
							
								
								
									
										46
									
								
								dist/utils/safe-spawn.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								dist/utils/safe-spawn.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | |||||||
|  |  | ||||||
|  | /** | ||||||
|  | Safe spawn utility for HackMyResume / FluentCV. | ||||||
|  | @module utils/safe-spawn | ||||||
|  | @license MIT. See LICENSE.md for details. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /** Safely spawn a process synchronously or asynchronously without throwing an | ||||||
|  | exception | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | (function() { | ||||||
|  |   module.exports = function(cmd, args, isSync, callback, param) { | ||||||
|  |     var info, spawn; | ||||||
|  |     try { | ||||||
|  |       spawn = require('child_process')[isSync ? 'spawnSync' : 'spawn']; | ||||||
|  |       info = spawn(cmd, args); | ||||||
|  |       if (!isSync) { | ||||||
|  |         info.on('error', function(err) { | ||||||
|  |           if (typeof callback === "function") { | ||||||
|  |             callback(err, param); | ||||||
|  |           } | ||||||
|  |         }); | ||||||
|  |       } else { | ||||||
|  |         if (info.error) { | ||||||
|  |           if (typeof callback === "function") { | ||||||
|  |             callback(info.error, param); | ||||||
|  |           } | ||||||
|  |           return { | ||||||
|  |             cmd: cmd, | ||||||
|  |             inner: info.error | ||||||
|  |           }; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } catch (_error) { | ||||||
|  |       if (typeof callback === "function") { | ||||||
|  |         callback(_error, param); | ||||||
|  |       } | ||||||
|  |       return _error; | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  | }).call(this); | ||||||
|  |  | ||||||
|  | //# sourceMappingURL=safe-spawn.js.map | ||||||
							
								
								
									
										64
									
								
								dist/utils/string-transformer.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								dist/utils/string-transformer.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,64 @@ | |||||||
|  |  | ||||||
|  | /** | ||||||
|  | Object string transformation. | ||||||
|  | @module utils/string-transformer | ||||||
|  | @license MIT. See LICENSE.md for details. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | (function() { | ||||||
|  |   var _, moment; | ||||||
|  |  | ||||||
|  |   _ = require('underscore'); | ||||||
|  |  | ||||||
|  |   moment = require('moment'); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |   Create a copy of this object in which all string fields have been run through | ||||||
|  |   a transformation function (such as a Markdown filter or XML encoder). | ||||||
|  |    */ | ||||||
|  |  | ||||||
|  |   module.exports = function(ret, filt, transformer) { | ||||||
|  |     var that, transformStringsInObject; | ||||||
|  |     that = this; | ||||||
|  |     transformStringsInObject = function(obj, filters) { | ||||||
|  |       if (!obj) { | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |       if (moment.isMoment(obj)) { | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |       if (_.isArray(obj)) { | ||||||
|  |         return obj.forEach(function(elem, idx, ar) { | ||||||
|  |           if (typeof elem === 'string' || elem instanceof String) { | ||||||
|  |             return ar[idx] = transformer(null, elem); | ||||||
|  |           } else if (_.isObject(elem)) { | ||||||
|  |             return transformStringsInObject(elem, filters); | ||||||
|  |           } | ||||||
|  |         }); | ||||||
|  |       } else if (_.isObject(obj)) { | ||||||
|  |         return Object.keys(obj).forEach(function(k) { | ||||||
|  |           var sub; | ||||||
|  |           if (filters.length && _.contains(filters, k)) { | ||||||
|  |             return; | ||||||
|  |           } | ||||||
|  |           sub = obj[k]; | ||||||
|  |           if (typeof sub === 'string' || sub instanceof String) { | ||||||
|  |             return obj[k] = transformer(k, sub); | ||||||
|  |           } else if (_.isObject(sub)) { | ||||||
|  |             return transformStringsInObject(sub, filters); | ||||||
|  |           } | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  |     }; | ||||||
|  |     Object.keys(ret).forEach(function(member) { | ||||||
|  |       if (!filt || !filt.length || !_.contains(filt, member)) { | ||||||
|  |         return transformStringsInObject(ret[member], filt || []); | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  |     return ret; | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  | }).call(this); | ||||||
|  |  | ||||||
|  | //# sourceMappingURL=string-transformer.js.map | ||||||
							
								
								
									
										29
									
								
								dist/utils/string.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								dist/utils/string.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | |||||||
|  |  | ||||||
|  | /** | ||||||
|  | Definitions of string utility functions. | ||||||
|  | @module utils/string | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /** | ||||||
|  | Determine if the string is null, empty, or whitespace. | ||||||
|  | See: http://stackoverflow.com/a/32800728/4942583 | ||||||
|  | @method isNullOrWhitespace | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | (function() { | ||||||
|  |   String.isNullOrWhitespace = function(input) { | ||||||
|  |     return !input || !input.trim(); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   String.prototype.endsWith = function(suffix) { | ||||||
|  |     return this.indexOf(suffix, this.length - suffix.length) !== -1; | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   String.is = function(val) { | ||||||
|  |     return typeof val === 'string' || val instanceof String; | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  | }).call(this); | ||||||
|  |  | ||||||
|  | //# sourceMappingURL=string.js.map | ||||||
							
								
								
									
										55
									
								
								dist/utils/syntax-error-ex.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								dist/utils/syntax-error-ex.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | |||||||
|  |  | ||||||
|  | /** | ||||||
|  | Definition of the SyntaxErrorEx class. | ||||||
|  | @module utils/syntax-error-ex | ||||||
|  | @license MIT. See LICENSE.md for details. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /** | ||||||
|  | Represents a SyntaxError exception with line and column info. | ||||||
|  | Collect syntax error information from the provided exception object. The | ||||||
|  | JavaScript `SyntaxError` exception isn't interpreted uniformly across environ- | ||||||
|  | ments, so we reparse on error to grab the line and column. | ||||||
|  | See: http://stackoverflow.com/q/13323356 | ||||||
|  | @class SyntaxErrorEx | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | (function() { | ||||||
|  |   var SyntaxErrorEx; | ||||||
|  |  | ||||||
|  |   SyntaxErrorEx = (function() { | ||||||
|  |     function SyntaxErrorEx(ex, rawData) { | ||||||
|  |       var JSONLint, colNum, lineNum, lint, ref; | ||||||
|  |       lineNum = null; | ||||||
|  |       colNum = null; | ||||||
|  |       JSONLint = require('json-lint'); | ||||||
|  |       lint = JSONLint(rawData, { | ||||||
|  |         comments: false | ||||||
|  |       }); | ||||||
|  |       if (lint.error) { | ||||||
|  |         ref = [lint.line, lint.character], this.line = ref[0], this.col = ref[1]; | ||||||
|  |       } | ||||||
|  |       if (!lint.error) { | ||||||
|  |         JSONLint = require('jsonlint'); | ||||||
|  |         try { | ||||||
|  |           JSONLint.parse(rawData); | ||||||
|  |         } catch (_error) { | ||||||
|  |           this.line = (/on line (\d+)/.exec(_error))[1]; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return SyntaxErrorEx; | ||||||
|  |  | ||||||
|  |   })(); | ||||||
|  |  | ||||||
|  |   SyntaxErrorEx.is = function(ex) { | ||||||
|  |     return ex instanceof SyntaxError; | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   module.exports = SyntaxErrorEx; | ||||||
|  |  | ||||||
|  | }).call(this); | ||||||
|  |  | ||||||
|  | //# sourceMappingURL=syntax-error-ex.js.map | ||||||
							
								
								
									
										110
									
								
								dist/verbs/analyze.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								dist/verbs/analyze.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,110 @@ | |||||||
|  |  | ||||||
|  | /** | ||||||
|  | Implementation of the 'analyze' verb for HackMyResume. | ||||||
|  | @module verbs/analyze | ||||||
|  | @license MIT. See LICENSE.md for details. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | (function() { | ||||||
|  |   var AnalyzeVerb, HMEVENT, HMSTATUS, MKDIRP, PATH, ResumeFactory, Verb, _, _analyze, _analyzeOne, _loadInspectors, chalk, | ||||||
|  |     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; | ||||||
|  |  | ||||||
|  |   MKDIRP = require('mkdirp'); | ||||||
|  |  | ||||||
|  |   PATH = require('path'); | ||||||
|  |  | ||||||
|  |   HMEVENT = require('../core/event-codes'); | ||||||
|  |  | ||||||
|  |   HMSTATUS = require('../core/status-codes'); | ||||||
|  |  | ||||||
|  |   _ = require('underscore'); | ||||||
|  |  | ||||||
|  |   ResumeFactory = require('../core/resume-factory'); | ||||||
|  |  | ||||||
|  |   Verb = require('../verbs/verb'); | ||||||
|  |  | ||||||
|  |   chalk = require('chalk'); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   /** An invokable resume analysis command. */ | ||||||
|  |  | ||||||
|  |   module.exports = AnalyzeVerb = (function(superClass) { | ||||||
|  |     extend(AnalyzeVerb, superClass); | ||||||
|  |  | ||||||
|  |     function AnalyzeVerb() { | ||||||
|  |       AnalyzeVerb.__super__.constructor.call(this, 'analyze', _analyze); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return AnalyzeVerb; | ||||||
|  |  | ||||||
|  |   })(Verb); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   /** Private workhorse for the 'analyze' command. */ | ||||||
|  |  | ||||||
|  |   _analyze = function(sources, dst, opts) { | ||||||
|  |     var nlzrs, results; | ||||||
|  |     if (!sources || !sources.length) { | ||||||
|  |       this.err(HMSTATUS.resumeNotFound, { | ||||||
|  |         quit: true | ||||||
|  |       }); | ||||||
|  |       return null; | ||||||
|  |     } | ||||||
|  |     nlzrs = _loadInspectors(); | ||||||
|  |     results = _.map(sources, function(src) { | ||||||
|  |       var r; | ||||||
|  |       r = ResumeFactory.loadOne(src, { | ||||||
|  |         format: 'FRESH', | ||||||
|  |         objectify: true | ||||||
|  |       }, this); | ||||||
|  |       if (opts.assert && this.hasError()) { | ||||||
|  |         return {}; | ||||||
|  |       } | ||||||
|  |       if (r.fluenterror) { | ||||||
|  |         r.quit = opts.assert; | ||||||
|  |         this.err(r.fluenterror, r); | ||||||
|  |         return r; | ||||||
|  |       } else { | ||||||
|  |         return _analyzeOne.call(this, r, nlzrs, opts); | ||||||
|  |       } | ||||||
|  |     }, this); | ||||||
|  |     if (this.hasError() && !opts.assert) { | ||||||
|  |       this.reject(this.errorCode); | ||||||
|  |     } else if (!this.hasError()) { | ||||||
|  |       this.resolve(results); | ||||||
|  |     } | ||||||
|  |     return results; | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   /** Analyze a single resume. */ | ||||||
|  |  | ||||||
|  |   _analyzeOne = function(resumeObject, nlzrs, opts) { | ||||||
|  |     var info, rez, safeFormat; | ||||||
|  |     rez = resumeObject.rez; | ||||||
|  |     safeFormat = rez.meta && rez.meta.format && rez.meta.format.startsWith('FRESH') ? 'FRESH' : 'JRS'; | ||||||
|  |     this.stat(HMEVENT.beforeAnalyze, { | ||||||
|  |       fmt: safeFormat, | ||||||
|  |       file: resumeObject.file | ||||||
|  |     }); | ||||||
|  |     info = _.mapObject(nlzrs, function(val, key) { | ||||||
|  |       return val.run(rez); | ||||||
|  |     }); | ||||||
|  |     this.stat(HMEVENT.afterAnalyze, { | ||||||
|  |       info: info | ||||||
|  |     }); | ||||||
|  |     return info; | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   _loadInspectors = function() { | ||||||
|  |     return { | ||||||
|  |       totals: require('../inspectors/totals-inspector'), | ||||||
|  |       coverage: require('../inspectors/gap-inspector'), | ||||||
|  |       keywords: require('../inspectors/keyword-inspector') | ||||||
|  |     }; | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  | }).call(this); | ||||||
|  |  | ||||||
|  | //# sourceMappingURL=analyze.js.map | ||||||
							
								
								
									
										450
									
								
								dist/verbs/build.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										450
									
								
								dist/verbs/build.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,450 @@ | |||||||
|  |  | ||||||
|  | /** | ||||||
|  | Implementation of the 'build' verb for HackMyResume. | ||||||
|  | @module verbs/build | ||||||
|  | @license MIT. See LICENSE.md for details. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | (function() { | ||||||
|  |   var BuildVerb, FRESHTheme, FS, HMEVENT, HMSTATUS, JRSTheme, MD, MKDIRP, PATH, RConverter, RTYPES, ResumeFactory, Verb, _, _addFreebieFormats, _build, _err, _expand, _fmts, _loadTheme, _log, _opts, _prep, _rezObj, _single, _verifyOutputs, _verifyTheme, addFreebieFormats, build, expand, extend, loadTheme, parsePath, prep, single, verifyOutputs, verifyTheme, | ||||||
|  |     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; | ||||||
|  |  | ||||||
|  |   _ = require('underscore'); | ||||||
|  |  | ||||||
|  |   PATH = require('path'); | ||||||
|  |  | ||||||
|  |   FS = require('fs'); | ||||||
|  |  | ||||||
|  |   MD = require('marked'); | ||||||
|  |  | ||||||
|  |   MKDIRP = require('mkdirp'); | ||||||
|  |  | ||||||
|  |   extend = require('extend'); | ||||||
|  |  | ||||||
|  |   parsePath = require('parse-filepath'); | ||||||
|  |  | ||||||
|  |   RConverter = require('fresh-jrs-converter'); | ||||||
|  |  | ||||||
|  |   HMSTATUS = require('../core/status-codes'); | ||||||
|  |  | ||||||
|  |   HMEVENT = require('../core/event-codes'); | ||||||
|  |  | ||||||
|  |   RTYPES = { | ||||||
|  |     FRESH: require('../core/fresh-resume'), | ||||||
|  |     JRS: require('../core/jrs-resume') | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   _opts = require('../core/default-options'); | ||||||
|  |  | ||||||
|  |   FRESHTheme = require('../core/fresh-theme'); | ||||||
|  |  | ||||||
|  |   JRSTheme = require('../core/jrs-theme'); | ||||||
|  |  | ||||||
|  |   ResumeFactory = require('../core/resume-factory'); | ||||||
|  |  | ||||||
|  |   _fmts = require('../core/default-formats'); | ||||||
|  |  | ||||||
|  |   Verb = require('../verbs/verb'); | ||||||
|  |  | ||||||
|  |   _err = null; | ||||||
|  |  | ||||||
|  |   _log = null; | ||||||
|  |  | ||||||
|  |   _rezObj = null; | ||||||
|  |  | ||||||
|  |   build = null; | ||||||
|  |  | ||||||
|  |   prep = null; | ||||||
|  |  | ||||||
|  |   single = null; | ||||||
|  |  | ||||||
|  |   verifyOutputs = null; | ||||||
|  |  | ||||||
|  |   addFreebieFormats = null; | ||||||
|  |  | ||||||
|  |   expand = null; | ||||||
|  |  | ||||||
|  |   verifyTheme = null; | ||||||
|  |  | ||||||
|  |   loadTheme = null; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   /** An invokable resume generation command. */ | ||||||
|  |  | ||||||
|  |   module.exports = BuildVerb = (function(superClass) { | ||||||
|  |     extend1(BuildVerb, superClass); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     /** Create a new build verb. */ | ||||||
|  |  | ||||||
|  |     function BuildVerb() { | ||||||
|  |       BuildVerb.__super__.constructor.call(this, 'build', _build); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return BuildVerb; | ||||||
|  |  | ||||||
|  |   })(Verb); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |   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 opts Generation options. | ||||||
|  |    */ | ||||||
|  |  | ||||||
|  |   _build = function(src, dst, opts) { | ||||||
|  |     var inv, isFRESH, mixed, newEx, orgFormat, problemSheets, results, rez, sheetObjects, sheets, tFolder, targets, theme, toFormat; | ||||||
|  |     if (!src || !src.length) { | ||||||
|  |       this.err(HMSTATUS.resumeNotFound, { | ||||||
|  |         quit: true | ||||||
|  |       }); | ||||||
|  |       return null; | ||||||
|  |     } | ||||||
|  |     _prep.call(this, src, dst, opts); | ||||||
|  |     sheetObjects = ResumeFactory.load(src, { | ||||||
|  |       format: null, | ||||||
|  |       objectify: false, | ||||||
|  |       quit: true, | ||||||
|  |       inner: { | ||||||
|  |         sort: _opts.sort | ||||||
|  |       } | ||||||
|  |     }, this); | ||||||
|  |     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) { | ||||||
|  |       return r.json; | ||||||
|  |     }); | ||||||
|  |     theme = null; | ||||||
|  |     this.stat(HMEVENT.beforeTheme, { | ||||||
|  |       theme: _opts.theme | ||||||
|  |     }); | ||||||
|  |     try { | ||||||
|  |       tFolder = _verifyTheme.call(this, _opts.theme); | ||||||
|  |       if (tFolder.fluenterror) { | ||||||
|  |         tFolder.quit = true; | ||||||
|  |         this.err(tFolder.fluenterror, tFolder); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |       theme = _opts.themeObj = _loadTheme(tFolder); | ||||||
|  |       _addFreebieFormats(theme); | ||||||
|  |     } catch (_error) { | ||||||
|  |       newEx = { | ||||||
|  |         fluenterror: HMSTATUS.themeLoad, | ||||||
|  |         inner: _error, | ||||||
|  |         attempted: _opts.theme, | ||||||
|  |         quit: true | ||||||
|  |       }; | ||||||
|  |       this.err(HMSTATUS.themeLoad, newEx); | ||||||
|  |       return null; | ||||||
|  |     } | ||||||
|  |     this.stat(HMEVENT.afterTheme, { | ||||||
|  |       theme: theme | ||||||
|  |     }); | ||||||
|  |     inv = _verifyOutputs.call(this, dst, theme); | ||||||
|  |     if (inv && inv.length) { | ||||||
|  |       this.err(HMSTATUS.invalidFormat, { | ||||||
|  |         data: inv, | ||||||
|  |         theme: theme, | ||||||
|  |         quit: true | ||||||
|  |       }); | ||||||
|  |       return null; | ||||||
|  |     } | ||||||
|  |     rez = null; | ||||||
|  |     if (sheets.length > 1) { | ||||||
|  |       isFRESH = !sheets[0].basics; | ||||||
|  |       mixed = _.any(sheets, function(s) { | ||||||
|  |         if (isFRESH) { | ||||||
|  |           return s.basics; | ||||||
|  |         } else { | ||||||
|  |           return !s.basics; | ||||||
|  |         } | ||||||
|  |       }); | ||||||
|  |       this.stat(HMEVENT.beforeMerge, { | ||||||
|  |         f: _.clone(sheetObjects), | ||||||
|  |         mixed: mixed | ||||||
|  |       }); | ||||||
|  |       if (mixed) { | ||||||
|  |         this.err(HMSTATUS.mixedMerge); | ||||||
|  |       } | ||||||
|  |       rez = _.reduceRight(sheets, function(a, b, idx) { | ||||||
|  |         return extend(true, b, a); | ||||||
|  |       }); | ||||||
|  |       this.stat(HMEVENT.afterMerge, { | ||||||
|  |         r: rez | ||||||
|  |       }); | ||||||
|  |     } else { | ||||||
|  |       rez = sheets[0]; | ||||||
|  |     } | ||||||
|  |     orgFormat = rez.basics ? 'JRS' : 'FRESH'; | ||||||
|  |     toFormat = theme.render ? 'JRS' : 'FRESH'; | ||||||
|  |     if (toFormat !== orgFormat) { | ||||||
|  |       this.stat(HMEVENT.beforeInlineConvert); | ||||||
|  |       rez = RConverter['to' + toFormat](rez); | ||||||
|  |       this.stat(HMEVENT.afterInlineConvert, { | ||||||
|  |         file: sheetObjects[0].file, | ||||||
|  |         fmt: toFormat | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |     this.stat(HMEVENT.applyTheme, { | ||||||
|  |       r: rez, | ||||||
|  |       theme: theme | ||||||
|  |     }); | ||||||
|  |     _rezObj = new RTYPES[toFormat]().parseJSON(rez); | ||||||
|  |     targets = _expand(dst, theme); | ||||||
|  |     _.each(targets, function(t) { | ||||||
|  |       var ref; | ||||||
|  |       if (this.hasError() && opts.assert) { | ||||||
|  |         return {}; | ||||||
|  |       } | ||||||
|  |       t.final = _single.call(this, t, theme, targets); | ||||||
|  |       if ((ref = t.final) != null ? ref.fluenterror : void 0) { | ||||||
|  |         t.final.quit = opts.assert; | ||||||
|  |         this.err(t.final.fluenterror, t.final); | ||||||
|  |       } | ||||||
|  |     }, this); | ||||||
|  |     results = { | ||||||
|  |       sheet: _rezObj, | ||||||
|  |       targets: targets, | ||||||
|  |       processed: targets | ||||||
|  |     }; | ||||||
|  |     if (this.hasError() && !opts.assert) { | ||||||
|  |       this.reject(results); | ||||||
|  |     } else if (!this.hasError()) { | ||||||
|  |       this.resolve(results); | ||||||
|  |     } | ||||||
|  |     return results; | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |   Prepare for a BUILD run. | ||||||
|  |    */ | ||||||
|  |  | ||||||
|  |   _prep = function(src, dst, opts) { | ||||||
|  |     var that; | ||||||
|  |     _opts.theme = (opts.theme && opts.theme.toLowerCase().trim()) || 'modern'; | ||||||
|  |     _opts.prettify = opts.prettify === true; | ||||||
|  |     _opts.css = opts.css; | ||||||
|  |     _opts.pdf = opts.pdf; | ||||||
|  |     _opts.wrap = opts.wrap || 60; | ||||||
|  |     _opts.stitles = opts.sectionTitles; | ||||||
|  |     _opts.tips = opts.tips; | ||||||
|  |     _opts.errHandler = opts.errHandler; | ||||||
|  |     _opts.noTips = opts.noTips; | ||||||
|  |     _opts.debug = opts.debug; | ||||||
|  |     _opts.sort = opts.sort; | ||||||
|  |     that = this; | ||||||
|  |     _opts.onTransform = function(info) { | ||||||
|  |       that.stat(HMEVENT.afterTransform, info); | ||||||
|  |     }; | ||||||
|  |     _opts.beforeWrite = function(info) { | ||||||
|  |       that.stat(HMEVENT.beforeWrite, info); | ||||||
|  |     }; | ||||||
|  |     _opts.afterWrite = function(info) { | ||||||
|  |       that.stat(HMEVENT.afterWrite, info); | ||||||
|  |     }; | ||||||
|  |     (src.length > 1 && (!dst || !dst.length)) && dst.push(src.pop()); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |   Generate a single target resume such as "out/rez.html" or "out/rez.doc". | ||||||
|  |   TODO: Refactor. | ||||||
|  |   @param targInfo Information for the target resume. | ||||||
|  |   @param theme A FRESHTheme or JRSTheme object. | ||||||
|  |    */ | ||||||
|  |  | ||||||
|  |   _single = function(targInfo, theme, finished) { | ||||||
|  |     var e, ex, f, fName, fType, outFolder, ret, theFormat; | ||||||
|  |     ret = null; | ||||||
|  |     ex = null; | ||||||
|  |     f = targInfo.file; | ||||||
|  |     try { | ||||||
|  |       if (!targInfo.fmt) { | ||||||
|  |         return {}; | ||||||
|  |       } | ||||||
|  |       fType = targInfo.fmt.outFormat; | ||||||
|  |       fName = PATH.basename(f, '.' + fType); | ||||||
|  |       theFormat = null; | ||||||
|  |       this.stat(HMEVENT.beforeGenerate, { | ||||||
|  |         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)); | ||||||
|  |         ret = theFormat.gen.generate(_rezObj, f, _opts); | ||||||
|  |       } else { | ||||||
|  |         theFormat = _fmts.filter(function(fmt) { | ||||||
|  |           return fmt.name === targInfo.fmt.outFormat; | ||||||
|  |         })[0]; | ||||||
|  |         outFolder = PATH.dirname(f); | ||||||
|  |         MKDIRP.sync(outFolder); | ||||||
|  |         ret = theFormat.gen.generate(_rezObj, f, _opts); | ||||||
|  |       } | ||||||
|  |     } catch (_error) { | ||||||
|  |       e = _error; | ||||||
|  |       ex = e; | ||||||
|  |     } | ||||||
|  |     this.stat(HMEVENT.afterGenerate, { | ||||||
|  |       fmt: targInfo.fmt.outFormat, | ||||||
|  |       file: PATH.relative(process.cwd(), f), | ||||||
|  |       error: ex | ||||||
|  |     }); | ||||||
|  |     if (ex) { | ||||||
|  |       if (ex.fluenterror) { | ||||||
|  |         ret = ex; | ||||||
|  |       } else { | ||||||
|  |         ret = { | ||||||
|  |           fluenterror: HMSTATUS.generateError, | ||||||
|  |           inner: ex | ||||||
|  |         }; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return ret; | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   /** Ensure that user-specified outputs/targets are valid. */ | ||||||
|  |  | ||||||
|  |   _verifyOutputs = function(targets, theme) { | ||||||
|  |     this.stat(HMEVENT.verifyOutputs, { | ||||||
|  |       targets: targets, | ||||||
|  |       theme: theme | ||||||
|  |     }); | ||||||
|  |     return _.reject(targets.map(function(t) { | ||||||
|  |       var pathInfo; | ||||||
|  |       pathInfo = parsePath(t); | ||||||
|  |       return { | ||||||
|  |         format: pathInfo.extname.substr(1) | ||||||
|  |       }; | ||||||
|  |     }), function(t) { | ||||||
|  |       return 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 | ||||||
|  |   generated directly from the resume model or from one of the theme's declared | ||||||
|  |   output formats. For example, the PNG format can be generated for any theme | ||||||
|  |   that declares an HTML format; the theme doesn't have to provide an explicit | ||||||
|  |   PNG template. | ||||||
|  |   @param theTheme A FRESHTheme or JRSTheme object. | ||||||
|  |    */ | ||||||
|  |  | ||||||
|  |   _addFreebieFormats = function(theTheme) { | ||||||
|  |     theTheme.formats.json = theTheme.formats.json || { | ||||||
|  |       freebie: true, | ||||||
|  |       title: 'json', | ||||||
|  |       outFormat: 'json', | ||||||
|  |       pre: 'json', | ||||||
|  |       ext: 'json', | ||||||
|  |       path: null, | ||||||
|  |       data: null | ||||||
|  |     }; | ||||||
|  |     theTheme.formats.yml = theTheme.formats.yml || { | ||||||
|  |       freebie: true, | ||||||
|  |       title: 'yaml', | ||||||
|  |       outFormat: 'yml', | ||||||
|  |       pre: 'yml', | ||||||
|  |       ext: 'yml', | ||||||
|  |       path: null, | ||||||
|  |       data: null | ||||||
|  |     }; | ||||||
|  |     if (theTheme.formats.html && !theTheme.formats.png) { | ||||||
|  |       theTheme.formats.png = { | ||||||
|  |         freebie: true, | ||||||
|  |         title: 'png', | ||||||
|  |         outFormat: 'png', | ||||||
|  |         ext: 'yml', | ||||||
|  |         path: null, | ||||||
|  |         data: null | ||||||
|  |       }; | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |   Expand output files. For example, "foo.all" should be expanded to | ||||||
|  |   ["foo.html", "foo.doc", "foo.pdf", "etc"]. | ||||||
|  |   @param dst An array of output files as specified by the user. | ||||||
|  |   @param theTheme A FRESHTheme or JRSTheme object. | ||||||
|  |    */ | ||||||
|  |  | ||||||
|  |   _expand = function(dst, theTheme) { | ||||||
|  |     var destColl, targets; | ||||||
|  |     destColl = (dst && dst.length && dst) || [PATH.normalize('out/resume.all')]; | ||||||
|  |     targets = []; | ||||||
|  |     destColl.forEach(function(t) { | ||||||
|  |       var fmat, pa, to; | ||||||
|  |       to = PATH.resolve(t); | ||||||
|  |       pa = parsePath(to); | ||||||
|  |       fmat = pa.extname || '.all'; | ||||||
|  |       return targets.push.apply(targets, fmat === '.all' ? Object.keys(theTheme.formats).map(function(k) { | ||||||
|  |         var z; | ||||||
|  |         z = theTheme.formats[k]; | ||||||
|  |         return { | ||||||
|  |           file: to.replace(/all$/g, z.outFormat), | ||||||
|  |           fmt: z | ||||||
|  |         }; | ||||||
|  |       }) : [ | ||||||
|  |         { | ||||||
|  |           file: to, | ||||||
|  |           fmt: theTheme.getFormat(fmat.slice(1)) | ||||||
|  |         } | ||||||
|  |       ]); | ||||||
|  |     }); | ||||||
|  |     return targets; | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |   Verify the specified theme name/path. | ||||||
|  |    */ | ||||||
|  |  | ||||||
|  |   _verifyTheme = function(themeNameOrPath) { | ||||||
|  |     var exists, tFolder; | ||||||
|  |     tFolder = PATH.join(parsePath(require.resolve('fresh-themes')).dirname, '/themes/', themeNameOrPath); | ||||||
|  |     exists = require('path-exists').sync; | ||||||
|  |     if (!exists(tFolder)) { | ||||||
|  |       tFolder = PATH.resolve(themeNameOrPath); | ||||||
|  |       if (!exists(tFolder)) { | ||||||
|  |         return { | ||||||
|  |           fluenterror: HMSTATUS.themeNotFound, | ||||||
|  |           data: _opts.theme | ||||||
|  |         }; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return tFolder; | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |   Load the specified theme, which could be either a FRESH theme or a JSON Resume | ||||||
|  |   theme. | ||||||
|  |    */ | ||||||
|  |  | ||||||
|  |   _loadTheme = function(tFolder) { | ||||||
|  |     var theTheme; | ||||||
|  |     theTheme = _opts.theme.indexOf('jsonresume-theme-') > -1 ? new JRSTheme().open(tFolder) : new FRESHTheme().open(tFolder); | ||||||
|  |     _opts.themeObj = theTheme; | ||||||
|  |     return theTheme; | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  | }).call(this); | ||||||
|  |  | ||||||
|  | //# sourceMappingURL=build.js.map | ||||||
							
								
								
									
										115
									
								
								dist/verbs/convert.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								dist/verbs/convert.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,115 @@ | |||||||
|  |  | ||||||
|  | /** | ||||||
|  | Implementation of the 'convert' verb for HackMyResume. | ||||||
|  | @module verbs/convert | ||||||
|  | @license MIT. See LICENSE.md for details. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | (function() { | ||||||
|  |   var ConvertVerb, HMEVENT, HMSTATUS, ResumeFactory, Verb, _, _convert, _convertOne, chalk, | ||||||
|  |     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; | ||||||
|  |  | ||||||
|  |   ResumeFactory = require('../core/resume-factory'); | ||||||
|  |  | ||||||
|  |   chalk = require('chalk'); | ||||||
|  |  | ||||||
|  |   Verb = require('../verbs/verb'); | ||||||
|  |  | ||||||
|  |   HMSTATUS = require('../core/status-codes'); | ||||||
|  |  | ||||||
|  |   _ = require('underscore'); | ||||||
|  |  | ||||||
|  |   HMEVENT = require('../core/event-codes'); | ||||||
|  |  | ||||||
|  |   module.exports = ConvertVerb = (function(superClass) { | ||||||
|  |     extend(ConvertVerb, superClass); | ||||||
|  |  | ||||||
|  |     function ConvertVerb() { | ||||||
|  |       ConvertVerb.__super__.constructor.call(this, 'convert', _convert); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return ConvertVerb; | ||||||
|  |  | ||||||
|  |   })(Verb); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   /** Private workhorse method. Convert 0..N resumes between FRESH and JRS | ||||||
|  |   formats. | ||||||
|  |    */ | ||||||
|  |  | ||||||
|  |   _convert = function(srcs, dst, opts) { | ||||||
|  |     var results; | ||||||
|  |     if (!srcs || !srcs.length) { | ||||||
|  |       this.err(HMSTATUS.resumeNotFound, { | ||||||
|  |         quit: true | ||||||
|  |       }); | ||||||
|  |       return null; | ||||||
|  |     } | ||||||
|  |     if (!dst || !dst.length) { | ||||||
|  |       if (srcs.length === 1) { | ||||||
|  |         this.err(HMSTATUS.inputOutputParity, { | ||||||
|  |           quit: true | ||||||
|  |         }); | ||||||
|  |       } else if (srcs.length === 2) { | ||||||
|  |         dst = dst || []; | ||||||
|  |         dst.push(srcs.pop()); | ||||||
|  |       } else { | ||||||
|  |         this.err(HMSTATUS.inputOutputParity, { | ||||||
|  |           quit: true | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     if (srcs && dst && srcs.length && dst.length && srcs.length !== dst.length) { | ||||||
|  |       this.err(HMSTATUS.inputOutputParity, { | ||||||
|  |         quit: true | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |     results = _.map(srcs, function(src, idx) { | ||||||
|  |       var r; | ||||||
|  |       if (opts.assert && this.hasError()) { | ||||||
|  |         return {}; | ||||||
|  |       } | ||||||
|  |       r = _convertOne.call(this, src, dst, idx); | ||||||
|  |       if (r.fluenterror) { | ||||||
|  |         r.quit = opts.assert; | ||||||
|  |         this.err(r.fluenterror, r); | ||||||
|  |       } | ||||||
|  |       return r; | ||||||
|  |     }, this); | ||||||
|  |     if (this.hasError() && !opts.assert) { | ||||||
|  |       this.reject(results); | ||||||
|  |     } else if (!this.hasError()) { | ||||||
|  |       this.resolve(results); | ||||||
|  |     } | ||||||
|  |     return results; | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   /** Private workhorse method. Convert a single resume. */ | ||||||
|  |  | ||||||
|  |   _convertOne = function(src, dst, idx) { | ||||||
|  |     var rinfo, s, srcFmt, targetFormat; | ||||||
|  |     rinfo = ResumeFactory.loadOne(src, { | ||||||
|  |       format: null, | ||||||
|  |       objectify: true | ||||||
|  |     }); | ||||||
|  |     if (rinfo.fluenterror) { | ||||||
|  |       return rinfo; | ||||||
|  |     } | ||||||
|  |     s = rinfo.rez; | ||||||
|  |     srcFmt = ((s.basics && s.basics.imp) || s.imp).orgFormat === 'JRS' ? 'JRS' : 'FRESH'; | ||||||
|  |     targetFormat = srcFmt === 'JRS' ? 'FRESH' : 'JRS'; | ||||||
|  |     this.stat(HMEVENT.beforeConvert, { | ||||||
|  |       srcFile: rinfo.file, | ||||||
|  |       srcFmt: srcFmt, | ||||||
|  |       dstFile: dst[idx], | ||||||
|  |       dstFmt: targetFormat | ||||||
|  |     }); | ||||||
|  |     s.saveAs(dst[idx], targetFormat); | ||||||
|  |     return s; | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  | }).call(this); | ||||||
|  |  | ||||||
|  | //# sourceMappingURL=convert.js.map | ||||||
							
								
								
									
										103
									
								
								dist/verbs/create.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								dist/verbs/create.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,103 @@ | |||||||
|  |  | ||||||
|  | /** | ||||||
|  | Implementation of the 'create' verb for HackMyResume. | ||||||
|  | @module verbs/create | ||||||
|  | @license MIT. See LICENSE.md for details. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | (function() { | ||||||
|  |   var CreateVerb, HMEVENT, HMSTATUS, MKDIRP, PATH, Verb, _, _create, _createOne, chalk, | ||||||
|  |     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; | ||||||
|  |  | ||||||
|  |   MKDIRP = require('mkdirp'); | ||||||
|  |  | ||||||
|  |   PATH = require('path'); | ||||||
|  |  | ||||||
|  |   chalk = require('chalk'); | ||||||
|  |  | ||||||
|  |   Verb = require('../verbs/verb'); | ||||||
|  |  | ||||||
|  |   _ = require('underscore'); | ||||||
|  |  | ||||||
|  |   HMSTATUS = require('../core/status-codes'); | ||||||
|  |  | ||||||
|  |   HMEVENT = require('../core/event-codes'); | ||||||
|  |  | ||||||
|  |   module.exports = CreateVerb = (function(superClass) { | ||||||
|  |     extend(CreateVerb, superClass); | ||||||
|  |  | ||||||
|  |     function CreateVerb() { | ||||||
|  |       CreateVerb.__super__.constructor.call(this, 'new', _create); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return CreateVerb; | ||||||
|  |  | ||||||
|  |   })(Verb); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   /** Create a new empty resume in either FRESH or JRS format. */ | ||||||
|  |  | ||||||
|  |   _create = function(src, dst, opts) { | ||||||
|  |     var results; | ||||||
|  |     if (!src || !src.length) { | ||||||
|  |       this.err(HMSTATUS.createNameMissing, { | ||||||
|  |         quit: true | ||||||
|  |       }); | ||||||
|  |       return null; | ||||||
|  |     } | ||||||
|  |     results = _.map(src, function(t) { | ||||||
|  |       var r; | ||||||
|  |       if (opts.assert && this.hasError()) { | ||||||
|  |         return {}; | ||||||
|  |       } | ||||||
|  |       r = _createOne.call(this, t, opts); | ||||||
|  |       if (r.fluenterror) { | ||||||
|  |         r.quit = opts.assert; | ||||||
|  |         this.err(r.fluenterror, r); | ||||||
|  |       } | ||||||
|  |       return r; | ||||||
|  |     }, this); | ||||||
|  |     if (this.hasError() && !opts.assert) { | ||||||
|  |       this.reject(this.errorCode); | ||||||
|  |     } else if (!this.hasError()) { | ||||||
|  |       this.resolve(results); | ||||||
|  |     } | ||||||
|  |     return results; | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   /** Create a single new resume */ | ||||||
|  |  | ||||||
|  |   _createOne = function(t, opts) { | ||||||
|  |     var RezClass, newRez, ret, safeFmt; | ||||||
|  |     try { | ||||||
|  |       ret = null; | ||||||
|  |       safeFmt = opts.format.toUpperCase(); | ||||||
|  |       this.stat(HMEVENT.beforeCreate, { | ||||||
|  |         fmt: safeFmt, | ||||||
|  |         file: t | ||||||
|  |       }); | ||||||
|  |       MKDIRP.sync(PATH.dirname(t)); | ||||||
|  |       RezClass = require('../core/' + safeFmt.toLowerCase() + '-resume'); | ||||||
|  |       newRez = RezClass["default"](); | ||||||
|  |       newRez.save(t); | ||||||
|  |       ret = newRez; | ||||||
|  |     } catch (_error) { | ||||||
|  |       ret = { | ||||||
|  |         fluenterror: HMSTATUS.createError, | ||||||
|  |         inner: _error | ||||||
|  |       }; | ||||||
|  |     } finally { | ||||||
|  |       this.stat(HMEVENT.afterCreate, { | ||||||
|  |         fmt: safeFmt, | ||||||
|  |         file: t, | ||||||
|  |         isError: ret.fluenterror | ||||||
|  |       }); | ||||||
|  |       return ret; | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  | }).call(this); | ||||||
|  |  | ||||||
|  | //# sourceMappingURL=create.js.map | ||||||
							
								
								
									
										106
									
								
								dist/verbs/peek.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								dist/verbs/peek.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,106 @@ | |||||||
|  |  | ||||||
|  | /** | ||||||
|  | Implementation of the 'peek' verb for HackMyResume. | ||||||
|  | @module verbs/peek | ||||||
|  | @license MIT. See LICENSE.md for details. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | (function() { | ||||||
|  |   var HMEVENT, HMSTATUS, PeekVerb, Verb, _, __, _peek, _peekOne, safeLoadJSON, | ||||||
|  |     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; | ||||||
|  |  | ||||||
|  |   Verb = require('../verbs/verb'); | ||||||
|  |  | ||||||
|  |   _ = require('underscore'); | ||||||
|  |  | ||||||
|  |   __ = require('lodash'); | ||||||
|  |  | ||||||
|  |   safeLoadJSON = require('../utils/safe-json-loader'); | ||||||
|  |  | ||||||
|  |   HMSTATUS = require('../core/status-codes'); | ||||||
|  |  | ||||||
|  |   HMEVENT = require('../core/event-codes'); | ||||||
|  |  | ||||||
|  |   module.exports = PeekVerb = (function(superClass) { | ||||||
|  |     extend(PeekVerb, superClass); | ||||||
|  |  | ||||||
|  |     function PeekVerb() { | ||||||
|  |       PeekVerb.__super__.constructor.call(this, 'peek', _peek); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return PeekVerb; | ||||||
|  |  | ||||||
|  |   })(Verb); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   /** Peek at a resume, resume section, or resume field. */ | ||||||
|  |  | ||||||
|  |   _peek = function(src, dst, opts) { | ||||||
|  |     var objPath, results; | ||||||
|  |     if (!src || !src.length) { | ||||||
|  |       this.err(HMSTATUS.resumeNotFound, { | ||||||
|  |         quit: true | ||||||
|  |       }); | ||||||
|  |       return null; | ||||||
|  |     } | ||||||
|  |     objPath = (dst && dst[0]) || ''; | ||||||
|  |     results = _.map(src, function(t) { | ||||||
|  |       var tgt; | ||||||
|  |       if (opts.assert && this.hasError()) { | ||||||
|  |         return {}; | ||||||
|  |       } | ||||||
|  |       tgt = _peekOne.call(this, t, objPath); | ||||||
|  |       if (tgt.error) { | ||||||
|  |         this.setError(tgt.error.fluenterror, tgt.error); | ||||||
|  |       } | ||||||
|  |       return tgt; | ||||||
|  |     }, this); | ||||||
|  |     if (this.hasError() && !opts.assert) { | ||||||
|  |       this.reject(this.errorCode); | ||||||
|  |     } else if (!this.hasError()) { | ||||||
|  |       this.resolve(results); | ||||||
|  |     } | ||||||
|  |     return results; | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   /** Peek at a single resume, resume section, or resume field. */ | ||||||
|  |  | ||||||
|  |   _peekOne = function(t, objPath) { | ||||||
|  |     var errCode, obj, pkgError, tgt; | ||||||
|  |     this.stat(HMEVENT.beforePeek, { | ||||||
|  |       file: t, | ||||||
|  |       target: objPath | ||||||
|  |     }); | ||||||
|  |     obj = safeLoadJSON(t); | ||||||
|  |     tgt = null; | ||||||
|  |     if (!obj.ex) { | ||||||
|  |       tgt = objPath ? __.get(obj.json, objPath) : obj.json; | ||||||
|  |     } | ||||||
|  |     pkgError = null; | ||||||
|  |     if (obj.ex) { | ||||||
|  |       errCode = obj.ex.operation === 'parse' ? HMSTATUS.parseError : HMSTATUS.readError; | ||||||
|  |       if (errCode === HMSTATUS.readError) { | ||||||
|  |         obj.ex.quiet = true; | ||||||
|  |       } | ||||||
|  |       pkgError = { | ||||||
|  |         fluenterror: errCode, | ||||||
|  |         inner: obj.ex | ||||||
|  |       }; | ||||||
|  |     } | ||||||
|  |     this.stat(HMEVENT.afterPeek, { | ||||||
|  |       file: t, | ||||||
|  |       requested: objPath, | ||||||
|  |       target: obj.ex ? void 0 : tgt, | ||||||
|  |       error: pkgError | ||||||
|  |     }); | ||||||
|  |     return { | ||||||
|  |       val: obj.ex ? void 0 : tgt, | ||||||
|  |       error: pkgError | ||||||
|  |     }; | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  | }).call(this); | ||||||
|  |  | ||||||
|  | //# sourceMappingURL=peek.js.map | ||||||
							
								
								
									
										139
									
								
								dist/verbs/validate.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										139
									
								
								dist/verbs/validate.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,139 @@ | |||||||
|  |  | ||||||
|  | /** | ||||||
|  | Implementation of the 'validate' verb for HackMyResume. | ||||||
|  | @module verbs/validate | ||||||
|  | @license MIT. See LICENSE.md for details. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | (function() { | ||||||
|  |   var FS, HMEVENT, HMSTATUS, ResumeFactory, SyntaxErrorEx, ValidateVerb, Verb, _, _validate, _validateOne, chalk, safeLoadJSON, | ||||||
|  |     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; | ||||||
|  |  | ||||||
|  |   FS = require('fs'); | ||||||
|  |  | ||||||
|  |   ResumeFactory = require('../core/resume-factory'); | ||||||
|  |  | ||||||
|  |   SyntaxErrorEx = require('../utils/syntax-error-ex'); | ||||||
|  |  | ||||||
|  |   chalk = require('chalk'); | ||||||
|  |  | ||||||
|  |   Verb = require('../verbs/verb'); | ||||||
|  |  | ||||||
|  |   HMSTATUS = require('../core/status-codes'); | ||||||
|  |  | ||||||
|  |   HMEVENT = require('../core/event-codes'); | ||||||
|  |  | ||||||
|  |   _ = require('underscore'); | ||||||
|  |  | ||||||
|  |   safeLoadJSON = require('../utils/safe-json-loader'); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   /** An invokable resume validation command. */ | ||||||
|  |  | ||||||
|  |   module.exports = ValidateVerb = (function(superClass) { | ||||||
|  |     extend(ValidateVerb, superClass); | ||||||
|  |  | ||||||
|  |     function ValidateVerb() { | ||||||
|  |       ValidateVerb.__super__.constructor.call(this, 'validate', _validate); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return ValidateVerb; | ||||||
|  |  | ||||||
|  |   })(Verb); | ||||||
|  |  | ||||||
|  |   _validate = function(sources, unused, opts) { | ||||||
|  |     var results, schemas, validator; | ||||||
|  |     if (!sources || !sources.length) { | ||||||
|  |       this.err(HMSTATUS.resumeNotFoundAlt, { | ||||||
|  |         quit: true | ||||||
|  |       }); | ||||||
|  |       return null; | ||||||
|  |     } | ||||||
|  |     validator = require('is-my-json-valid'); | ||||||
|  |     schemas = { | ||||||
|  |       fresh: require('fresca'), | ||||||
|  |       jars: require('../core/resume.json') | ||||||
|  |     }; | ||||||
|  |     results = _.map(sources, function(t) { | ||||||
|  |       var r; | ||||||
|  |       r = _validateOne.call(this, t, validator, schemas, opts); | ||||||
|  |       if (r.error) { | ||||||
|  |         this.err(r.error.fluenterror, r.error); | ||||||
|  |       } | ||||||
|  |       return r; | ||||||
|  |     }, this); | ||||||
|  |     if (this.hasError() && !opts.assert) { | ||||||
|  |       this.reject(this.errorCode); | ||||||
|  |     } else if (!this.hasError()) { | ||||||
|  |       this.resolve(results); | ||||||
|  |     } | ||||||
|  |     return results; | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |   Validate a single resume. | ||||||
|  |   @returns { | ||||||
|  |     file: <fileName>, | ||||||
|  |     isValid: <validFlag>, | ||||||
|  |     status: <validationStatus>, | ||||||
|  |     violations: <validationErrors>, | ||||||
|  |     schema: <schemaType>, | ||||||
|  |     error: <errorObject> | ||||||
|  |   } | ||||||
|  |    */ | ||||||
|  |  | ||||||
|  |   _validateOne = function(t, validator, schemas, opts) { | ||||||
|  |     var errCode, obj, ret, validate; | ||||||
|  |     ret = { | ||||||
|  |       file: t, | ||||||
|  |       isValid: false, | ||||||
|  |       status: 'unknown', | ||||||
|  |       schema: '-----' | ||||||
|  |     }; | ||||||
|  |     try { | ||||||
|  |       obj = safeLoadJSON(t); | ||||||
|  |       if (!obj.ex) { | ||||||
|  |         if (obj.json.basics) { | ||||||
|  |           ret.schema = 'jars'; | ||||||
|  |         } else { | ||||||
|  |           ret.schema = 'fresh'; | ||||||
|  |         } | ||||||
|  |         validate = validator(schemas[ret.schema], { | ||||||
|  |           formats: { | ||||||
|  |             date: /^\d{4}(?:-(?:0[0-9]{1}|1[0-2]{1})(?:-[0-9]{2})?)?$/ | ||||||
|  |           } | ||||||
|  |         }); | ||||||
|  |         ret.isValid = validate(obj.json); | ||||||
|  |         ret.status = ret.isValid ? 'valid' : 'invalid'; | ||||||
|  |         if (!ret.isValid) { | ||||||
|  |           ret.violations = validate.errors; | ||||||
|  |         } | ||||||
|  |       } else { | ||||||
|  |         if (obj.ex.operation === 'parse') { | ||||||
|  |           errCode = HMSTATUS.parseError; | ||||||
|  |           ret.status = 'broken'; | ||||||
|  |         } else { | ||||||
|  |           errCode = HMSTATUS.readError; | ||||||
|  |           ret.status = 'missing'; | ||||||
|  |         } | ||||||
|  |         ret.error = { | ||||||
|  |           fluenterror: errCode, | ||||||
|  |           inner: obj.ex.inner, | ||||||
|  |           quiet: errCode === HMSTATUS.readError | ||||||
|  |         }; | ||||||
|  |       } | ||||||
|  |     } catch (_error) { | ||||||
|  |       ret.error = { | ||||||
|  |         fluenterror: HMSTATUS.validateError, | ||||||
|  |         inner: _error | ||||||
|  |       }; | ||||||
|  |     } | ||||||
|  |     this.stat(HMEVENT.afterValidate, ret); | ||||||
|  |     return ret; | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  | }).call(this); | ||||||
|  |  | ||||||
|  | //# sourceMappingURL=validate.js.map | ||||||
							
								
								
									
										118
									
								
								dist/verbs/verb.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								dist/verbs/verb.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,118 @@ | |||||||
|  |  | ||||||
|  | /** | ||||||
|  | Definition of the Verb class. | ||||||
|  | @module verbs/verb | ||||||
|  | @license MIT. See LICENSE.md for details. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | (function() { | ||||||
|  |   var EVENTS, HMEVENT, Promise, Verb; | ||||||
|  |  | ||||||
|  |   EVENTS = require('events'); | ||||||
|  |  | ||||||
|  |   HMEVENT = require('../core/event-codes'); | ||||||
|  |  | ||||||
|  |   Promise = require('pinkie-promise'); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |   An abstract invokable verb. | ||||||
|  |   Provides base class functionality for verbs. Provide common services such as | ||||||
|  |   error handling, event management, and promise support. | ||||||
|  |   @class Verb | ||||||
|  |    */ | ||||||
|  |  | ||||||
|  |   module.exports = Verb = (function() { | ||||||
|  |  | ||||||
|  |     /** Constructor. Automatically called at creation. */ | ||||||
|  |     function Verb(moniker, workhorse) { | ||||||
|  |       this.moniker = moniker; | ||||||
|  |       this.workhorse = workhorse; | ||||||
|  |       this.emitter = new EVENTS.EventEmitter(); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     /** Invoke the command. */ | ||||||
|  |  | ||||||
|  |     Verb.prototype.invoke = function() { | ||||||
|  |       var argsArray, that; | ||||||
|  |       this.stat(HMEVENT.begin, { | ||||||
|  |         cmd: this.moniker | ||||||
|  |       }); | ||||||
|  |       argsArray = Array.prototype.slice.call(arguments); | ||||||
|  |       that = this; | ||||||
|  |       return this.promise = new Promise(function(res, rej) { | ||||||
|  |         that.resolve = res; | ||||||
|  |         that.reject = rej; | ||||||
|  |         that.workhorse.apply(that, argsArray); | ||||||
|  |       }); | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     /** Forward subscriptions to the event emitter. */ | ||||||
|  |  | ||||||
|  |     Verb.prototype.on = function() { | ||||||
|  |       return this.emitter.on.apply(this.emitter, arguments); | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     /** Fire an arbitrary event, scoped to "hmr:". */ | ||||||
|  |  | ||||||
|  |     Verb.prototype.fire = function(evtName, payload) { | ||||||
|  |       payload = payload || {}; | ||||||
|  |       payload.cmd = this.moniker; | ||||||
|  |       this.emitter.emit('hmr:' + evtName, payload); | ||||||
|  |       return true; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     /** Handle an error condition. */ | ||||||
|  |  | ||||||
|  |     Verb.prototype.err = function(errorCode, payload, hot) { | ||||||
|  |       payload = payload || {}; | ||||||
|  |       payload.sub = payload.fluenterror = errorCode; | ||||||
|  |       payload["throw"] = hot; | ||||||
|  |       this.setError(errorCode, payload); | ||||||
|  |       if (payload.quit) { | ||||||
|  |         this.reject(errorCode); | ||||||
|  |       } | ||||||
|  |       this.fire('error', payload); | ||||||
|  |       if (hot) { | ||||||
|  |         throw payload; | ||||||
|  |       } | ||||||
|  |       return true; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     /** Fire the 'hmr:status' error event. */ | ||||||
|  |  | ||||||
|  |     Verb.prototype.stat = function(subEvent, payload) { | ||||||
|  |       payload = payload || {}; | ||||||
|  |       payload.sub = subEvent; | ||||||
|  |       this.fire('status', payload); | ||||||
|  |       return true; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     /** Has an error occurred during this verb invocation? */ | ||||||
|  |  | ||||||
|  |     Verb.prototype.hasError = function() { | ||||||
|  |       return this.errorCode || this.errorObj; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     /** Associate error info with the invocation. */ | ||||||
|  |  | ||||||
|  |     Verb.prototype.setError = function(code, obj) { | ||||||
|  |       this.errorCode = code; | ||||||
|  |       this.errorObj = obj; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     return Verb; | ||||||
|  |  | ||||||
|  |   })(); | ||||||
|  |  | ||||||
|  | }).call(this); | ||||||
|  |  | ||||||
|  | //# sourceMappingURL=verb.js.map | ||||||
							
								
								
									
										30
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										30
									
								
								package.json
									
									
									
									
									
								
							| @@ -1,6 +1,6 @@ | |||||||
| { | { | ||||||
|   "name": "hackmyresume", |   "name": "hackmyresume", | ||||||
|   "version": "1.7.1", |   "version": "1.8.0", | ||||||
|   "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.", |   "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": { |   "repository": { | ||||||
|     "type": "git", |     "type": "git", | ||||||
| @@ -43,28 +43,50 @@ | |||||||
|     "url": "https://github.com/hacksalot/HackMyResume/issues" |     "url": "https://github.com/hacksalot/HackMyResume/issues" | ||||||
|   }, |   }, | ||||||
|   "bin": { |   "bin": { | ||||||
|     "hackmyresume": "dist/index.js" |     "hackmyresume": "dist/cli/index.js" | ||||||
|   }, |   }, | ||||||
|  |   "main": "dist/index.js", | ||||||
|   "homepage": "https://github.com/hacksalot/HackMyResume", |   "homepage": "https://github.com/hacksalot/HackMyResume", | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|     "chalk": "^1.1.1", |     "chalk": "^1.1.1", | ||||||
|     "commander": "^2.9.0", |     "commander": "^2.9.0", | ||||||
|     "copy": "^0.1.3", |     "copy": "^0.1.3", | ||||||
|  |     "escape-latex": "^0.1.2", | ||||||
|     "extend": "^3.0.0", |     "extend": "^3.0.0", | ||||||
|     "fresca": "~0.6.0", |     "fresca": "~0.6.0", | ||||||
|     "hackmycore": "^0.4.0", |     "fresh-jrs-converter": "^0.2.2", | ||||||
|  |     "fresh-resume-starter": "^0.2.2", | ||||||
|  |     "fresh-themes": "^0.15.1-beta", | ||||||
|  |     "fs-extra": "^0.26.4", | ||||||
|     "handlebars": "^4.0.5", |     "handlebars": "^4.0.5", | ||||||
|  |     "html": "0.0.10", | ||||||
|  |     "is-my-json-valid": "^2.12.4", | ||||||
|  |     "json-lint": "^0.1.0", | ||||||
|  |     "jsonlint": "^1.6.2", | ||||||
|     "lodash": "^3.10.1", |     "lodash": "^3.10.1", | ||||||
|  |     "marked": "^0.3.5", | ||||||
|  |     "mkdirp": "^0.5.1", | ||||||
|  |     "moment": "^2.11.1", | ||||||
|  |     "parse-filepath": "^0.6.3", | ||||||
|  |     "path-exists": "^2.1.0", | ||||||
|  |     "pinkie-promise": "^2.0.0", | ||||||
|     "printf": "^0.2.3", |     "printf": "^0.2.3", | ||||||
|  |     "recursive-readdir-sync": "^1.0.6", | ||||||
|  |     "simple-html-tokenizer": "^0.2.1", | ||||||
|  |     "slash": "^1.0.0", | ||||||
|     "string-padding": "^1.0.2", |     "string-padding": "^1.0.2", | ||||||
|  |     "string.prototype.endswith": "^0.2.0", | ||||||
|     "string.prototype.startswith": "^0.2.0", |     "string.prototype.startswith": "^0.2.0", | ||||||
|  |     "traverse": "^0.6.6", | ||||||
|     "underscore": "^1.8.3", |     "underscore": "^1.8.3", | ||||||
|     "word-wrap": "^1.1.0", |     "word-wrap": "^1.1.0", | ||||||
|  |     "xml-escape": "^1.0.0", | ||||||
|     "yamljs": "^0.2.4" |     "yamljs": "^0.2.4" | ||||||
|   }, |   }, | ||||||
|   "devDependencies": { |   "devDependencies": { | ||||||
|     "chai": "*", |     "chai": "*", | ||||||
|     "fresh-test-resumes": "^0.6.0", |     "dir-compare": "0.0.2", | ||||||
|  |     "fresh-test-resumes": "^0.7.0", | ||||||
|     "grunt": "*", |     "grunt": "*", | ||||||
|     "grunt-cli": "^0.1.13", |     "grunt-cli": "^0.1.13", | ||||||
|     "grunt-contrib-clean": "^0.7.0", |     "grunt-contrib-clean": "^0.7.0", | ||||||
|   | |||||||
| @@ -6,26 +6,24 @@ Error-handling routines for HackMyResume. | |||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| HMSTATUS = require('hackmycore/dist/core/status-codes') | HMSTATUS = require '../core/status-codes' | ||||||
| PKG = require('../../package.json') | PKG = require '../../package.json' | ||||||
| FS = require('fs') | FS = require 'fs' | ||||||
| FCMD = require('hackmycore') | FCMD = require '../index' | ||||||
| PATH = require('path') | PATH = require 'path' | ||||||
| WRAP = require('word-wrap') | WRAP = require 'word-wrap' | ||||||
| M2C = require('hackmycore/dist/utils/md2chalk.js') | M2C = require '../utils/md2chalk' | ||||||
| chalk = require('chalk') | chalk = require 'chalk' | ||||||
| extend = require('extend') | extend = require 'extend' | ||||||
| YAML = require('yamljs') | YAML = require 'yamljs' | ||||||
| printf = require('printf') | printf = require 'printf' | ||||||
| SyntaxErrorEx = require('hackmycore/dist/utils/syntax-error-ex') | SyntaxErrorEx = require '../utils/syntax-error-ex' | ||||||
| require('string.prototype.startswith') | require 'string.prototype.startswith' | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ###* | ###* Error handler for HackMyResume. All errors are handled here. | ||||||
| Error handler for HackMyResume. All errors are handled here. | @class ErrorHandler ### | ||||||
| @class ErrorHandler |  | ||||||
| ### |  | ||||||
| ErrorHandler = module.exports = | ErrorHandler = module.exports = | ||||||
|  |  | ||||||
|   init: ( debug, assert, silent ) -> |   init: ( debug, assert, silent ) -> | ||||||
| @@ -38,7 +36,7 @@ ErrorHandler = module.exports = | |||||||
|   err: ( ex, shouldExit ) -> |   err: ( ex, shouldExit ) -> | ||||||
|  |  | ||||||
|     # Short-circuit logging output if --silent is on |     # Short-circuit logging output if --silent is on | ||||||
|     o = if this.silent then () -> else _defaultLog |     o = if @silent then () -> else _defaultLog | ||||||
|  |  | ||||||
|     # Special case; can probably be removed. |     # Special case; can probably be removed. | ||||||
|     throw ex if ex.pass |     throw ex if ex.pass | ||||||
| @@ -51,7 +49,7 @@ ErrorHandler = module.exports = | |||||||
|  |  | ||||||
|       # Output the error message |       # Output the error message | ||||||
|       objError = assembleError.call @, ex |       objError = assembleError.call @, ex | ||||||
|       o( this[ 'format_' + objError.etype ]( objError.msg )) |       o( @[ 'format_' + objError.etype ]( objError.msg )) | ||||||
|  |  | ||||||
|       # Output the stack (sometimes) |       # Output the stack (sometimes) | ||||||
|       if objError.withStack |       if objError.withStack | ||||||
| @@ -59,20 +57,20 @@ ErrorHandler = module.exports = | |||||||
|         stack && o( chalk.gray( stack ) ); |         stack && o( chalk.gray( stack ) ); | ||||||
|  |  | ||||||
|       # Quit if necessary |       # Quit if necessary | ||||||
|       if ex.quit || objError.quit |       if shouldExit | ||||||
|         if @debug |         if @debug | ||||||
|           o chalk.cyan('Exiting with error code ' + ex.fluenterror.toString()) |           o chalk.cyan('Exiting with error code ' + ex.fluenterror.toString()) | ||||||
|         if this.assert |         if @assert | ||||||
|           ex.pass = true |           ex.pass = true | ||||||
|           throw ex |           throw ex | ||||||
|         process.exit ex.fluenterror |         process.exit ex.fluenterror | ||||||
|  |  | ||||||
|     # Handle raw exceptions |     # Handle raw exceptions | ||||||
|     else |     else | ||||||
|       o( ex ) |       o ex | ||||||
|       stackTrace = ex.stack || (ex.inner && ex.inner.stack) |       stackTrace = ex.stack || (ex.inner && ex.inner.stack) | ||||||
|       if stackTrace && this.debug |       if stackTrace && this.debug | ||||||
|         o( M2C(ex.stack || ex.inner.stack, 'gray') ) |         o M2C(ex.stack || ex.inner.stack, 'gray') | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -139,7 +137,6 @@ assembleError = ( ex ) -> | |||||||
|     when HMSTATUS.pdfGeneration |     when HMSTATUS.pdfGeneration | ||||||
|       msg = M2C( this.msgs.pdfGeneration.msg, 'bold' ) |       msg = M2C( this.msgs.pdfGeneration.msg, 'bold' ) | ||||||
|       msg += chalk.red('\n' + ex.inner) if ex.inner |       msg += chalk.red('\n' + ex.inner) if ex.inner | ||||||
|       withStack = true |  | ||||||
|       quit = false |       quit = false | ||||||
|       etype = 'error' |       etype = 'error' | ||||||
|  |  | ||||||
| @@ -205,16 +202,30 @@ assembleError = ( ex ) -> | |||||||
|       etype = 'custom' |       etype = 'custom' | ||||||
|  |  | ||||||
|     when HMSTATUS.parseError |     when HMSTATUS.parseError | ||||||
|       if SyntaxErrorEx.is( ex.inner ) |       if SyntaxErrorEx.is ex.inner | ||||||
|         console.error printf( M2C(this.msgs.readError.msg, 'red'), ex.file ) |         console.error printf( M2C(this.msgs.readError.msg, 'red'), ex.file ) | ||||||
|         se = new SyntaxErrorEx ex, ex.raw |         se = new SyntaxErrorEx ex, ex.raw | ||||||
|         msg = printf M2C( this.msgs.parseError.msg, 'red' ), se.line, se.col |         if se.line? and se.col? | ||||||
|       else if ex.inner && ex.inner.line != undefined && ex.inner.col != undefined |           msg = printf M2C( this.msgs.parseError.msg[0], 'red' ), se.line, se.col | ||||||
|         msg = printf( M2C( this.msgs.parseError.msg, 'red' ), ex.inner.line, ex.inner.col) |         else if se.line? | ||||||
|  |           msg = printf M2C( this.msgs.parseError.msg[1], 'red' ), se.line | ||||||
|  |         else | ||||||
|  |           msg = M2C @msgs.parseError.msg[2], 'red' | ||||||
|  |       else if ex.inner && ex.inner.line? && ex.inner.col? | ||||||
|  |         msg = printf( M2C( this.msgs.parseError.msg[0], 'red' ), ex.inner.line, ex.inner.col) | ||||||
|       else |       else | ||||||
|         msg = ex |         msg = ex | ||||||
|       etype = 'error' |       etype = 'error' | ||||||
|  |  | ||||||
|  |     when HMSTATUS.createError | ||||||
|  |       # inner.code could be EPERM, EACCES, etc | ||||||
|  |       msg = printf M2C( this.msgs.createError.msg ), ex.inner.path | ||||||
|  |       etype = 'error' | ||||||
|  |  | ||||||
|  |     when HMSTATUS.validateError | ||||||
|  |       msg = printf M2C( @msgs.validateError.msg ), ex.inner.toString() | ||||||
|  |       etype = 'error' | ||||||
|  |  | ||||||
|   msg: msg              # The error message to display |   msg: msg              # The error message to display | ||||||
|   withStack: withStack  # Whether to include the stack |   withStack: withStack  # Whether to include the stack | ||||||
|   quit: quit |   quit: quit | ||||||
|   | |||||||
							
								
								
									
										22
									
								
								src/cli/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								src/cli/index.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | |||||||
|  | #! /usr/bin/env node | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /** | ||||||
|  | Command-line interface (CLI) for HackMyResume. | ||||||
|  | @license MIT. See LICENSE.md for details. | ||||||
|  | @module index.js | ||||||
|  | */ | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | try { | ||||||
|  |  | ||||||
|  |   require('./main')( process.argv ); | ||||||
|  |  | ||||||
|  | } | ||||||
|  | catch( ex ) { | ||||||
|  |  | ||||||
|  |   require('./error').err( ex, true ); | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -6,23 +6,27 @@ Definition of the `main` function. | |||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| HMR = require 'hackmycore' | HMR = require '../index' | ||||||
| PKG = require '../../package.json' | PKG = require '../../package.json' | ||||||
| FS = require 'fs' | FS = require 'fs' | ||||||
| EXTEND = require 'extend' | EXTEND = require 'extend' | ||||||
| chalk = require 'chalk' | chalk = require 'chalk' | ||||||
| PATH = require 'path' | PATH = require 'path' | ||||||
| HMSTATUS = require 'hackmycore/dist/core/status-codes' | HMSTATUS = require '../core/status-codes' | ||||||
| HME = require 'hackmycore/dist/core/event-codes' | HME = require '../core/event-codes' | ||||||
| safeLoadJSON = require 'hackmycore/dist/utils/safe-json-loader' | safeLoadJSON = require '../utils/safe-json-loader' | ||||||
| StringUtils = require 'hackmycore/dist/utils/string.js' | StringUtils = require '../utils/string.js' | ||||||
| _ = require 'underscore' | _ = require 'underscore' | ||||||
| OUTPUT = require './out' | OUTPUT = require './out' | ||||||
| PAD = require 'string-padding' | PAD = require 'string-padding' | ||||||
| Command = require('commander').Command | Command = require('commander').Command | ||||||
|  | M2C = require '../utils/md2chalk' | ||||||
|  | printf = require 'printf' | ||||||
| _opts = { } | _opts = { } | ||||||
| _title = chalk.white.bold('\n*** HackMyResume v' +PKG.version+ ' ***') | _title = chalk.white.bold('\n*** HackMyResume v' +PKG.version+ ' ***') | ||||||
| _out = new OUTPUT( _opts ) | _out = new OUTPUT( _opts ) | ||||||
|  | _err = require('./error') | ||||||
|  | _exitCallback = null | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -33,9 +37,9 @@ line interface as a single method accepting a parameter array. | |||||||
| @param rawArgs {Array} An array of command-line parameters. Will either be | @param rawArgs {Array} An array of command-line parameters. Will either be | ||||||
| process.argv (in production) or custom parameters (in test). | process.argv (in production) or custom parameters (in test). | ||||||
| ### | ### | ||||||
| main = module.exports = (rawArgs) -> | main = module.exports = ( rawArgs, exitCallback ) -> | ||||||
|  |  | ||||||
|   initInfo = initialize( rawArgs ) |   initInfo = initialize( rawArgs, exitCallback ) | ||||||
|   args = initInfo.args |   args = initInfo.args | ||||||
|  |  | ||||||
|   # Create the top-level (application) command... |   # Create the top-level (application) command... | ||||||
| @@ -124,15 +128,15 @@ main = module.exports = (rawArgs) -> | |||||||
|   program.parse( args ) |   program.parse( args ) | ||||||
|  |  | ||||||
|   if !program.args.length |   if !program.args.length | ||||||
|     throw { fluenterror: 4 } |     throw fluenterror: 4 | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ### Massage command-line args and setup Commander.js. ### | ### Massage command-line args and setup Commander.js. ### | ||||||
| initialize = ( ar ) -> | initialize = ( ar, exitCallback ) -> | ||||||
|  |  | ||||||
|   o = initOptions( ar ); |  | ||||||
|  |  | ||||||
|  |   _exitCallback = exitCallback || process.exit | ||||||
|  |   o = initOptions ar | ||||||
|   o.silent || logMsg( _title ) |   o.silent || logMsg( _title ) | ||||||
|  |  | ||||||
|   # Emit debug prelude if --debug was specified |   # Emit debug prelude if --debug was specified | ||||||
| @@ -147,14 +151,22 @@ initialize = ( ar ) -> | |||||||
|     #_out.log(chalk.cyan(PAD('  fresh-jrs-converter:',25, null, PAD.RIGHT)) + chalk.cyan.bold( PKG.dependencies['fresh-jrs-converter'] )) |     #_out.log(chalk.cyan(PAD('  fresh-jrs-converter:',25, null, PAD.RIGHT)) + chalk.cyan.bold( PKG.dependencies['fresh-jrs-converter'] )) | ||||||
|     _out.log('') |     _out.log('') | ||||||
|  |  | ||||||
|  |   _err.init o.debug, o.assert, o.silent | ||||||
|  |  | ||||||
|   # Handle invalid verbs here (a bit easier here than in commander.js)... |   # 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 ] | ||||||
|     throw { fluenterror: HMSTATUS.invalidCommand, quit: true, attempted: o.orgVerb } |     _err.err fluenterror: HMSTATUS.invalidCommand, quit: true, attempted: o.orgVerb, true | ||||||
|  |  | ||||||
|   # Override the .missingArgument behavior |   # Override the .missingArgument behavior | ||||||
|   Command.prototype.missingArgument = (name) -> |   Command.prototype.missingArgument = (name) -> | ||||||
|  |     _err.err | ||||||
|  |       fluenterror: | ||||||
|         if this.name() != 'new' |         if this.name() != 'new' | ||||||
|       throw { fluenterror: HMSTATUS.resumeNotFound, quit: true } |         then HMSTATUS.resumeNotFound | ||||||
|  |         else HMSTATUS.createNameMissing | ||||||
|  |       , true | ||||||
|  |     return | ||||||
|  |  | ||||||
|  |  | ||||||
|   # Override the .helpInformation behavior |   # Override the .helpInformation behavior | ||||||
|   Command.prototype.helpInformation = -> |   Command.prototype.helpInformation = -> | ||||||
| @@ -205,23 +217,17 @@ initOptions = ( ar ) -> | |||||||
|             oJSON = inf.json |             oJSON = inf.json | ||||||
|           # TODO: Error handling |           # TODO: Error handling | ||||||
|  |  | ||||||
|   # Grab the --debug flag |   # Grab the --debug flag, --silent, --assert and --no-color flags | ||||||
|   isDebug = _.some( args, (v) -> |   isDebug = _.some args, (v) -> v == '-d' || v == '--debug' | ||||||
|     return v == '-d' || v == '--debug' |   isSilent = _.some args, (v) -> v == '-s' || v == '--silent' | ||||||
|   ) |   isAssert = _.some args, (v) -> v == '-a' || v == '--assert' | ||||||
|  |  | ||||||
|   # Grab the --silent flag |  | ||||||
|   isSilent = _.some( args, (v) -> |  | ||||||
|     return v == '-s' || v == '--silent' |  | ||||||
|   ) |  | ||||||
|  |  | ||||||
|   # Grab the --no-color flag |  | ||||||
|   isMono = _.some args, (v) -> v == '--no-color' |   isMono = _.some args, (v) -> v == '--no-color' | ||||||
|  |  | ||||||
|   return { |   return { | ||||||
|     color: !isMono, |     color: !isMono, | ||||||
|     debug: isDebug, |     debug: isDebug, | ||||||
|     silent: isSilent, |     silent: isSilent, | ||||||
|  |     assert: isAssert, | ||||||
|     orgVerb: oVerb, |     orgVerb: oVerb, | ||||||
|     verb: verb, |     verb: verb, | ||||||
|     json: oJSON, |     json: oJSON, | ||||||
| @@ -233,17 +239,45 @@ initOptions = ( ar ) -> | |||||||
| ### Invoke a HackMyResume verb. ### | ### Invoke a HackMyResume verb. ### | ||||||
| execute = ( src, dst, opts, log ) -> | execute = ( src, dst, opts, log ) -> | ||||||
|  |  | ||||||
|  |   # Create the verb | ||||||
|  |   v = new HMR.verbs[ @name() ]() | ||||||
|  |  | ||||||
|  |   # Initialize command-specific options | ||||||
|   loadOptions.call( this, opts, this.parent.jsonArgs ) |   loadOptions.call( this, opts, this.parent.jsonArgs ) | ||||||
|   hand = require( './error' ) |  | ||||||
|   hand.init( _opts.debug, _opts.assert, _opts.silent ) |   # Set up error/output handling | ||||||
|   v = new HMR.verbs[ this.name() ]() |  | ||||||
|   _opts.errHandler = v |   _opts.errHandler = v | ||||||
|   _out.init( _opts ) |   _out.init _opts | ||||||
|   v.on( 'hmr:status', -> _out.do.apply( _out, arguments ) ) |  | ||||||
|   v.on( 'hmr:error', ->  hand.err.apply( hand, arguments ) ) |   # Hook up event notifications | ||||||
|   v.invoke.call( v, src, dst, _opts, log ) |   v.on 'hmr:status', -> _out.do.apply _out, arguments | ||||||
|   if v.errorCode |   v.on 'hmr:error', ->  _err.err.apply _err, arguments | ||||||
|     process.exit(v.errorCode) |  | ||||||
|  |   # Invoke the verb using promise syntax | ||||||
|  |   prom = v.invoke.call v, src, dst, _opts, log | ||||||
|  |   prom.then executeSuccess, executeFail | ||||||
|  |   return | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ### Success handler for verb invocations. Calls process.exit by default ### | ||||||
|  | executeSuccess = (obj) -> | ||||||
|  |   # Can't call _exitCallback here (process.exit) when PDF is running in BK | ||||||
|  |   #_exitCallback 0; return | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ### Failure handler for verb invocations. Calls process.exit by default ### | ||||||
|  | executeFail = (err) -> | ||||||
|  |   finalErrorCode = -1 | ||||||
|  |   if err | ||||||
|  |     finalErrorCode = if err.fluenterror then err.fluenterror else err | ||||||
|  |   if _opts.debug | ||||||
|  |     msgs = require('./msg').errors; | ||||||
|  |     logMsg printf M2C( msgs.exiting.msg, 'cyan' ), finalErrorCode | ||||||
|  |     logMsg err.stack if err.stack | ||||||
|  |   _exitCallback finalErrorCode | ||||||
|  |   return | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -3,6 +3,8 @@ events: | |||||||
|     msg: Invoking **%s** command. |     msg: Invoking **%s** command. | ||||||
|   beforeCreate: |   beforeCreate: | ||||||
|     msg: Creating new **%s** resume: **%s** |     msg: Creating new **%s** resume: **%s** | ||||||
|  |   afterCreate: | ||||||
|  |     msg: Creating new **%s** resume: **%s** | ||||||
|   afterRead: |   afterRead: | ||||||
|     msg: Reading **%s** resume: **%s** |     msg: Reading **%s** resume: **%s** | ||||||
|   beforeTheme: |   beforeTheme: | ||||||
| @@ -41,6 +43,8 @@ events: | |||||||
|       - "VALID!" |       - "VALID!" | ||||||
|       - "INVALID" |       - "INVALID" | ||||||
|       - "BROKEN" |       - "BROKEN" | ||||||
|  |       - "MISSING" | ||||||
|  |       - "ERROR" | ||||||
|   beforePeek: |   beforePeek: | ||||||
|     msg: |     msg: | ||||||
|       - Peeking at **%s** in **%s** |       - Peeking at **%s** in **%s** | ||||||
| @@ -79,7 +83,10 @@ errors: | |||||||
|   readError: |   readError: | ||||||
|     msg: Reading **???** resume: **%s** |     msg: Reading **???** resume: **%s** | ||||||
|   parseError: |   parseError: | ||||||
|     msg: Invalid or corrupt JSON on line %s column %s. |     msg: | ||||||
|  |       - Invalid or corrupt JSON on line %s column %s. | ||||||
|  |       - Invalid or corrupt JSON on line %s. | ||||||
|  |       - Invalid or corrupt JSON. | ||||||
|   invalidHelperUse: |   invalidHelperUse: | ||||||
|     msg: "**Warning**: Incorrect use of the **%s** theme helper." |     msg: "**Warning**: Incorrect use of the **%s** theme helper." | ||||||
|   fileSaveError: |   fileSaveError: | ||||||
| @@ -96,3 +103,9 @@ errors: | |||||||
|     msg: "Invalid number of parameters. Expected: **%s**." |     msg: "Invalid number of parameters. Expected: **%s**." | ||||||
|   missingParam: |   missingParam: | ||||||
|     msg: The '**%s**' parameter was needed but not supplied. |     msg: The '**%s**' parameter was needed but not supplied. | ||||||
|  |   createError: | ||||||
|  |     msg: Failed to create **'%s'**. | ||||||
|  |   exiting: | ||||||
|  |     msg: Exiting with status code **%s**. | ||||||
|  |   validateError: | ||||||
|  |     msg: "An error occurred during validation:\n%s" | ||||||
|   | |||||||
| @@ -7,10 +7,9 @@ Output routines for HackMyResume. | |||||||
|  |  | ||||||
|  |  | ||||||
| chalk = require('chalk') | chalk = require('chalk') | ||||||
| HME = require('hackmycore/dist/core/event-codes') | HME = require('../core/event-codes') | ||||||
| _ = require('underscore') | _ = require('underscore') | ||||||
| Class = require('hackmycore/dist/utils/class.js') | M2C = require('../utils/md2chalk.js') | ||||||
| M2C = require('hackmycore/dist/utils/md2chalk.js') |  | ||||||
| PATH = require('path') | PATH = require('path') | ||||||
| LO = require('lodash') | LO = require('lodash') | ||||||
| FS = require('fs') | FS = require('fs') | ||||||
| @@ -22,12 +21,22 @@ pad = require('string-padding') | |||||||
| dbgStyle = 'cyan'; | dbgStyle = 'cyan'; | ||||||
|  |  | ||||||
|  |  | ||||||
| ###* A stateful output module. All HMR console output handled here. ### |  | ||||||
| OutputHandler = module.exports = Class.extend |  | ||||||
|  |  | ||||||
|   init: ( opts ) -> | ###* A stateful output module. All HMR console output handled here. ### | ||||||
|     @opts = EXTEND( true, this.opts || { }, opts ) | module.exports = class OutputHandler | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   constructor: ( opts ) -> | ||||||
|  |     @init opts | ||||||
|  |     return | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   init: (opts) -> | ||||||
|  |     @opts = EXTEND( true, @opts || { }, opts ) | ||||||
|     @msgs = YAML.load(PATH.join( __dirname, 'msg.yml' )).events |     @msgs = YAML.load(PATH.join( __dirname, 'msg.yml' )).events | ||||||
|  |     return | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -38,6 +47,7 @@ OutputHandler = module.exports = Class.extend | |||||||
|     @opts.silent || console.log( finished ) |     @opts.silent || console.log( finished ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|   do: ( evt ) -> |   do: ( evt ) -> | ||||||
|  |  | ||||||
|     that = @ |     that = @ | ||||||
| @@ -49,8 +59,12 @@ OutputHandler = module.exports = Class.extend | |||||||
|         this.opts.debug && |         this.opts.debug && | ||||||
|         L( M2C( this.msgs.begin.msg, dbgStyle), evt.cmd.toUpperCase() ) |         L( M2C( this.msgs.begin.msg, dbgStyle), evt.cmd.toUpperCase() ) | ||||||
|  |  | ||||||
|       when HME.beforeCreate |       #when HME.beforeCreate | ||||||
|         L( M2C( this.msgs.beforeCreate.msg, 'green' ), evt.fmt, evt.file ) |         #L( M2C( this.msgs.beforeCreate.msg, 'green' ), evt.fmt, evt.file ) | ||||||
|  |         #break; | ||||||
|  |  | ||||||
|  |       when HME.afterCreate | ||||||
|  |         L( M2C( @msgs.beforeCreate.msg, if evt.isError then 'red' else 'green' ), evt.fmt, evt.file ) | ||||||
|         break; |         break; | ||||||
|  |  | ||||||
|       when HME.beforeTheme |       when HME.beforeTheme | ||||||
| @@ -108,7 +122,7 @@ OutputHandler = module.exports = Class.extend | |||||||
|       when HME.afterAnalyze |       when HME.afterAnalyze | ||||||
|         info = evt.info |         info = evt.info | ||||||
|         rawTpl = FS.readFileSync( PATH.join( __dirname, 'analyze.hbs' ), 'utf8') |         rawTpl = FS.readFileSync( PATH.join( __dirname, 'analyze.hbs' ), 'utf8') | ||||||
|         HANDLEBARS.registerHelper( require('hackmycore/dist/helpers/console-helpers') ) |         HANDLEBARS.registerHelper( require('../helpers/console-helpers') ) | ||||||
|         template = HANDLEBARS.compile(rawTpl, { strict: false, assumeObjects: false }) |         template = HANDLEBARS.compile(rawTpl, { strict: false, assumeObjects: false }) | ||||||
|         tot = 0 |         tot = 0 | ||||||
|         info.keywords.forEach (g) -> tot += g.count |         info.keywords.forEach (g) -> tot += g.count | ||||||
| @@ -126,29 +140,43 @@ OutputHandler = module.exports = Class.extend | |||||||
|           evt.file, evt.fmt ); |           evt.file, evt.fmt ); | ||||||
|  |  | ||||||
|       when HME.afterValidate |       when HME.afterValidate | ||||||
|         style = if evt.isValid then 'green' else 'yellow' |         style = 'red' | ||||||
|         L( |         adj = '' | ||||||
|           M2C( this.msgs.afterValidate.msg[0], 'white' ) + |         msgs = @msgs.afterValidate.msg; | ||||||
|           chalk[style].bold( |         switch evt.status | ||||||
|             if evt.isValid |           when 'valid' then style = 'green'; adj = msgs[1] | ||||||
|             then this.msgs.afterValidate.msg[1] |           when 'invalid' then style = 'yellow'; adj = msgs[2] | ||||||
|             else this.msgs.afterValidate.msg[2] ), |           when 'broken' then style = 'red'; adj = msgs[3] | ||||||
|           evt.file, evt.fmt |           when 'missing' then style = 'red'; adj = msgs[4] | ||||||
|         ); |           when 'unknown' then style = 'red'; adj = msgs[5] | ||||||
|  |         evt.schema = evt.schema.replace('jars','JSON Resume').toUpperCase() | ||||||
|  |         L(M2C( msgs[0], 'white' ) + chalk[style].bold(adj), evt.file, evt.schema) | ||||||
|  |  | ||||||
|         if evt.errors |         if evt.violations | ||||||
|           _.each( evt.errors, (err,idx) -> |           _.each evt.violations, (err,idx) -> | ||||||
|             L( chalk.yellow.bold('--> ') + chalk.yellow(err.field.replace('data.','resume.').toUpperCase() + ' ' + err.message)) |             L( chalk.yellow.bold('--> ') + | ||||||
|           , @) |                chalk.yellow(err.field.replace('data.','resume.').toUpperCase() + | ||||||
|  |                ' ' + err.message)) | ||||||
|  |             return | ||||||
|  |           , @ | ||||||
|  |         return | ||||||
|  |  | ||||||
|       when HME.afterPeek |       when HME.afterPeek | ||||||
|         sty = if evt.error then 'red' else ( if evt.target != undefined then 'green' else 'yellow' ) |         sty = if evt.error then 'red' else ( if evt.target != undefined then 'green' else 'yellow' ) | ||||||
|  |  | ||||||
|  |         # "Peeking at 'someKey' in 'someFile'." | ||||||
|         if evt.requested |         if evt.requested | ||||||
|           L(M2C(this.msgs.beforePeek.msg[0], sty), evt.requested, evt.file) |           L(M2C(this.msgs.beforePeek.msg[0], sty), evt.requested, evt.file) | ||||||
|         else |         else | ||||||
|           L(M2C(this.msgs.beforePeek.msg[1], sty), evt.file) |           L(M2C(this.msgs.beforePeek.msg[1], sty), evt.file) | ||||||
|  |  | ||||||
|         if evt.target != undefined |         # If the key was present, print it | ||||||
|  |         if evt.target != undefined and !evt.error | ||||||
|           console.dir( evt.target, { depth: null, colors: true } ) |           console.dir( evt.target, { depth: null, colors: true } ) | ||||||
|  |  | ||||||
|  |         # If the key was not present, but no error occurred, print it | ||||||
|         else if !evt.error |         else if !evt.error | ||||||
|           L(M2C( this.msgs.afterPeek.msg, 'yellow'), evt.requested, evt.file); |           L M2C( this.msgs.afterPeek.msg, 'yellow'), evt.requested, evt.file | ||||||
|  |  | ||||||
|  |         else if evt.error | ||||||
|  |           L chalk.red( evt.error.inner.inner ) | ||||||
|   | |||||||
							
								
								
									
										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 | ||||||
							
								
								
									
										18
									
								
								src/core/default-formats.coffee
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/core/default-formats.coffee
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | |||||||
|  | ### | ||||||
|  | Event code definitions. | ||||||
|  | @module core/default-formats | ||||||
|  | @license MIT. See LICENSE.md for details. | ||||||
|  | ### | ||||||
|  |  | ||||||
|  | ###* Supported resume formats. ### | ||||||
|  | module.exports = [ | ||||||
|  |   { name: 'html', ext: 'html', gen: new (require('../generators/html-generator'))() }, | ||||||
|  |   { name: 'txt',  ext: 'txt', gen: new (require('../generators/text-generator'))()  }, | ||||||
|  |   { name: 'doc',  ext: 'doc',  fmt: 'xml', gen: new (require('../generators/word-generator'))() }, | ||||||
|  |   { name: 'pdf',  ext: 'pdf', fmt: 'html', is: false, gen: new (require('../generators/html-pdf-cli-generator'))() }, | ||||||
|  |   { name: 'png',  ext: 'png', fmt: 'html', is: false, gen: new (require('../generators/html-png-generator'))() }, | ||||||
|  |   { name: 'md', ext: 'md', fmt: 'txt', gen: new (require('../generators/markdown-generator'))() }, | ||||||
|  |   { name: 'json', ext: 'json', gen: new (require('../generators/json-generator'))() }, | ||||||
|  |   { name: 'yml', ext: 'yml', fmt: 'yml', gen: new (require('../generators/json-yaml-generator'))() }, | ||||||
|  |   { name: 'latex', ext: 'tex', fmt: 'latex', gen: new (require('../generators/latex-generator'))() } | ||||||
|  | ] | ||||||
							
								
								
									
										13
									
								
								src/core/default-options.coffee
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/core/default-options.coffee
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | |||||||
|  | ### | ||||||
|  | Event code definitions. | ||||||
|  | @module core/default-options | ||||||
|  | @license MIT. See LICENSE.md for details. | ||||||
|  | ### | ||||||
|  |  | ||||||
|  | module.exports = | ||||||
|  |   theme: 'modern' | ||||||
|  |   prettify: # ← See https://github.com/beautify-web/js-beautify#options | ||||||
|  |     indent_size: 2 | ||||||
|  |     unformatted: ['em','strong'] | ||||||
|  |     max_char: 80, # ← See lib/html.js in above-linked repo | ||||||
|  |     # wrap_line_length: 120, ← Don't use this | ||||||
							
								
								
									
										77
									
								
								src/core/empty-jrs.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								src/core/empty-jrs.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,77 @@ | |||||||
|  | { | ||||||
|  |   "basics": { | ||||||
|  |     "name": "", | ||||||
|  |     "label": "", | ||||||
|  |     "picture": "", | ||||||
|  |     "email": "", | ||||||
|  |     "phone": "", | ||||||
|  |     "degree": "", | ||||||
|  |     "website": "", | ||||||
|  |     "summary": "", | ||||||
|  |     "location": { | ||||||
|  |       "address": "", | ||||||
|  |       "postalCode": "", | ||||||
|  |       "city": "", | ||||||
|  |       "countryCode": "", | ||||||
|  |       "region": "" | ||||||
|  |     }, | ||||||
|  |     "profiles": [{ | ||||||
|  |       "network": "", | ||||||
|  |       "username": "", | ||||||
|  |       "url": "" | ||||||
|  |     }] | ||||||
|  |   }, | ||||||
|  |  | ||||||
|  |   "work": [{ | ||||||
|  |     "company": "", | ||||||
|  |     "position": "", | ||||||
|  |     "website": "", | ||||||
|  |     "startDate": "", | ||||||
|  |     "endDate": "", | ||||||
|  |     "summary": "", | ||||||
|  |     "highlights": [ | ||||||
|  |       "" | ||||||
|  |     ] | ||||||
|  |   }], | ||||||
|  |  | ||||||
|  |   "awards": [{ | ||||||
|  |     "title": "", | ||||||
|  |     "date": "", | ||||||
|  |     "awarder": "", | ||||||
|  |     "summary": "" | ||||||
|  |   }], | ||||||
|  |  | ||||||
|  |   "education": [{ | ||||||
|  |     "institution": "", | ||||||
|  |     "area": "", | ||||||
|  |     "studyType": "", | ||||||
|  |     "startDate": "", | ||||||
|  |     "endDate": "", | ||||||
|  |     "gpa": "", | ||||||
|  |     "courses": [ "" ] | ||||||
|  |   }], | ||||||
|  |  | ||||||
|  |   "publications": [{ | ||||||
|  |     "name": "", | ||||||
|  |     "publisher": "", | ||||||
|  |     "releaseDate": "", | ||||||
|  |     "website": "", | ||||||
|  |     "summary": "" | ||||||
|  |   }], | ||||||
|  |  | ||||||
|  |   "volunteer": [{ | ||||||
|  |     "organization": "", | ||||||
|  |     "position": "", | ||||||
|  |     "website": "", | ||||||
|  |     "startDate": "", | ||||||
|  |     "endDate": "", | ||||||
|  |     "summary": "", | ||||||
|  |     "highlights": [ "" ] | ||||||
|  |   }], | ||||||
|  |  | ||||||
|  |   "skills": [{ | ||||||
|  |       "name": "", | ||||||
|  |       "level": "", | ||||||
|  |       "keywords": [""] | ||||||
|  |   }] | ||||||
|  | } | ||||||
							
								
								
									
										38
									
								
								src/core/event-codes.coffee
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/core/event-codes.coffee
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | |||||||
|  | ### | ||||||
|  | Event code definitions. | ||||||
|  | @module core/event-codes | ||||||
|  | @license MIT. See LICENSE.md for details. | ||||||
|  | ### | ||||||
|  |  | ||||||
|  |  | ||||||
|  | module.exports = | ||||||
|  |   error:            -1 | ||||||
|  |   success:          0 | ||||||
|  |   begin:            1 | ||||||
|  |   end:              2 | ||||||
|  |   beforeRead:       3 | ||||||
|  |   afterRead:        4 | ||||||
|  |   beforeCreate:     5 | ||||||
|  |   afterCreate:      6 | ||||||
|  |   beforeTheme:      7 | ||||||
|  |   afterTheme:       8 | ||||||
|  |   beforeMerge:      9 | ||||||
|  |   afterMerge:       10 | ||||||
|  |   beforeGenerate:   11 | ||||||
|  |   afterGenerate:    12 | ||||||
|  |   beforeAnalyze:    13 | ||||||
|  |   afterAnalyze:     14 | ||||||
|  |   beforeConvert:    15 | ||||||
|  |   afterConvert:     16 | ||||||
|  |   verifyOutputs:    17 | ||||||
|  |   beforeParse:      18 | ||||||
|  |   afterParse:       19 | ||||||
|  |   beforePeek:       20 | ||||||
|  |   afterPeek:        21 | ||||||
|  |   beforeInlineConvert: 22 | ||||||
|  |   afterInlineConvert: 23 | ||||||
|  |   beforeValidate:   24 | ||||||
|  |   afterValidate:    25 | ||||||
|  |   beforeWrite:      26 | ||||||
|  |   afterWrite:       27 | ||||||
|  |   applyTheme:       28 | ||||||
							
								
								
									
										77
									
								
								src/core/fluent-date.coffee
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								src/core/fluent-date.coffee
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,77 @@ | |||||||
|  | ###* | ||||||
|  | The HackMyResume date representation. | ||||||
|  | @license MIT. See LICENSE.md for details. | ||||||
|  | @module core/fluent-date | ||||||
|  | ### | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | moment = require 'moment' | ||||||
|  | require('../utils/string') | ||||||
|  |  | ||||||
|  | ###* | ||||||
|  | Create a FluentDate from a string or Moment date object. There are a few date | ||||||
|  | formats to be aware of here. | ||||||
|  | 1. The words "Present" and "Now", referring to the current date | ||||||
|  | 2. The default "YYYY-MM-DD" format used in JSON Resume ("2015-02-10") | ||||||
|  | 3. Year-and-month only ("2015-04") | ||||||
|  | 4. Year-only "YYYY" ("2015") | ||||||
|  | 5. The friendly HackMyResume "mmm YYYY" format ("Mar 2015" or "Dec 2008") | ||||||
|  | 6. Empty dates ("", " ") | ||||||
|  | 7. Any other date format that Moment.js can parse from | ||||||
|  | Note: Moment can transparently parse all or most of these, without requiring us | ||||||
|  | to specify a date format...but for maximum parsing safety and to avoid Moment | ||||||
|  | deprecation warnings, it's recommended to either a) explicitly specify the date | ||||||
|  | format or b) use an ISO format. For clarity, we handle these cases explicitly. | ||||||
|  | @class FluentDate | ||||||
|  | ### | ||||||
|  |  | ||||||
|  | class FluentDate | ||||||
|  |  | ||||||
|  |   constructor: (dt) -> | ||||||
|  |     @rep = this.fmt dt | ||||||
|  |  | ||||||
|  |   @isCurrent: (dt) -> | ||||||
|  |     !dt || (String.is(dt) and /^(present|now|current)$/.test(dt)) | ||||||
|  |  | ||||||
|  | months = {} | ||||||
|  | abbr = {} | ||||||
|  | moment.months().forEach((m,idx) -> months[m.toLowerCase()] = idx+1 ) | ||||||
|  | moment.monthsShort().forEach((m,idx) -> abbr[m.toLowerCase()]=idx+1 ) | ||||||
|  | abbr.sept = 9 | ||||||
|  | module.exports = FluentDate | ||||||
|  |  | ||||||
|  | FluentDate.fmt = ( dt, throws ) -> | ||||||
|  |  | ||||||
|  |   throws = (throws == undefined || throws == null) || throws | ||||||
|  |  | ||||||
|  |   if typeof dt == 'string' or dt instanceof String | ||||||
|  |     dt = dt.toLowerCase().trim() | ||||||
|  |     if /^(present|now|current)$/.test(dt) # "Present", "Now" | ||||||
|  |       return moment() | ||||||
|  |     else if /^\D+\s+\d{4}$/.test(dt) # "Mar 2015" | ||||||
|  |       parts = dt.split(' '); | ||||||
|  |       month = (months[parts[0]] || abbr[parts[0]]); | ||||||
|  |       temp = parts[1] + '-' + (month < 10 ? '0' + month : month.toString()); | ||||||
|  |       return moment temp, 'YYYY-MM' | ||||||
|  |     else if /^\d{4}-\d{1,2}$/.test(dt) # "2015-03", "1998-4" | ||||||
|  |       return moment dt, 'YYYY-MM' | ||||||
|  |     else if /^\s*\d{4}\s*$/.test(dt) # "2015" | ||||||
|  |       return moment dt, 'YYYY' | ||||||
|  |     else if /^\s*$/.test(dt) # "", " " | ||||||
|  |       return moment() | ||||||
|  |     else | ||||||
|  |       mt = moment dt | ||||||
|  |       if mt.isValid() | ||||||
|  |         return mt | ||||||
|  |       if throws | ||||||
|  |         throw 'Invalid date format encountered.' | ||||||
|  |       return null | ||||||
|  |   else | ||||||
|  |     if !dt | ||||||
|  |       return moment() | ||||||
|  |     else if dt.isValid and dt.isValid() | ||||||
|  |       return dt | ||||||
|  |     if throws | ||||||
|  |       throw 'Unknown date object encountered.' | ||||||
|  |     return null | ||||||
							
								
								
									
										410
									
								
								src/core/fresh-resume.coffee
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										410
									
								
								src/core/fresh-resume.coffee
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,410 @@ | |||||||
|  | ###* | ||||||
|  | Definition of the FRESHResume class. | ||||||
|  | @license MIT. See LICENSE.md for details. | ||||||
|  | @module core/fresh-resume | ||||||
|  | ### | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | FS = require 'fs' | ||||||
|  | extend = require 'extend' | ||||||
|  | validator = require 'is-my-json-valid' | ||||||
|  | _ = require 'underscore' | ||||||
|  | __ = require 'lodash' | ||||||
|  | PATH = require 'path' | ||||||
|  | moment = require 'moment' | ||||||
|  | XML = require 'xml-escape' | ||||||
|  | MD = require 'marked' | ||||||
|  | CONVERTER = require 'fresh-jrs-converter' | ||||||
|  | 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 | ||||||
|  | object is an instantiation of that JSON decorated with utility methods. | ||||||
|  | @constructor | ||||||
|  | ### | ||||||
|  | class FreshResume extends AbstractResume | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   ###* Initialize the the FreshResume from JSON string data. ### | ||||||
|  |   parse: ( stringData, opts ) -> | ||||||
|  |     @imp = @imp ? raw: stringData | ||||||
|  |     this.parseJSON JSON.parse( stringData ), opts | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   ###* | ||||||
|  |   Initialize the FreshResume from JSON. | ||||||
|  |   Open and parse the specified FRESH 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. | ||||||
|  |   } | ||||||
|  |   ### | ||||||
|  |   parseJSON: ( rep, opts ) -> | ||||||
|  |  | ||||||
|  |     # Ignore any element with the 'ignore: true' designator. | ||||||
|  |     that = @ | ||||||
|  |     traverse = require 'traverse' | ||||||
|  |     ignoreList = [] | ||||||
|  |     scrubbed = traverse( rep ).map ( x ) -> | ||||||
|  |       if !@isLeaf && @node.ignore | ||||||
|  |         if @node.ignore == true || this.node.ignore == 'true' | ||||||
|  |           ignoreList.push this.node | ||||||
|  |           @remove() | ||||||
|  |  | ||||||
|  |     # Now apply the resume representation onto this object | ||||||
|  |     extend( true, @, scrubbed ); | ||||||
|  |  | ||||||
|  |     # If the resume has already been processed, then we are being called from | ||||||
|  |     # the .dupe method, and there's no need to do any post processing | ||||||
|  |     if !@imp?.processed | ||||||
|  |       # Set up metadata TODO: Clean up metadata on the object model. | ||||||
|  |       opts = opts || { } | ||||||
|  |       if opts.imp == undefined || opts.imp | ||||||
|  |         @imp = @imp || { } | ||||||
|  |         @imp.title = (opts.title || @imp.title) || @name | ||||||
|  |         unless @imp.raw | ||||||
|  |           @imp.raw = JSON.stringify rep | ||||||
|  |       @imp.processed = true | ||||||
|  |       # Parse dates, sort dates, and calculate computed values | ||||||
|  |       (opts.date == undefined || opts.date) && _parseDates.call( this ); | ||||||
|  |       (opts.sort == undefined || opts.sort) && this.sort(); | ||||||
|  |       (opts.compute == undefined || opts.compute) && (@computed = { | ||||||
|  |          numYears: this.duration(), | ||||||
|  |          keywords: this.keywords() | ||||||
|  |       }); | ||||||
|  |  | ||||||
|  |     @ | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   ###* Save the sheet to disk (for environments that have disk access). ### | ||||||
|  |   save: ( filename ) -> | ||||||
|  |     @imp.file = filename || @imp.file | ||||||
|  |     FS.writeFileSync @imp.file, @stringify(), 'utf8' | ||||||
|  |     @ | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   ###* | ||||||
|  |   Save the sheet to disk in a specific format, either FRESH or JSON Resume. | ||||||
|  |   ### | ||||||
|  |   saveAs: ( filename, format ) -> | ||||||
|  |     if format != 'JRS' | ||||||
|  |       @imp.file = filename || @imp.file | ||||||
|  |       FS.writeFileSync @imp.file, @stringify(), 'utf8' | ||||||
|  |     else | ||||||
|  |       newRep = CONVERTER.toJRS this | ||||||
|  |       FS.writeFileSync filename, JRSResume.stringify( newRep ), 'utf8' | ||||||
|  |     @ | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   ###* | ||||||
|  |   Duplicate this FreshResume instance. | ||||||
|  |   This method first extend()s this object onto an empty, creating a deep copy, | ||||||
|  |   and then passes the result into a new FreshResume instance via .parseJSON. | ||||||
|  |   We do it this way to create a true clone of the object without re-running any | ||||||
|  |   of the associated processing. | ||||||
|  |   ### | ||||||
|  |   dupe: () -> | ||||||
|  |     jso = extend true, { }, @ | ||||||
|  |     rnew = new FreshResume() | ||||||
|  |     rnew.parseJSON jso, { } | ||||||
|  |     rnew | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   ###* | ||||||
|  |   Convert this object to a JSON string, sanitizing meta-properties along the | ||||||
|  |   way. | ||||||
|  |   ### | ||||||
|  |   stringify: () -> FreshResume.stringify @ | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   ###* | ||||||
|  |   Create a copy of this resume in which all string fields have been run through | ||||||
|  |   a transformation function (such as a Markdown filter or XML encoder). | ||||||
|  |   TODO: Move this out of FRESHResume. | ||||||
|  |   ### | ||||||
|  |   transformStrings: ( filt, transformer ) -> | ||||||
|  |     ret = this.dupe() | ||||||
|  |     trx = require '../utils/string-transformer' | ||||||
|  |     trx ret, filt, transformer | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   ###* | ||||||
|  |   Create a copy of this resume in which all fields have been interpreted as | ||||||
|  |   Markdown. | ||||||
|  |   ### | ||||||
|  |   markdownify: () -> | ||||||
|  |  | ||||||
|  |     MDIN = ( txt ) -> | ||||||
|  |       return MD(txt || '' ).replace(/^\s*<p>|<\/p>\s*$/gi, '') | ||||||
|  |  | ||||||
|  |     trx = ( key, val ) -> | ||||||
|  |       if key == 'summary' | ||||||
|  |         return MD val | ||||||
|  |       MDIN(val) | ||||||
|  |  | ||||||
|  |     return @transformStrings ['skills','url','start','end','date'], trx | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   ###* | ||||||
|  |   Create a copy of this resume in which all fields have been interpreted as | ||||||
|  |   Markdown. | ||||||
|  |   ### | ||||||
|  |   xmlify: () -> | ||||||
|  |     trx = (key, val) -> XML val | ||||||
|  |     return @transformStrings [], trx | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   ###* Return the resume format. ### | ||||||
|  |   format: () -> 'FRESH' | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   ###* | ||||||
|  |   Return internal metadata. Create if it doesn't exist. | ||||||
|  |   ### | ||||||
|  |   i: () -> this.imp = this.imp || { } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   ###* Return a unique list of all keywords across all skills. ### | ||||||
|  |   keywords: () -> | ||||||
|  |     flatSkills = [] | ||||||
|  |     if @skills | ||||||
|  |       if @skills.sets | ||||||
|  |         flatSkills = @skills.sets.map((sk) -> sk.skills ).reduce( (a,b) -> a.concat(b) ) | ||||||
|  |       else if @skills.list | ||||||
|  |         flatSkills = flatSkills.concat( this.skills.list.map (sk) -> return sk.name ) | ||||||
|  |       flatSkills = _.uniq flatSkills | ||||||
|  |     flatSkills | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   ###* | ||||||
|  |   Reset the sheet to an empty state. TODO: refactor/review | ||||||
|  |   ### | ||||||
|  |   clear: ( clearMeta ) -> | ||||||
|  |     clearMeta = ((clearMeta == undefined) && true) || clearMeta | ||||||
|  |     delete this.imp if clearMeta | ||||||
|  |     delete this.computed # Don't use Object.keys() here | ||||||
|  |     delete this.employment | ||||||
|  |     delete this.service | ||||||
|  |     delete this.education | ||||||
|  |     delete this.recognition | ||||||
|  |     delete this.reading | ||||||
|  |     delete this.writing | ||||||
|  |     delete this.interests | ||||||
|  |     delete this.skills | ||||||
|  |     delete this.social | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   ###* | ||||||
|  |   Get a safe count of the number of things in a section. | ||||||
|  |   ### | ||||||
|  |   count: ( obj ) -> | ||||||
|  |     return 0 if !obj | ||||||
|  |     return obj.history.length if obj.history | ||||||
|  |     return obj.sets.length if obj.sets | ||||||
|  |     obj.length || 0; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   ###* Add work experience to the sheet. ### | ||||||
|  |   add: ( moniker ) -> | ||||||
|  |     defSheet = FreshResume.default() | ||||||
|  |     newObject = | ||||||
|  |       if defSheet[moniker].history | ||||||
|  |       then $.extend( true, {}, defSheet[ moniker ].history[0] ) | ||||||
|  |       else | ||||||
|  |         if moniker == 'skills' | ||||||
|  |         then $.extend( true, {}, defSheet.skills.sets[0] ) | ||||||
|  |         else $.extend( true, {}, defSheet[ moniker ][0] ) | ||||||
|  |  | ||||||
|  |     @[ moniker ] = @[ moniker ] || [] | ||||||
|  |     if @[ moniker ].history | ||||||
|  |       @[ moniker ].history.push newObject | ||||||
|  |     else if moniker == 'skills' | ||||||
|  |       @skills.sets.push newObject | ||||||
|  |     else | ||||||
|  |       @[ moniker ].push newObject | ||||||
|  |     newObject | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   ###* | ||||||
|  |   Determine if the sheet includes a specific social profile (eg, GitHub). | ||||||
|  |   ### | ||||||
|  |   hasProfile: ( socialNetwork ) -> | ||||||
|  |     socialNetwork = socialNetwork.trim().toLowerCase() | ||||||
|  |     @social && _.some @social, (p) -> | ||||||
|  |       p.network.trim().toLowerCase() == socialNetwork | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   ###* Return the specified network profile. ### | ||||||
|  |   getProfile: ( socialNetwork ) -> | ||||||
|  |     socialNetwork = socialNetwork.trim().toLowerCase() | ||||||
|  |     @social && _.find @social, (sn) -> | ||||||
|  |       sn.network.trim().toLowerCase() == socialNetwork | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   ###* | ||||||
|  |   Return an array of profiles for the specified network, for when the user | ||||||
|  |   has multiple eg. GitHub accounts. | ||||||
|  |   ### | ||||||
|  |   getProfiles: ( socialNetwork ) -> | ||||||
|  |     socialNetwork = socialNetwork.trim().toLowerCase() | ||||||
|  |     @social && _.filter @social, (sn) -> | ||||||
|  |       sn.network.trim().toLowerCase() == socialNetwork | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   ###* Determine if the sheet includes a specific skill. ### | ||||||
|  |   hasSkill: ( skill ) -> | ||||||
|  |     skill = skill.trim().toLowerCase() | ||||||
|  |     @skills && _.some @skills, (sk) -> | ||||||
|  |       sk.keywords && _.some sk.keywords, (kw) -> | ||||||
|  |         kw.trim().toLowerCase() == skill | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   ###* Validate the sheet against the FRESH Resume schema. ### | ||||||
|  |   isValid: ( info ) -> | ||||||
|  |     schemaObj = require 'fresca' | ||||||
|  |     validator = require 'is-my-json-valid' | ||||||
|  |     validate = validator( schemaObj, { # See Note [1]. | ||||||
|  |       formats: { date: /^\d{4}(?:-(?:0[0-9]{1}|1[0-2]{1})(?:-[0-9]{2})?)?$/ } | ||||||
|  |     }) | ||||||
|  |     ret = validate @ | ||||||
|  |     if !ret | ||||||
|  |       this.imp = this.imp || { }; | ||||||
|  |       this.imp.validationErrors = validate.errors; | ||||||
|  |     ret | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   duration: (unit) -> | ||||||
|  |     super('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(). | ||||||
|  |   ### | ||||||
|  |   sort: () -> | ||||||
|  |  | ||||||
|  |     byDateDesc = (a,b) -> | ||||||
|  |       if a.safe.start.isBefore(b.safe.start) | ||||||
|  |       then 1 | ||||||
|  |       else ( if a.safe.start.isAfter(b.safe.start) then -1 else 0 ) | ||||||
|  |  | ||||||
|  |     sortSection = ( key ) -> | ||||||
|  |       ar = __.get this, key | ||||||
|  |       if ar && ar.length | ||||||
|  |         datedThings = obj.filter (o) -> o.start | ||||||
|  |         datedThings.sort( byDateDesc ); | ||||||
|  |  | ||||||
|  |     sortSection 'employment.history' | ||||||
|  |     sortSection 'education.history' | ||||||
|  |     sortSection 'service.history' | ||||||
|  |     sortSection 'projects' | ||||||
|  |  | ||||||
|  |     @writing && @writing.sort (a, b) -> | ||||||
|  |       if a.safe.date.isBefore b.safe.date | ||||||
|  |       then 1 | ||||||
|  |       else ( a.safe.date.isAfter(b.safe.date) && -1 ) || 0 | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ###* | ||||||
|  | Get the default (starter) sheet. | ||||||
|  | ### | ||||||
|  | FreshResume.default = () -> | ||||||
|  |   new FreshResume().parseJSON require('fresh-resume-starter').fresh | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ###* | ||||||
|  | Convert the supplied FreshResume to a JSON string, sanitizing meta-properties | ||||||
|  | along the way. | ||||||
|  | ### | ||||||
|  | FreshResume.stringify = ( obj ) -> | ||||||
|  |   replacer = ( key,value ) -> # Exclude these keys from stringification | ||||||
|  |     exKeys = ['imp', 'warnings', 'computed', 'filt', 'ctrl', 'index', | ||||||
|  |       'safe', 'result', 'isModified', 'htmlPreview', 'display_progress_bar'] | ||||||
|  |     return if _.some( exKeys, (val) -> key.trim() == val ) | ||||||
|  |     then undefined else value | ||||||
|  |   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 = () -> | ||||||
|  |  | ||||||
|  |   _fmt = require('./fluent-date').fmt | ||||||
|  |   that = @ | ||||||
|  |  | ||||||
|  |   # TODO: refactor recursion | ||||||
|  |   replaceDatesInObject = ( obj ) -> | ||||||
|  |  | ||||||
|  |     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 | ||||||
|  |       Object.keys( obj ).forEach (key) -> replaceDatesInObject obj[key] | ||||||
|  |       ['start','end','date'].forEach (val) -> | ||||||
|  |         if (obj[val] != undefined) && (!obj.safe || !obj.safe[val]) | ||||||
|  |           obj.safe = obj.safe || { } | ||||||
|  |           obj.safe[ val ] = _fmt obj[val] | ||||||
|  |           if obj[val] && (val == 'start') && !obj.end | ||||||
|  |             obj.safe.end = _fmt 'current' | ||||||
|  |             return | ||||||
|  |       return | ||||||
|  |   Object.keys( this ).forEach (member) -> | ||||||
|  |     replaceDatesInObject(that[member]) | ||||||
|  |     return | ||||||
|  |   return | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ###* 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}$/ | ||||||
|  | # | ||||||
							
								
								
									
										209
									
								
								src/core/fresh-theme.coffee
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										209
									
								
								src/core/fresh-theme.coffee
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,209 @@ | |||||||
|  | ###* | ||||||
|  | Definition of the FRESHTheme class. | ||||||
|  | @module core/fresh-theme | ||||||
|  | @license MIT. See LICENSE.md for details. | ||||||
|  | ### | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | FS = require 'fs' | ||||||
|  | validator = require 'is-my-json-valid' | ||||||
|  | _ = require 'underscore' | ||||||
|  | PATH = require 'path' | ||||||
|  | parsePath = require 'parse-filepath' | ||||||
|  | pathExists = require('path-exists').sync | ||||||
|  | EXTEND = require 'extend' | ||||||
|  | HMSTATUS = require './status-codes' | ||||||
|  | moment = require 'moment' | ||||||
|  | loadSafeJson = require '../utils/safe-json-loader' | ||||||
|  | READFILES = require 'recursive-readdir-sync' | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ### A representation of a FRESH theme asset. | ||||||
|  | @class FRESHTheme ### | ||||||
|  | class FRESHTheme | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   ### Open and parse the specified theme. ### | ||||||
|  |   open: ( themeFolder ) -> | ||||||
|  |  | ||||||
|  |     @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: | ||||||
|  |           if themeInfo.ex.operation == 'parse' | ||||||
|  |           then HMSTATUS.parseError | ||||||
|  |           else HMSTATUS.readError | ||||||
|  |       inner: themeInfo.ex.inner | ||||||
|  |  | ||||||
|  |     that = this | ||||||
|  |  | ||||||
|  |     # Move properties from the theme JSON file to the theme object | ||||||
|  |     EXTEND true, @, themeInfo.json | ||||||
|  |  | ||||||
|  |     # Check for an "inherits" entry in the theme JSON. | ||||||
|  |     if @inherits | ||||||
|  |       cached = { } | ||||||
|  |       _.each @inherits, (th, key) -> | ||||||
|  |         themesFolder = require.resolve 'fresh-themes' | ||||||
|  |         d = parsePath( themeFolder ).dirname | ||||||
|  |         themePath = PATH.join d, th | ||||||
|  |         cached[ th ] = cached[th] || new FRESHTheme().open( themePath ) | ||||||
|  |         formatsHash[ key ] = cached[ th ].getFormat( key ) | ||||||
|  |  | ||||||
|  |     # Load theme files | ||||||
|  |     formatsHash = _load.call @, formatsHash | ||||||
|  |  | ||||||
|  |     # Cache | ||||||
|  |     @formats = formatsHash | ||||||
|  |  | ||||||
|  |     # Set the official theme name | ||||||
|  |     @name = parsePath( @folder ).name | ||||||
|  |     @ | ||||||
|  |  | ||||||
|  |   ### Determine if the theme supports the specified output format. ### | ||||||
|  |   hasFormat: ( fmt ) -> _.has @formats, fmt | ||||||
|  |  | ||||||
|  |   ### Determine if the theme supports the specified output format. ### | ||||||
|  |   getFormat: ( fmt ) -> @formats[ fmt ] | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ### Load and parse theme source files. ### | ||||||
|  | _load = (formatsHash) -> | ||||||
|  |  | ||||||
|  |   that = @ | ||||||
|  |   major = false | ||||||
|  |   tplFolder = PATH.join @folder, 'src' | ||||||
|  |  | ||||||
|  |   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 (absPath) -> | ||||||
|  |     _loadOne.call @, absPath, formatsHash, tplFolder | ||||||
|  |   , @ | ||||||
|  |  | ||||||
|  |   # Now, get all the CSS files... | ||||||
|  |   @cssFiles = fmts.filter (fmt) -> fmt and (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. | ||||||
|  |   @cssFiles.forEach (cssf) -> | ||||||
|  |     idx = _.findIndex fmts, ( fmt ) -> | ||||||
|  |       fmt && fmt.pre == cssf.pre && fmt.ext == 'html' | ||||||
|  |     cssf.major = false | ||||||
|  |     if idx > -1 | ||||||
|  |       fmts[ idx ].css = cssf.data | ||||||
|  |       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. | ||||||
|  |         that.overrides = { file: cssf.path, data: cssf.data } | ||||||
|  |   formatsHash | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ### Load a single theme file. ### | ||||||
|  | _loadOne = ( absPath, formatsHash, tplFolder ) -> | ||||||
|  |  | ||||||
|  |   pathInfo = parsePath absPath | ||||||
|  |   absPathSafe = absPath.trim().toLowerCase() | ||||||
|  |   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 @explicit | ||||||
|  |  | ||||||
|  |     outFmt = _.find Object.keys( @formats ), ( fmtKey ) -> | ||||||
|  |       fmtVal = @formats[ fmtKey ] | ||||||
|  |       _.some fmtVal.transform, (fpath) -> | ||||||
|  |         absPathB = PATH.join( @folder, fpath ).trim().toLowerCase() | ||||||
|  |         absPathB == absPathSafe | ||||||
|  |       , @ | ||||||
|  |     , @ | ||||||
|  |     act = 'transform' if outFmt | ||||||
|  |  | ||||||
|  |   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() | ||||||
|  |       return if portion[1] == '_' | ||||||
|  |       reg = /^(?:\/|\\)(html|latex|doc|pdf|png|partials)(?:\/|\\)?/ig | ||||||
|  |       res = reg.exec( portion ) | ||||||
|  |       if res | ||||||
|  |         if res[1] != 'partials' | ||||||
|  |           outFmt = res[1] | ||||||
|  |           act = 'transform' if !@explicit | ||||||
|  |         else | ||||||
|  |           @partials = @partials || [] | ||||||
|  |           @partials.push( { name: pathInfo.name, path: absPath } ) | ||||||
|  |           return null | ||||||
|  |  | ||||||
|  |   # Otherwise, the output format is inferred from the filename, as in | ||||||
|  |   # compact-[outputformat].[extension], for ex, compact-pdf.html | ||||||
|  |   if !outFmt | ||||||
|  |     idx = pathInfo.name.lastIndexOf '-' | ||||||
|  |     outFmt = if idx == -1 then pathInfo.name else pathInfo.name.substr idx+1 | ||||||
|  |     act = 'transform' if !@explicit | ||||||
|  |     defFormats = require './default-formats' | ||||||
|  |     isPrimary = _.some defFormats, (form) -> | ||||||
|  |       form.name == outFmt and 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 @formats?[ outFmt ]?.symLinks | ||||||
|  |     formatsHash[ outFmt ].symLinks = @formats[ outFmt ].symLinks | ||||||
|  |  | ||||||
|  |   # Create the file representation object | ||||||
|  |   obj = | ||||||
|  |     action: act | ||||||
|  |     primary: isPrimary | ||||||
|  |     path: absPath | ||||||
|  |     orgPath: PATH.relative tplFolder, absPath | ||||||
|  |     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 ) | ||||||
|  |   obj | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ### Return a more friendly name for certain formats. ### | ||||||
|  | friendlyName = ( val ) -> | ||||||
|  |   val = (val && val.trim().toLowerCase()) || '' | ||||||
|  |   friendly = { yml: 'yaml', md: 'markdown', txt: 'text' } | ||||||
|  |   friendly[val] || val | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | module.exports = FRESHTheme | ||||||
							
								
								
									
										337
									
								
								src/core/jrs-resume.coffee
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										337
									
								
								src/core/jrs-resume.coffee
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,337 @@ | |||||||
|  | ###* | ||||||
|  | Definition of the JRSResume class. | ||||||
|  | @license MIT. See LICENSE.md for details. | ||||||
|  | @module core/jrs-resume | ||||||
|  | ### | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | FS = require('fs') | ||||||
|  | extend = require('extend') | ||||||
|  | validator = require('is-my-json-valid') | ||||||
|  | _ = require('underscore') | ||||||
|  | PATH = require('path') | ||||||
|  | MD = require('marked') | ||||||
|  | CONVERTER = require('fresh-jrs-converter') | ||||||
|  | moment = require('moment') | ||||||
|  | AbstractResume = require('./abstract-resume') | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ###* | ||||||
|  | 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 ) -> | ||||||
|  |     @imp = @imp ? raw: stringData | ||||||
|  |     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. | ||||||
|  |   } | ||||||
|  |   ### | ||||||
|  |   parseJSON: ( rep, opts ) -> | ||||||
|  |     opts = opts || { }; | ||||||
|  |  | ||||||
|  |     # Ignore any element with the 'ignore: true' designator. | ||||||
|  |     that = this | ||||||
|  |     traverse = require 'traverse' | ||||||
|  |     ignoreList = [] | ||||||
|  |     scrubbed = traverse( rep ).map ( x ) -> | ||||||
|  |       if !@isLeaf && @node.ignore | ||||||
|  |         if  @node.ignore == true || this.node.ignore == 'true' | ||||||
|  |           ignoreList.push @node | ||||||
|  |           @remove() | ||||||
|  |  | ||||||
|  |     # Extend resume properties onto ourself. | ||||||
|  |     extend true, this, scrubbed | ||||||
|  |  | ||||||
|  |     # Set up metadata | ||||||
|  |     if !@imp?.processed | ||||||
|  |       # Set up metadata TODO: Clean up metadata on the object model. | ||||||
|  |       opts = opts || { } | ||||||
|  |       if opts.imp == undefined || opts.imp | ||||||
|  |         @imp = @imp || { } | ||||||
|  |         @imp.title = (opts.title || @imp.title) || @basics.name | ||||||
|  |         unless @imp.raw | ||||||
|  |           @imp.raw = JSON.stringify rep | ||||||
|  |       @imp.processed = true | ||||||
|  |     # Parse dates, sort dates, and calculate computed values | ||||||
|  |     (opts.date == undefined || opts.date) && _parseDates.call( this ) | ||||||
|  |     (opts.sort == undefined || opts.sort) && this.sort() | ||||||
|  |     if opts.compute == undefined || opts.compute | ||||||
|  |       @basics.computed = | ||||||
|  |         numYears: this.duration() | ||||||
|  |         keywords: this.keywords() | ||||||
|  |     @ | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   ###* Save the sheet to disk (for environments that have disk access). ### | ||||||
|  |   save: ( filename ) -> | ||||||
|  |     @imp.file = filename || @imp.file | ||||||
|  |     FS.writeFileSync @imp.file, @stringify( this ), 'utf8' | ||||||
|  |     @ | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   ###* Save the sheet to disk in a specific format, either FRESH or JRS. ### | ||||||
|  |   saveAs: ( filename, format ) -> | ||||||
|  |     if format == 'JRS' | ||||||
|  |       @imp.file = filename || @imp.file; | ||||||
|  |       FS.writeFileSync( @imp.file, @stringify(), 'utf8' ); | ||||||
|  |     else | ||||||
|  |       newRep = CONVERTER.toFRESH @ | ||||||
|  |       stringRep = CONVERTER.toSTRING newRep | ||||||
|  |       FS.writeFileSync filename, stringRep, 'utf8' | ||||||
|  |     @ | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   ###* Return the resume format. ### | ||||||
|  |   format: () -> 'JRS' | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   stringify: () -> JRSResume.stringify( @ ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   ###* Return a unique list of all keywords across all skills. ### | ||||||
|  |   keywords: () -> | ||||||
|  |     flatSkills = [] | ||||||
|  |     if @skills && this.skills.length | ||||||
|  |       @skills.forEach ( s ) -> flatSkills = _.union flatSkills, s.keywords | ||||||
|  |     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: () -> | ||||||
|  |     @imp = @imp ? { } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   ###* Reset the sheet to an empty state. ### | ||||||
|  |   clear = ( clearMeta ) -> | ||||||
|  |     clearMeta = ((clearMeta == undefined) && true) || clearMeta; | ||||||
|  |     delete this.imp if clearMeta | ||||||
|  |     delete this.basics.computed # Don't use Object.keys() here | ||||||
|  |     delete this.work | ||||||
|  |     delete this.volunteer | ||||||
|  |     delete this.education | ||||||
|  |     delete this.awards | ||||||
|  |     delete this.publications | ||||||
|  |     delete this.interests | ||||||
|  |     delete this.skills | ||||||
|  |     delete this.basics.profiles | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   ###* Add work experience to the sheet. ### | ||||||
|  |   add: ( moniker ) -> | ||||||
|  |     defSheet = JRSResume.default() | ||||||
|  |     newObject = $.extend( true, {}, defSheet[ moniker ][0] ) | ||||||
|  |     this[ moniker ] = this[ moniker ] || [] | ||||||
|  |     this[ moniker ].push( newObject ) | ||||||
|  |     newObject | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   ###* Determine if the sheet includes a specific social profile (eg, GitHub). ### | ||||||
|  |   hasProfile: ( socialNetwork ) -> | ||||||
|  |     socialNetwork = socialNetwork.trim().toLowerCase() | ||||||
|  |     return @basics.profiles && _.some @basics.profiles, (p) -> | ||||||
|  |       return p.network.trim().toLowerCase() == socialNetwork | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   ###* Determine if the sheet includes a specific skill. ### | ||||||
|  |   hasSkill: ( skill ) -> | ||||||
|  |     skill = skill.trim().toLowerCase() | ||||||
|  |     return this.skills && _.some this.skills, (sk) -> | ||||||
|  |       return sk.keywords && _.some sk.keywords, (kw) -> | ||||||
|  |         kw.trim().toLowerCase() == skill | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   ###* Validate the sheet against the JSON Resume schema. ### | ||||||
|  |   isValid: ( ) -> # TODO: ↓ fix this path ↓ | ||||||
|  |     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 = @imp | ||||||
|  |     delete @imp | ||||||
|  |     ret = validate @ | ||||||
|  |     @imp = temp | ||||||
|  |     if !ret | ||||||
|  |       @imp = @imp || { }; | ||||||
|  |       @imp.validationErrors = validate.errors; | ||||||
|  |     ret | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   duration: (unit) -> | ||||||
|  |     super('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: ( ) -> | ||||||
|  |  | ||||||
|  |     byDateDesc = (a,b) -> | ||||||
|  |       if a.safeStartDate.isBefore(b.safeStartDate) | ||||||
|  |       then 1 | ||||||
|  |       else ( a.safeStartDate.isAfter(b.safeStartDate) && -1 ) || 0 | ||||||
|  |  | ||||||
|  |     @work && @work.sort byDateDesc | ||||||
|  |     @education && @education.sort byDateDesc | ||||||
|  |     @volunteer && @volunteer.sort byDateDesc | ||||||
|  |  | ||||||
|  |     @awards && @awards.sort (a, b) -> | ||||||
|  |       if a.safeDate.isBefore b.safeDate | ||||||
|  |       then 1 | ||||||
|  |       else (a.safeDate.isAfter(b.safeDate) && -1 ) || 0; | ||||||
|  |  | ||||||
|  |     @publications && @publications.sort (a, b) -> | ||||||
|  |       if ( a.safeReleaseDate.isBefore(b.safeReleaseDate) ) | ||||||
|  |       then 1 | ||||||
|  |       else ( a.safeReleaseDate.isAfter(b.safeReleaseDate) && -1 ) || 0 | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   dupe: () -> | ||||||
|  |     rnew = new JRSResume() | ||||||
|  |     rnew.parse this.stringify(), { } | ||||||
|  |     rnew | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   ###* | ||||||
|  |   Create a copy of this resume in which all fields have been interpreted as | ||||||
|  |   Markdown. | ||||||
|  |   ### | ||||||
|  |   harden: () -> | ||||||
|  |  | ||||||
|  |     that = @ | ||||||
|  |     ret = @dupe() | ||||||
|  |  | ||||||
|  |     HD = (txt) -> '@@@@~' + txt + '~@@@@' | ||||||
|  |  | ||||||
|  |     HDIN = (txt) -> | ||||||
|  |       #return MD(txt || '' ).replace(/^\s*<p>|<\/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 | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ###* Get the default (empty) sheet. ### | ||||||
|  | JRSResume.default = () -> | ||||||
|  |   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 = ( obj ) -> | ||||||
|  |   replacer = ( key,value ) -> # Exclude these keys from stringification | ||||||
|  |     temp = _.some ['imp', 'warnings', 'computed', 'filt', 'ctrl', 'index', | ||||||
|  |       'safeStartDate', 'safeEndDate', 'safeDate', 'safeReleaseDate', 'result', | ||||||
|  |       'isModified', 'htmlPreview', 'display_progress_bar'], | ||||||
|  |       ( val ) -> return key.trim() == val | ||||||
|  |     return if temp then undefined else value | ||||||
|  |   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 = () -> | ||||||
|  |  | ||||||
|  |   _fmt = require('./fluent-date').fmt | ||||||
|  |  | ||||||
|  |   @work && @work.forEach (job) -> | ||||||
|  |     job.safeStartDate = _fmt( job.startDate ) | ||||||
|  |     job.safeEndDate = _fmt( job.endDate ) | ||||||
|  |   @education && @education.forEach (edu) -> | ||||||
|  |     edu.safeStartDate = _fmt( edu.startDate ) | ||||||
|  |     edu.safeEndDate = _fmt( edu.endDate ) | ||||||
|  |   @volunteer && @volunteer.forEach (vol) -> | ||||||
|  |     vol.safeStartDate = _fmt( vol.startDate ) | ||||||
|  |     vol.safeEndDate = _fmt( vol.endDate ) | ||||||
|  |   @awards && @awards.forEach (awd) -> | ||||||
|  |     awd.safeDate = _fmt( awd.date ) | ||||||
|  |   @publications && @publications.forEach (pub) -> | ||||||
|  |     pub.safeReleaseDate = _fmt( pub.releaseDate ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ###* | ||||||
|  | Export the JRSResume function/ctor. | ||||||
|  | ### | ||||||
|  | module.exports = JRSResume | ||||||
							
								
								
									
										88
									
								
								src/core/jrs-theme.coffee
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								src/core/jrs-theme.coffee
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,88 @@ | |||||||
|  | ###* | ||||||
|  | Definition of the JRSTheme class. | ||||||
|  | @module core/jrs-theme | ||||||
|  | @license MIT. See LICENSE.MD for details. | ||||||
|  | ### | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | _ = require 'underscore' | ||||||
|  | PATH = require 'path' | ||||||
|  | parsePath = require 'parse-filepath' | ||||||
|  | pathExists = require('path-exists').sync | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ###* | ||||||
|  | The JRSTheme class is a representation of a JSON Resume theme asset. | ||||||
|  | @class JRSTheme | ||||||
|  | ### | ||||||
|  | class JRSTheme | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   ###* | ||||||
|  |   Open and parse the specified theme. | ||||||
|  |   @method open | ||||||
|  |   ### | ||||||
|  |   open: ( thFolder ) -> | ||||||
|  |  | ||||||
|  |     @folder = thFolder | ||||||
|  |  | ||||||
|  |     # Open the [theme-name].json file; should have the same | ||||||
|  |     # name as folder | ||||||
|  |     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 | ||||||
|  |       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, | ||||||
|  |             primary: true, | ||||||
|  |             ext: 'html', | ||||||
|  |             css: null | ||||||
|  |           }] | ||||||
|  |         pdf: | ||||||
|  |           outFormat: 'pdf' | ||||||
|  |           files: [{ | ||||||
|  |             action: 'transform', | ||||||
|  |             render: this.render, | ||||||
|  |             primary: 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 | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   ###* | ||||||
|  |   Return the requested output format. | ||||||
|  |   @method getFormat | ||||||
|  |   ### | ||||||
|  |   getFormat: ( fmt ) -> @formats[ fmt ] | ||||||
|  |  | ||||||
|  |  | ||||||
|  | module.exports = JRSTheme; | ||||||
							
								
								
									
										109
									
								
								src/core/resume-factory.coffee
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								src/core/resume-factory.coffee
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,109 @@ | |||||||
|  | ###* | ||||||
|  | Definition of the ResumeFactory class. | ||||||
|  | @license MIT. See LICENSE.md for details. | ||||||
|  | @module core/resume-factory | ||||||
|  | ### | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | FS              = require('fs') | ||||||
|  | HACKMYSTATUS    = require('./status-codes') | ||||||
|  | HME             = require('./event-codes') | ||||||
|  | ResumeConverter = require('fresh-jrs-converter') | ||||||
|  | chalk           = require('chalk') | ||||||
|  | SyntaxErrorEx   = require('../utils/syntax-error-ex') | ||||||
|  | _               = require('underscore') | ||||||
|  | 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? | ||||||
|  |         inner: {            // Passthru options for FRESH/JRSResume | ||||||
|  |           sort: false | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |   ### | ||||||
|  |   load: ( sources, opts, emitter ) -> | ||||||
|  |     sources.map( (src) -> | ||||||
|  |       @loadOne( src, opts, emitter ) | ||||||
|  |     , @) | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   ###* Load a single resume from disk.  ### | ||||||
|  |   loadOne: ( src, opts, emitter ) -> | ||||||
|  |  | ||||||
|  |     toFormat = opts.format     # Can be null | ||||||
|  |     objectify = opts.objectify | ||||||
|  |  | ||||||
|  |     # 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 | ||||||
|  |     return info if info.fluenterror | ||||||
|  |  | ||||||
|  |     # Determine the resume format: FRESH or JRS | ||||||
|  |     json = info.json | ||||||
|  |     isFRESH = json.meta && json.meta.format && json.meta.format.startsWith('FRESH@'); | ||||||
|  |     orgFormat = if isFRESH then 'fresh' else 'jrs' | ||||||
|  |  | ||||||
|  |     # Convert between formats if necessary | ||||||
|  |     if toFormat and ( 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 objectify | ||||||
|  |       ResumeClass = require('../core/' + (toFormat || orgFormat) + '-resume'); | ||||||
|  |       rez = new ResumeClass().parseJSON( json, opts.inner ); | ||||||
|  |       rez.i().file = src; | ||||||
|  |  | ||||||
|  |     file: src | ||||||
|  |     json: info.json | ||||||
|  |     rez: rez | ||||||
|  |  | ||||||
|  |  | ||||||
|  | _parse = ( fileName, opts, eve ) -> | ||||||
|  |  | ||||||
|  |   rawData = null | ||||||
|  |   try | ||||||
|  |  | ||||||
|  |     # Read the file | ||||||
|  |     eve && eve.stat( HME.beforeRead, { file: fileName }); | ||||||
|  |     rawData = FS.readFileSync( fileName, 'utf8' ); | ||||||
|  |     eve && eve.stat( HME.afterRead, { file: fileName, data: rawData }); | ||||||
|  |  | ||||||
|  |     # Parse the file | ||||||
|  |     eve && eve.stat HME.beforeParse, { data: rawData } | ||||||
|  |     ret = { json: JSON.parse( rawData ) } | ||||||
|  |     orgFormat = | ||||||
|  |       if ret.json.meta && ret.json.meta.format && ret.json.meta.format.startsWith('FRESH@') | ||||||
|  |       then 'fresh' else 'jrs' | ||||||
|  |  | ||||||
|  |     eve && eve.stat HME.afterParse, { file: fileName, data: ret.json, fmt: orgFormat } | ||||||
|  |     return ret | ||||||
|  |   catch | ||||||
|  |     # Can be ENOENT, EACCES, SyntaxError, etc. | ||||||
|  |     fluenterror: if rawData then HACKMYSTATUS.parseError else HACKMYSTATUS.readError | ||||||
|  |     inner: _error | ||||||
|  |     raw: rawData | ||||||
|  |     file: fileName | ||||||
							
								
								
									
										380
									
								
								src/core/resume.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										380
									
								
								src/core/resume.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,380 @@ | |||||||
|  | { | ||||||
|  |   "$schema": "http://json-schema.org/draft-04/schema#", | ||||||
|  |   "title": "Resume Schema", | ||||||
|  |   "type": "object", | ||||||
|  |   "additionalProperties": false, | ||||||
|  |   "properties": { | ||||||
|  |     "basics": { | ||||||
|  |       "type": "object", | ||||||
|  |       "additionalProperties": true, | ||||||
|  |       "properties": { | ||||||
|  |         "name": { | ||||||
|  |           "type": "string" | ||||||
|  |         }, | ||||||
|  |         "label": { | ||||||
|  |           "type": "string", | ||||||
|  |           "description": "e.g. Web Developer" | ||||||
|  |         }, | ||||||
|  |         "picture": { | ||||||
|  |           "type": "string", | ||||||
|  |           "description": "URL (as per RFC 3986) to a picture in JPEG or PNG format" | ||||||
|  |         }, | ||||||
|  |         "email": { | ||||||
|  |           "type": "string", | ||||||
|  |           "description": "e.g. thomas@gmail.com", | ||||||
|  |           "format": "email" | ||||||
|  |         }, | ||||||
|  |         "phone": { | ||||||
|  |           "type": "string", | ||||||
|  |           "description": "Phone numbers are stored as strings so use any format you like, e.g. 712-117-2923" | ||||||
|  |         }, | ||||||
|  |         "website": { | ||||||
|  |           "type": "string", | ||||||
|  |           "description": "URL (as per RFC 3986) to your website, e.g. personal homepage", | ||||||
|  |           "format": "uri" | ||||||
|  |         }, | ||||||
|  |         "summary": { | ||||||
|  |           "type": "string", | ||||||
|  |           "description": "Write a short 2-3 sentence biography about yourself" | ||||||
|  |         }, | ||||||
|  |         "location": { | ||||||
|  |           "type": "object", | ||||||
|  |           "additionalProperties": true, | ||||||
|  |           "properties": { | ||||||
|  |             "address": { | ||||||
|  |               "type": "string", | ||||||
|  |               "description": "To add multiple address lines, use \n. For example, 1234 Glücklichkeit Straße\nHinterhaus 5. Etage li." | ||||||
|  |             }, | ||||||
|  |             "postalCode": { | ||||||
|  |               "type": "string" | ||||||
|  |             }, | ||||||
|  |             "city": { | ||||||
|  |               "type": "string" | ||||||
|  |             }, | ||||||
|  |             "countryCode": { | ||||||
|  |               "type": "string", | ||||||
|  |               "description": "code as per ISO-3166-1 ALPHA-2, e.g. US, AU, IN" | ||||||
|  |             }, | ||||||
|  |             "region": { | ||||||
|  |               "type": "string", | ||||||
|  |               "description": "The general region where you live. Can be a US state, or a province, for instance." | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         }, | ||||||
|  |         "profiles": { | ||||||
|  |           "type": "array", | ||||||
|  |           "description": "Specify any number of social networks that you participate in", | ||||||
|  |           "additionalItems": false, | ||||||
|  |           "items": { | ||||||
|  |             "type": "object", | ||||||
|  |             "additionalProperties": true, | ||||||
|  |             "properties": { | ||||||
|  |               "network": { | ||||||
|  |                 "type": "string", | ||||||
|  |                 "description": "e.g. Facebook or Twitter" | ||||||
|  |               }, | ||||||
|  |               "username": { | ||||||
|  |                 "type": "string", | ||||||
|  |                 "description": "e.g. neutralthoughts" | ||||||
|  |               }, | ||||||
|  |               "url": { | ||||||
|  |                 "type": "string", | ||||||
|  |                 "description": "e.g. http://twitter.com/neutralthoughts" | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "work": { | ||||||
|  |       "type": "array", | ||||||
|  |       "additionalItems": false, | ||||||
|  |       "items": { | ||||||
|  |         "type": "object", | ||||||
|  |         "additionalProperties": true, | ||||||
|  |       "properties": { | ||||||
|  |           "company": { | ||||||
|  |             "type": "string", | ||||||
|  |             "description": "e.g. Facebook" | ||||||
|  |           }, | ||||||
|  |           "position": { | ||||||
|  |             "type": "string", | ||||||
|  |             "description": "e.g. Software Engineer" | ||||||
|  |           }, | ||||||
|  |           "website": { | ||||||
|  |             "type": "string", | ||||||
|  |             "description": "e.g. http://facebook.com", | ||||||
|  |             "format": "uri" | ||||||
|  |           }, | ||||||
|  |           "startDate": { | ||||||
|  |             "type": "string", | ||||||
|  |             "description": "resume.json uses the ISO 8601 date standard e.g. 2014-06-29", | ||||||
|  |             "format": "date" | ||||||
|  |           }, | ||||||
|  |           "endDate": { | ||||||
|  |             "type": "string", | ||||||
|  |             "description": "e.g. 2012-06-29", | ||||||
|  |             "format": "date" | ||||||
|  |           }, | ||||||
|  |           "summary": { | ||||||
|  |             "type": "string", | ||||||
|  |             "description": "Give an overview of your responsibilities at the company" | ||||||
|  |           }, | ||||||
|  |           "highlights": { | ||||||
|  |             "type": "array", | ||||||
|  |             "description": "Specify multiple accomplishments", | ||||||
|  |             "additionalItems": false, | ||||||
|  |             "items": { | ||||||
|  |               "type": "string", | ||||||
|  |               "description": "e.g. Increased profits by 20% from 2011-2012 through viral advertising" | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "volunteer": { | ||||||
|  |       "type": "array", | ||||||
|  |       "additionalItems": false, | ||||||
|  |       "items": { | ||||||
|  |         "type": "object", | ||||||
|  |         "additionalProperties": true, | ||||||
|  |       "properties": { | ||||||
|  |           "organization": { | ||||||
|  |             "type": "string", | ||||||
|  |             "description": "e.g. Facebook" | ||||||
|  |           }, | ||||||
|  |           "position": { | ||||||
|  |             "type": "string", | ||||||
|  |             "description": "e.g. Software Engineer" | ||||||
|  |           }, | ||||||
|  |           "website": { | ||||||
|  |             "type": "string", | ||||||
|  |             "description": "e.g. http://facebook.com", | ||||||
|  |             "format": "uri" | ||||||
|  |           }, | ||||||
|  |           "startDate": { | ||||||
|  |             "type": "string", | ||||||
|  |             "description": "resume.json uses the ISO 8601 date standard e.g. 2014-06-29", | ||||||
|  |             "format": "date" | ||||||
|  |           }, | ||||||
|  |           "endDate": { | ||||||
|  |             "type": "string", | ||||||
|  |             "description": "e.g. 2012-06-29", | ||||||
|  |             "format": "date" | ||||||
|  |           }, | ||||||
|  |           "summary": { | ||||||
|  |             "type": "string", | ||||||
|  |             "description": "Give an overview of your responsibilities at the company" | ||||||
|  |           }, | ||||||
|  |           "highlights": { | ||||||
|  |             "type": "array", | ||||||
|  |             "description": "Specify multiple accomplishments", | ||||||
|  |             "additionalItems": false, | ||||||
|  |             "items": { | ||||||
|  |               "type": "string", | ||||||
|  |               "description": "e.g. Increased profits by 20% from 2011-2012 through viral advertising" | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "education": { | ||||||
|  |       "type": "array", | ||||||
|  |       "additionalItems": false, | ||||||
|  |       "items": { | ||||||
|  |         "type": "object", | ||||||
|  |         "additionalProperties": true, | ||||||
|  |       "properties": { | ||||||
|  |             "institution": { | ||||||
|  |               "type": "string", | ||||||
|  |               "description": "e.g. Massachusetts Institute of Technology" | ||||||
|  |             }, | ||||||
|  |             "area": { | ||||||
|  |               "type": "string", | ||||||
|  |               "description": "e.g. Arts" | ||||||
|  |             }, | ||||||
|  |             "studyType": { | ||||||
|  |               "type": "string", | ||||||
|  |               "description": "e.g. Bachelor" | ||||||
|  |             }, | ||||||
|  |             "startDate": { | ||||||
|  |               "type": "string", | ||||||
|  |               "description": "e.g. 2014-06-29", | ||||||
|  |               "format": "date" | ||||||
|  |             }, | ||||||
|  |             "endDate": { | ||||||
|  |               "type": "string", | ||||||
|  |               "description": "e.g. 2012-06-29", | ||||||
|  |               "format": "date" | ||||||
|  |             }, | ||||||
|  |             "gpa": { | ||||||
|  |               "type": "string", | ||||||
|  |               "description": "grade point average, e.g. 3.67/4.0" | ||||||
|  |             }, | ||||||
|  |             "courses": { | ||||||
|  |               "type": "array", | ||||||
|  |               "description": "List notable courses/subjects", | ||||||
|  |               "additionalItems": false, | ||||||
|  |               "items": { | ||||||
|  |                 "type": "string", | ||||||
|  |                 "description": "e.g. H1302 - Introduction to American history" | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "awards": { | ||||||
|  |       "type": "array", | ||||||
|  |       "description": "Specify any awards you have received throughout your professional career", | ||||||
|  |       "additionalItems": false, | ||||||
|  |       "items": { | ||||||
|  |           "type": "object", | ||||||
|  |           "additionalProperties": true, | ||||||
|  |         "properties": { | ||||||
|  |             "title": { | ||||||
|  |               "type": "string", | ||||||
|  |               "description": "e.g. One of the 100 greatest minds of the century" | ||||||
|  |             }, | ||||||
|  |             "date": { | ||||||
|  |               "type": "string", | ||||||
|  |               "description": "e.g. 1989-06-12", | ||||||
|  |               "format": "date" | ||||||
|  |             }, | ||||||
|  |             "awarder": { | ||||||
|  |               "type": "string", | ||||||
|  |               "description": "e.g. Time Magazine" | ||||||
|  |             }, | ||||||
|  |             "summary": { | ||||||
|  |               "type": "string", | ||||||
|  |               "description": "e.g. Received for my work with Quantum Physics" | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "publications": { | ||||||
|  |       "type": "array", | ||||||
|  |       "description": "Specify your publications through your career", | ||||||
|  |       "additionalItems": false, | ||||||
|  |       "items": { | ||||||
|  |           "type": "object", | ||||||
|  |           "additionalProperties": true, | ||||||
|  |         "properties": { | ||||||
|  |             "name": { | ||||||
|  |               "type": "string", | ||||||
|  |               "description": "e.g. The World Wide Web" | ||||||
|  |             }, | ||||||
|  |             "publisher": { | ||||||
|  |               "type": "string", | ||||||
|  |               "description": "e.g. IEEE, Computer Magazine" | ||||||
|  |             }, | ||||||
|  |             "releaseDate": { | ||||||
|  |               "type": "string", | ||||||
|  |               "description": "e.g. 1990-08-01" | ||||||
|  |             }, | ||||||
|  |             "website": { | ||||||
|  |               "type": "string", | ||||||
|  |               "description": "e.g. http://www.computer.org/csdl/mags/co/1996/10/rx069-abs.html" | ||||||
|  |             }, | ||||||
|  |             "summary": { | ||||||
|  |               "type": "string", | ||||||
|  |               "description": "Short summary of publication. e.g. Discussion of the World Wide Web, HTTP, HTML." | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "skills": { | ||||||
|  |       "type": "array", | ||||||
|  |       "description": "List out your professional skill-set", | ||||||
|  |       "additionalItems": false, | ||||||
|  |       "items": { | ||||||
|  |           "type": "object", | ||||||
|  |           "additionalProperties": true, | ||||||
|  |         "properties": { | ||||||
|  |             "name": { | ||||||
|  |               "type": "string", | ||||||
|  |               "description": "e.g. Web Development" | ||||||
|  |             }, | ||||||
|  |             "level": { | ||||||
|  |               "type": "string", | ||||||
|  |               "description": "e.g. Master" | ||||||
|  |             }, | ||||||
|  |             "keywords": { | ||||||
|  |               "type": "array", | ||||||
|  |               "description": "List some keywords pertaining to this skill", | ||||||
|  |               "additionalItems": false, | ||||||
|  |               "items": { | ||||||
|  |                 "type": "string", | ||||||
|  |                 "description": "e.g. HTML" | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "languages": { | ||||||
|  |       "type": "array", | ||||||
|  |       "description": "List any other languages you speak", | ||||||
|  |       "additionalItems": false, | ||||||
|  |       "items": { | ||||||
|  |         "type": "object", | ||||||
|  |         "additionalProperties": true, | ||||||
|  |         "properties": { | ||||||
|  |           "language": { | ||||||
|  |             "type": "string", | ||||||
|  |             "description": "e.g. English, Spanish" | ||||||
|  |           }, | ||||||
|  |           "fluency": { | ||||||
|  |             "type": "string", | ||||||
|  |             "description": "e.g. Fluent, Beginner" | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "interests": { | ||||||
|  |       "type": "array", | ||||||
|  |       "additionalItems": false, | ||||||
|  |       "items": { | ||||||
|  |         "type": "object", | ||||||
|  |         "additionalProperties": true, | ||||||
|  |       "properties": { | ||||||
|  |           "name": { | ||||||
|  |             "type": "string", | ||||||
|  |             "description": "e.g. Philosophy" | ||||||
|  |           }, | ||||||
|  |           "keywords": { | ||||||
|  |             "type": "array", | ||||||
|  |             "additionalItems": false, | ||||||
|  |             "items": { | ||||||
|  |               "type": "string", | ||||||
|  |               "description": "e.g. Friedrich Nietzsche" | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "references": { | ||||||
|  |       "type": "array", | ||||||
|  |       "description": "List references you have received", | ||||||
|  |       "additionalItems": false, | ||||||
|  |       "items": { | ||||||
|  |         "type": "object", | ||||||
|  |         "additionalProperties": true, | ||||||
|  |       "properties": { | ||||||
|  |           "name": { | ||||||
|  |             "type": "string", | ||||||
|  |             "description": "e.g. Timothy Cook" | ||||||
|  |           }, | ||||||
|  |           "reference": { | ||||||
|  |             "type": "string", | ||||||
|  |             "description": "e.g. Joe blogs was a great employee, who turned up to work at least once a week. He exceeded my expectations when it came to doing nothing." | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										35
									
								
								src/core/status-codes.coffee
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/core/status-codes.coffee
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | |||||||
|  | ###* | ||||||
|  | Status codes for HackMyResume. | ||||||
|  | @module core/status-codes | ||||||
|  | @license MIT. See LICENSE.MD for details. | ||||||
|  | ### | ||||||
|  |  | ||||||
|  |  | ||||||
|  | module.exports = | ||||||
|  |   success: 0 | ||||||
|  |   themeNotFound: 1 | ||||||
|  |   copyCss: 2 | ||||||
|  |   resumeNotFound: 3 | ||||||
|  |   missingCommand: 4 | ||||||
|  |   invalidCommand: 5 | ||||||
|  |   resumeNotFoundAlt: 6 | ||||||
|  |   inputOutputParity: 7 | ||||||
|  |   createNameMissing: 8 | ||||||
|  |   pdfGeneration: 9 | ||||||
|  |   missingPackageJSON: 10 | ||||||
|  |   invalid: 11 | ||||||
|  |   invalidFormat: 12 | ||||||
|  |   notOnPath: 13 | ||||||
|  |   readError: 14 | ||||||
|  |   parseError: 15 | ||||||
|  |   fileSaveError: 16 | ||||||
|  |   generateError: 17 | ||||||
|  |   invalidHelperUse: 18 | ||||||
|  |   mixedMerge: 19 | ||||||
|  |   invokeTemplate: 20 | ||||||
|  |   compileTemplate: 21 | ||||||
|  |   themeLoad: 22 | ||||||
|  |   invalidParamCount: 23 | ||||||
|  |   missingParam: 24 | ||||||
|  |   createError: 25 | ||||||
|  |   validateError: 26 | ||||||
							
								
								
									
										22
									
								
								src/generators/base-generator.coffee
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								src/generators/base-generator.coffee
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | |||||||
|  | ###* | ||||||
|  | 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. | ||||||
|  | ### | ||||||
|  |  | ||||||
|  | module.exports = class BaseGenerator | ||||||
|  |  | ||||||
|  |   ###* Base-class initialize. ### | ||||||
|  |   constructor: ( @format ) -> | ||||||
|  |  | ||||||
|  |   ###* Status codes. ### | ||||||
|  |   codes: require '../core/status-codes' | ||||||
|  |  | ||||||
|  |   ###* Generator options. ### | ||||||
|  |   opts: { } | ||||||
							
								
								
									
										30
									
								
								src/generators/html-generator.coffee
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/generators/html-generator.coffee
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | |||||||
|  | ###* | ||||||
|  | Definition of the HTMLGenerator class. | ||||||
|  | @module generators/html-generator | ||||||
|  | @license MIT. See LICENSE.md for details. | ||||||
|  | ### | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | TemplateGenerator = require './template-generator' | ||||||
|  | FS = require 'fs-extra' | ||||||
|  | HTML = require 'html' | ||||||
|  | PATH = require 'path' | ||||||
|  | require 'string.prototype.endswith' | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | module.exports = class HtmlGenerator extends TemplateGenerator | ||||||
|  |  | ||||||
|  |   constructor: -> super 'html' | ||||||
|  |  | ||||||
|  |   ###* | ||||||
|  |   Copy satellite CSS files to the destination and optionally pretty-print | ||||||
|  |   the HTML resume prior to saving. | ||||||
|  |   ### | ||||||
|  |   onBeforeSave: ( info ) -> | ||||||
|  |     if info.outputFile.endsWith '.css' | ||||||
|  |       return info.mk | ||||||
|  |     if @opts.prettify | ||||||
|  |     then HTML.prettyPrint info.mk, this.opts.prettify | ||||||
|  |     else info.mk | ||||||
							
								
								
									
										92
									
								
								src/generators/html-pdf-cli-generator.coffee
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								src/generators/html-pdf-cli-generator.coffee
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,92 @@ | |||||||
|  | ###* | ||||||
|  | Definition of the HtmlPdfCLIGenerator class. | ||||||
|  | @module generators/html-pdf-generator.js | ||||||
|  | @license MIT. See LICENSE.md for details. | ||||||
|  | ### | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | TemplateGenerator = require './template-generator' | ||||||
|  | FS = require 'fs-extra' | ||||||
|  | PATH = require 'path' | ||||||
|  | SLASH = require 'slash' | ||||||
|  | _ = require 'underscore' | ||||||
|  | HMSTATUS = require '../core/status-codes' | ||||||
|  | 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 = class HtmlPdfCLIGenerator extends TemplateGenerator | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   constructor: () -> super 'pdf', 'html' | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   ###* Generate the binary PDF. ### | ||||||
|  |   onBeforeSave: ( info ) -> | ||||||
|  |     #console.dir _.omit( info, 'mk' ), depth: null, colors: true | ||||||
|  |     return info.mk if info.ext != 'html' and info.ext != 'pdf' | ||||||
|  |     safe_eng = info.opts.pdf || 'wkhtmltopdf' | ||||||
|  |     safe_eng = 'phantomjs' if safe_eng == 'phantom' | ||||||
|  |     if _.has engines, safe_eng | ||||||
|  |       @errHandler = info.opts.errHandler | ||||||
|  |       engines[ safe_eng ].call @, info.mk, info.outputFile, @onError | ||||||
|  |       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. ### | ||||||
|  |   onError: (ex, param) -> | ||||||
|  |     param.errHandler?.err? HMSTATUS.pdfGeneration, ex | ||||||
|  |     return | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # 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 <source> <target>`. 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: (markup, fOut, on_error) -> | ||||||
|  |     # Save the markup to a temporary file | ||||||
|  |     tempFile = fOut.replace /\.pdf$/i, '.pdf.html' | ||||||
|  |     FS.writeFileSync tempFile, markup, 'utf8' | ||||||
|  |     SPAWN 'wkhtmltopdf', [ tempFile, fOut ], false, on_error, @ | ||||||
|  |     return | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   ###* | ||||||
|  |   Generate a PDF from HTML using Phantom's CLI interface. | ||||||
|  |   Spawns a child process with `phantomjs <script> <source> <target>`. Phantom | ||||||
|  |   must be installed and path-accessible. | ||||||
|  |   TODO: If HTML generation has run, reuse that output | ||||||
|  |   TODO: Local web server to ease Phantom rendering | ||||||
|  |   ### | ||||||
|  |   phantomjs: ( markup, fOut, on_error ) -> | ||||||
|  |     # Save the markup to a temporary file | ||||||
|  |     tempFile = fOut.replace /\.pdf$/i, '.pdf.html' | ||||||
|  |     FS.writeFileSync tempFile, markup, 'utf8' | ||||||
|  |     scriptPath = PATH.relative process.cwd(), PATH.resolve( __dirname, '../utils/rasterize.js' ) | ||||||
|  |     scriptPath = SLASH scriptPath | ||||||
|  |     sourcePath = SLASH PATH.relative( process.cwd(), tempFile) | ||||||
|  |     destPath = SLASH PATH.relative( process.cwd(), fOut) | ||||||
|  |     SPAWN 'phantomjs', [ scriptPath, sourcePath, destPath ], false, on_error, @ | ||||||
|  |     return | ||||||
							
								
								
									
										52
									
								
								src/generators/html-png-generator.coffee
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								src/generators/html-png-generator.coffee
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,52 @@ | |||||||
|  | ###* | ||||||
|  | Definition of the HtmlPngGenerator class. | ||||||
|  | @module generators/html-png-generator | ||||||
|  | @license MIT. See LICENSE.MD for details. | ||||||
|  | ### | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | TemplateGenerator = require './template-generator' | ||||||
|  | FS = require 'fs-extra' | ||||||
|  | HTML = require 'html' | ||||||
|  | SLASH = require 'slash' | ||||||
|  | SPAWN = require '../utils/safe-spawn' | ||||||
|  | PATH = require 'path' | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ###* | ||||||
|  | An HTML-based PNG resume generator for HackMyResume. | ||||||
|  | ### | ||||||
|  | module.exports = class HtmlPngGenerator extends TemplateGenerator | ||||||
|  |  | ||||||
|  |   constructor: -> super 'png', 'html' | ||||||
|  |  | ||||||
|  |   invoke: ( rez, themeMarkup, cssInfo, opts ) -> | ||||||
|  |     # TODO: Not currently called or callable. | ||||||
|  |  | ||||||
|  |   generate: ( rez, f, opts ) -> | ||||||
|  |     htmlResults = opts.targets.filter (t) -> t.fmt.outFormat == 'html' | ||||||
|  |     htmlFile = htmlResults[0].final.files.filter (fl) -> | ||||||
|  |       fl.info.ext == 'html' | ||||||
|  |     phantom htmlFile[0].data, f | ||||||
|  |     return | ||||||
|  |  | ||||||
|  | ###* | ||||||
|  | Generate a PDF from HTML using Phantom's CLI interface. | ||||||
|  | Spawns a child process with `phantomjs <script> <source> <target>`. Phantom | ||||||
|  | must be installed and path-accessible. | ||||||
|  | TODO: If HTML generation has run, reuse that output | ||||||
|  | TODO: Local web server to ease Phantom rendering | ||||||
|  | ### | ||||||
|  |  | ||||||
|  | phantom = ( markup, fOut ) -> | ||||||
|  |  | ||||||
|  |   # Save the markup to a temporary file | ||||||
|  |   tempFile = fOut.replace(/\.png$/i, '.png.html'); | ||||||
|  |   FS.writeFileSync tempFile, markup, 'utf8' | ||||||
|  |   scriptPath = SLASH( PATH.relative( process.cwd(), | ||||||
|  |     PATH.resolve( __dirname, '../utils/rasterize.js' ) ) ); | ||||||
|  |   sourcePath = SLASH( PATH.relative( process.cwd(), tempFile) ); | ||||||
|  |   destPath = SLASH( PATH.relative( process.cwd(), fOut) ); | ||||||
|  |   info = SPAWN('phantomjs', [ scriptPath, sourcePath, destPath ]); | ||||||
|  |   return | ||||||
							
								
								
									
										25
									
								
								src/generators/json-generator.coffee
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/generators/json-generator.coffee
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | |||||||
|  | ###* | ||||||
|  | Definition of the JsonGenerator class. | ||||||
|  | @module generators/json-generator | ||||||
|  | @license MIT. See LICENSE.md for details. | ||||||
|  | ### | ||||||
|  |  | ||||||
|  | BaseGenerator = require './base-generator' | ||||||
|  | FS = require 'fs' | ||||||
|  | _ = require 'underscore' | ||||||
|  | FJCV = require 'fresh-jrs-converter' | ||||||
|  |  | ||||||
|  | ###* The JsonGenerator generates a FRESH or JRS resume as an output. ### | ||||||
|  |  | ||||||
|  | module.exports = class JsonGenerator extends BaseGenerator | ||||||
|  |  | ||||||
|  |   constructor: () -> super 'json' | ||||||
|  |  | ||||||
|  |   invoke: ( rez ) -> | ||||||
|  |     altRez = FJCV[ 'to' + if rez.format() == 'FRESH' then 'JRS' else 'FRESH' ] rez | ||||||
|  |     altRez = FJCV.toSTRING( altRez ) | ||||||
|  |     #altRez.stringify() | ||||||
|  |  | ||||||
|  |   generate: ( rez, f ) -> | ||||||
|  |     FS.writeFileSync f, @invoke(rez), 'utf8' | ||||||
|  |     return | ||||||
							
								
								
									
										31
									
								
								src/generators/json-yaml-generator.coffee
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								src/generators/json-yaml-generator.coffee
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | |||||||
|  | ###* | ||||||
|  | Definition of the JsonYamlGenerator class. | ||||||
|  | @module generators/json-yaml-generator | ||||||
|  | @license MIT. See LICENSE.md for details. | ||||||
|  | ### | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | BaseGenerator = require('./base-generator') | ||||||
|  | FS = require('fs') | ||||||
|  | YAML = require('yamljs') | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ###* | ||||||
|  | JsonYamlGenerator takes a JSON resume object and translates it directly to | ||||||
|  | JSON without a template, producing an equivalent YAML-formatted resume. See | ||||||
|  | also YamlGenerator (yaml-generator.js). | ||||||
|  | ### | ||||||
|  |  | ||||||
|  | module.exports = class JsonYamlGenerator extends BaseGenerator | ||||||
|  |  | ||||||
|  |   constructor: () -> super 'yml' | ||||||
|  |  | ||||||
|  |   invoke: ( rez, themeMarkup, cssInfo, opts ) -> | ||||||
|  |     YAML.stringify JSON.parse( rez.stringify() ), Infinity, 2 | ||||||
|  |  | ||||||
|  |   generate: ( rez, f, opts ) -> | ||||||
|  |     data = YAML.stringify JSON.parse( rez.stringify() ), Infinity, 2 | ||||||
|  |     FS.writeFileSync f, data, 'utf8' | ||||||
|  |     data | ||||||
							
								
								
									
										14
									
								
								src/generators/latex-generator.coffee
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/generators/latex-generator.coffee
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | |||||||
|  | ###* | ||||||
|  | Definition of the LaTeXGenerator class. | ||||||
|  | @module generators/latex-generator | ||||||
|  | @license MIT. See LICENSE.md for details. | ||||||
|  | ### | ||||||
|  |  | ||||||
|  | TemplateGenerator = require './template-generator' | ||||||
|  |  | ||||||
|  | ###* | ||||||
|  | LaTeXGenerator generates a LaTeX resume via TemplateGenerator. | ||||||
|  | ### | ||||||
|  | module.exports = class LaTeXGenerator extends TemplateGenerator | ||||||
|  |  | ||||||
|  |   constructor: () -> super 'latex', 'tex' | ||||||
							
								
								
									
										14
									
								
								src/generators/markdown-generator.coffee
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/generators/markdown-generator.coffee
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | |||||||
|  | ###* | ||||||
|  | Definition of the MarkdownGenerator class. | ||||||
|  | @module generators/markdown-generator | ||||||
|  | @license MIT. See LICENSE.md for details. | ||||||
|  | ### | ||||||
|  |  | ||||||
|  | TemplateGenerator = require './template-generator' | ||||||
|  |  | ||||||
|  | ###* | ||||||
|  | MarkdownGenerator generates a Markdown-formatted resume via TemplateGenerator. | ||||||
|  | ### | ||||||
|  | module.exports = class MarkdownGenerator extends TemplateGenerator | ||||||
|  |  | ||||||
|  |   constructor: () -> super 'md', 'txt' | ||||||
							
								
								
									
										237
									
								
								src/generators/template-generator.coffee
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										237
									
								
								src/generators/template-generator.coffee
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,237 @@ | |||||||
|  | ###* | ||||||
|  | Definition of the TemplateGenerator class. TODO: Refactor | ||||||
|  | @module generators/template-generator | ||||||
|  | @license MIT. See LICENSE.md for details. | ||||||
|  | ### | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | FS = require 'fs-extra' | ||||||
|  | _ = require 'underscore' | ||||||
|  | MD = require 'marked' | ||||||
|  | XML = require 'xml-escape' | ||||||
|  | PATH = require 'path' | ||||||
|  | parsePath = require 'parse-filepath' | ||||||
|  | MKDIRP = require 'mkdirp' | ||||||
|  | BaseGenerator = require './base-generator' | ||||||
|  | EXTEND = require 'extend' | ||||||
|  | FRESHTheme = require '../core/fresh-theme' | ||||||
|  | JRSTheme = require '../core/jrs-theme' | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ###* | ||||||
|  | TemplateGenerator performs resume generation via local Handlebar or Underscore | ||||||
|  | style template expansion and is appropriate for text-based formats like HTML, | ||||||
|  | plain text, and XML versions of Microsoft Word, Excel, and OpenOffice. | ||||||
|  | @class TemplateGenerator | ||||||
|  | ### | ||||||
|  |  | ||||||
|  | module.exports = class TemplateGenerator extends BaseGenerator | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   ###* Constructor. Set the output format and template format for this | ||||||
|  |   generator. Will usually be called by a derived generator such as | ||||||
|  |   HTMLGenerator or MarkdownGenerator. ### | ||||||
|  |  | ||||||
|  |   constructor: ( outputFormat, templateFormat, cssFile ) -> | ||||||
|  |     super outputFormat | ||||||
|  |     @tplFormat = templateFormat || outputFormat | ||||||
|  |     return | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   ###* Generate a resume using string-based inputs and outputs without touching | ||||||
|  |   the filesystem. | ||||||
|  |   @method invoke | ||||||
|  |   @param rez A FreshResume object. | ||||||
|  |   @param opts Generator options. | ||||||
|  |   @returns {Array} An array of objects representing the generated output | ||||||
|  |   files. ### | ||||||
|  |  | ||||||
|  |   invoke: ( rez, opts ) -> | ||||||
|  |  | ||||||
|  |     opts = | ||||||
|  |       if opts | ||||||
|  |       then (@opts = EXTEND( true, { }, _defaultOpts, opts )) | ||||||
|  |       else @opts | ||||||
|  |  | ||||||
|  |     # Sort such that CSS files are processed before others | ||||||
|  |     curFmt = opts.themeObj.getFormat( this.format ) | ||||||
|  |     curFmt.files = _.sortBy curFmt.files, (fi) -> fi.ext != 'css' | ||||||
|  |  | ||||||
|  |     # Run the transformation! | ||||||
|  |     results = curFmt.files.map ( tplInfo, idx ) -> | ||||||
|  |       if tplInfo.action == 'transform' | ||||||
|  |         trx = @transform rez, tplInfo.data, @format, opts, opts.themeObj, curFmt | ||||||
|  |         if tplInfo.ext == 'css' | ||||||
|  |           curFmt.files[idx].data = trx | ||||||
|  |         else tplInfo.ext == 'html' | ||||||
|  |           #tplInfo.css contains the CSS data loaded by theme | ||||||
|  |           #tplInfo.cssPath contains the absolute path to the source CSS File | ||||||
|  |       else | ||||||
|  |         # Images and non-transformable binary files | ||||||
|  |       opts.onTransform? tplInfo | ||||||
|  |       return info: tplInfo, data: trx | ||||||
|  |     , @ | ||||||
|  |  | ||||||
|  |     files: results | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   ###* Generate a resume using file-based inputs and outputs. Requires access | ||||||
|  |   to the local filesystem. | ||||||
|  |   @method generate | ||||||
|  |   @param rez A FreshResume object. | ||||||
|  |   @param f Full path to the output resume file to generate. | ||||||
|  |   @param opts Generator options. ### | ||||||
|  |  | ||||||
|  |   generate: ( rez, f, opts ) -> | ||||||
|  |  | ||||||
|  |     # Prepare | ||||||
|  |     @opts = EXTEND true, { }, _defaultOpts, opts | ||||||
|  |  | ||||||
|  |     # Call the string-based generation method | ||||||
|  |     genInfo = @invoke rez, null | ||||||
|  |     outFolder = parsePath( f ).dirname | ||||||
|  |     curFmt = opts.themeObj.getFormat @format | ||||||
|  |  | ||||||
|  |     # Process individual files within this format. For example, the HTML | ||||||
|  |     # output format for a theme may have multiple HTML files, CSS files, | ||||||
|  |     # etc. Process them here. | ||||||
|  |     genInfo.files.forEach ( file ) -> | ||||||
|  |  | ||||||
|  |       # console.dir _.omit(file.info,'cssData','data','css' ) | ||||||
|  |  | ||||||
|  |       # Pre-processing | ||||||
|  |       file.info.orgPath = file.info.orgPath || '' | ||||||
|  |       thisFilePath = | ||||||
|  |         if file.info.primary | ||||||
|  |         then f | ||||||
|  |         else PATH.join outFolder, file.info.orgPath | ||||||
|  |  | ||||||
|  |       if file.info.action != 'copy' and @onBeforeSave | ||||||
|  |         file.data = this.onBeforeSave | ||||||
|  |           theme: opts.themeObj | ||||||
|  |           outputFile: thisFilePath | ||||||
|  |           mk: file.data | ||||||
|  |           opts: @opts, | ||||||
|  |           ext: file.info.ext | ||||||
|  |         if !file.data | ||||||
|  |           return | ||||||
|  |  | ||||||
|  |       # Write the file | ||||||
|  |       opts.beforeWrite? thisFilePath | ||||||
|  |       MKDIRP.sync PATH.dirname( thisFilePath ) | ||||||
|  |  | ||||||
|  |       if file.info.action != 'copy' | ||||||
|  |         FS.writeFileSync thisFilePath, file.data, encoding: 'utf8', flags: 'w' | ||||||
|  |       else | ||||||
|  |         FS.copySync file.info.path, thisFilePath | ||||||
|  |       opts.afterWrite? thisFilePath | ||||||
|  |  | ||||||
|  |       # Post-processing | ||||||
|  |       if @onAfterSave | ||||||
|  |         @onAfterSave outputFile: fileName, mk: file.data, opts: this.opts | ||||||
|  |  | ||||||
|  |     , @ | ||||||
|  |  | ||||||
|  |     # Some themes require a symlink structure. If so, create it. | ||||||
|  |     createSymLinks curFmt, outFolder | ||||||
|  |  | ||||||
|  |     genInfo | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   ###* Perform a single resume resume transformation using string-based inputs | ||||||
|  |   and outputs without touching the local file system. | ||||||
|  |   @param json A FRESH or JRS resume object. | ||||||
|  |   @param jst The stringified template data | ||||||
|  |   @param format The format name, such as "html" or "latex" | ||||||
|  |   @param cssInfo Needs to be refactored. | ||||||
|  |   @param opts Options and passthrough data. ### | ||||||
|  |  | ||||||
|  |   transform: ( json, jst, format, opts, theme, curFmt ) -> | ||||||
|  |     if @opts.freezeBreaks | ||||||
|  |       jst = freeze jst | ||||||
|  |     eng = require '../renderers/' + theme.engine  + '-generator' | ||||||
|  |     result = eng.generate json, jst, format, curFmt, opts, theme | ||||||
|  |     if this.opts.freezeBreaks | ||||||
|  |       result = unfreeze result | ||||||
|  |     result | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | createSymLinks = ( curFmt, outFolder ) -> | ||||||
|  |   # Some themes require a symlink structure. If so, create it. | ||||||
|  |   if curFmt.symLinks | ||||||
|  |     Object.keys( curFmt.symLinks ).forEach (loc) -> | ||||||
|  |       absLoc = PATH.join outFolder, loc | ||||||
|  |       absTarg = PATH.join PATH.dirname(absLoc), curFmt.symLinks[loc] | ||||||
|  |       # Set type to 'file', 'dir', or 'junction' (Windows only) | ||||||
|  |       type = if parsePath( absLoc ).extname then 'file' else 'junction' | ||||||
|  |  | ||||||
|  |       try | ||||||
|  |         FS.symlinkSync absTarg, absLoc, type | ||||||
|  |       catch | ||||||
|  |         succeeded = false | ||||||
|  |         if _error.code == 'EEXIST' | ||||||
|  |           FS.unlinkSync absLoc | ||||||
|  |           try | ||||||
|  |             FS.symlinkSync absTarg, absLoc, type | ||||||
|  |             succeeded = true | ||||||
|  |         if !succeeded | ||||||
|  |           throw ex | ||||||
|  |     return | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ###* Freeze newlines for protection against errant JST parsers. ### | ||||||
|  | freeze = ( markup ) -> | ||||||
|  |   markup.replace( _reg.regN, _defaultOpts.nSym ) | ||||||
|  |   markup.replace( _reg.regR, _defaultOpts.rSym ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ###* Unfreeze newlines when the coast is clear. ### | ||||||
|  | unfreeze = ( markup ) -> | ||||||
|  |   markup.replace _reg.regSymR, '\r' | ||||||
|  |   markup.replace _reg.regSymN, '\n' | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ###* Default template generator options. ### | ||||||
|  | _defaultOpts = | ||||||
|  |   engine: 'underscore' | ||||||
|  |   keepBreaks: true | ||||||
|  |   freezeBreaks: false | ||||||
|  |   nSym: '&newl;' # newline entity | ||||||
|  |   rSym: '&retn;' # return entity | ||||||
|  |   template: | ||||||
|  |     interpolate: /\{\{(.+?)\}\}/g | ||||||
|  |     escape: /\{\{\=(.+?)\}\}/g | ||||||
|  |     evaluate: /\{\%(.+?)\%\}/g | ||||||
|  |     comment: /\{\#(.+?)\#\}/g | ||||||
|  |   filters: | ||||||
|  |     out: ( txt ) -> txt | ||||||
|  |     raw: ( txt ) -> txt | ||||||
|  |     xml: ( txt ) -> XML(txt) | ||||||
|  |     md: ( txt ) -> MD( txt || '' ) | ||||||
|  |     mdin: ( txt ) -> MD(txt || '' ).replace(/^\s*<p>|<\/p>\s*$/gi, '') | ||||||
|  |     lower: ( txt ) -> txt.toLowerCase() | ||||||
|  |     link: ( name, url ) -> | ||||||
|  |       return if url then '<a href="' + url + '">' + name + '</a>' else name | ||||||
|  |   prettify: # ← See https://github.com/beautify-web/js-beautify#options | ||||||
|  |     indent_size: 2 | ||||||
|  |     unformatted: ['em','strong','a'] | ||||||
|  |     max_char: 80 # ← See lib/html.js in above-linked repo | ||||||
|  |     #wrap_line_length: 120, <-- Don't use this | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ###* Regexes for linebreak preservation. ### | ||||||
|  | _reg = | ||||||
|  |   regN: new RegExp( '\n', 'g' ) | ||||||
|  |   regR: new RegExp( '\r', 'g' ) | ||||||
|  |   regSymN: new RegExp( _defaultOpts.nSym, 'g' ) | ||||||
|  |   regSymR: new RegExp( _defaultOpts.rSym, 'g' ) | ||||||
							
								
								
									
										14
									
								
								src/generators/text-generator.coffee
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/generators/text-generator.coffee
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | |||||||
|  | ###* | ||||||
|  | Definition of the TextGenerator class. | ||||||
|  | @module generators/text-generator | ||||||
|  | @license MIT. See LICENSE.md for details. | ||||||
|  | ### | ||||||
|  |  | ||||||
|  | TemplateGenerator = require './template-generator' | ||||||
|  |  | ||||||
|  | ###* | ||||||
|  | The TextGenerator generates a plain-text resume via the TemplateGenerator. | ||||||
|  | ### | ||||||
|  | module.exports = class TextGenerator extends TemplateGenerator | ||||||
|  |  | ||||||
|  |   constructor: () -> super 'txt' | ||||||
							
								
								
									
										12
									
								
								src/generators/word-generator.coffee
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/generators/word-generator.coffee
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | |||||||
|  | ### | ||||||
|  | Definition of the WordGenerator class. | ||||||
|  | @module generators/word-generator | ||||||
|  | @license MIT. See LICENSE.md for details. | ||||||
|  | ### | ||||||
|  |  | ||||||
|  |  | ||||||
|  | TemplateGenerator = require './template-generator' | ||||||
|  |  | ||||||
|  | module.exports = class WordGenerator extends TemplateGenerator | ||||||
|  |  | ||||||
|  |   constructor: () -> super 'doc', 'xml' | ||||||
							
								
								
									
										12
									
								
								src/generators/xml-generator.coffee
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/generators/xml-generator.coffee
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | |||||||
|  | ###* | ||||||
|  | Definition of the XMLGenerator class. | ||||||
|  | @license MIT. See LICENSE.md for details. | ||||||
|  | @module generatprs/xml-generator | ||||||
|  | ### | ||||||
|  |  | ||||||
|  | BaseGenerator = require './base-generator' | ||||||
|  |  | ||||||
|  | ###* The XmlGenerator generates an XML resume via the TemplateGenerator. ### | ||||||
|  | module.exports = class XMLGenerator extends BaseGenerator | ||||||
|  |  | ||||||
|  |   constructor: () -> super 'xml' | ||||||
							
								
								
									
										16
									
								
								src/generators/yaml-generator.coffee
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/generators/yaml-generator.coffee
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | |||||||
|  | ###* | ||||||
|  | Definition of the YAMLGenerator class. | ||||||
|  | @module yaml-generator.js | ||||||
|  | @license MIT. See LICENSE.md for details. | ||||||
|  | ### | ||||||
|  |  | ||||||
|  |  | ||||||
|  | TemplateGenerator = require './template-generator' | ||||||
|  |  | ||||||
|  | ###* | ||||||
|  | YamlGenerator generates a YAML-formatted resume via TemplateGenerator. | ||||||
|  | ### | ||||||
|  |  | ||||||
|  | module.exports = class YAMLGenerator extends TemplateGenerator | ||||||
|  |  | ||||||
|  |   constructor: () -> super 'yml', 'yml' | ||||||
							
								
								
									
										58
									
								
								src/helpers/block-helpers.coffee
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								src/helpers/block-helpers.coffee
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,58 @@ | |||||||
|  | ###* | ||||||
|  | Block helper definitions for HackMyResume / FluentCV. | ||||||
|  | @license MIT. See LICENSE.md for details. | ||||||
|  | @module helpers/generic-helpers | ||||||
|  | ### | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | HMSTATUS = require '../core/status-codes' | ||||||
|  | LO = require 'lodash' | ||||||
|  | _ = require 'underscore' | ||||||
|  | unused = require '../utils/string' | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ###* Block helper function definitions. ### | ||||||
|  | BlockHelpers = module.exports = | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   ###* | ||||||
|  |   Emit the enclosed content if the resume has a section with | ||||||
|  |   the specified name. Otherwise, emit an empty string ''. | ||||||
|  |   ### | ||||||
|  |  | ||||||
|  |   section: ( title, options ) -> | ||||||
|  |     title = title.trim().toLowerCase() | ||||||
|  |     obj = LO.get this.r, title | ||||||
|  |     ret = '' | ||||||
|  |     if obj | ||||||
|  |       if _.isArray obj | ||||||
|  |         if obj.length | ||||||
|  |           ret = options.fn @ | ||||||
|  |       else if _.isObject obj | ||||||
|  |         if (obj.history && obj.history.length) || (obj.sets && obj.sets.length) | ||||||
|  |             ret = options.fn @ | ||||||
|  |     ret | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   ###* | ||||||
|  |   Emit the enclosed content if the resume has the named | ||||||
|  |   property or subproperty. | ||||||
|  |   ### | ||||||
|  |  | ||||||
|  |   has: ( title, options ) -> | ||||||
|  |     title = title && title.trim().toLowerCase() | ||||||
|  |     if LO.get this.r, title | ||||||
|  |       return options.fn this | ||||||
|  |     return | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   ###* | ||||||
|  |   Return true if either value is truthy. | ||||||
|  |   @method either | ||||||
|  |   ### | ||||||
|  |   either: ( lhs, rhs, options ) -> options.fn @ if lhs || rhs     | ||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user