diff --git a/package.json b/package.json index 0ff221d..d89ccf5 100644 --- a/package.json +++ b/package.json @@ -53,33 +53,14 @@ "copy": "^0.1.3", "extend": "^3.0.0", "fresca": "~0.6.0", - "fresh-jrs-converter": "^0.2.0", - "fresh-resume-starter": "^0.2.2", - "fresh-themes": "~0.13.0-beta", - "fs-extra": "^0.24.0", + "hackmyapi": "file:..\\HackMyAPI", "handlebars": "^4.0.5", - "html": "0.0.10", - "is-my-json-valid": "^2.12.2", - "json-lint": "^0.1.0", - "jst": "0.0.13", "lodash": "^3.10.1", - "marked": "^0.3.5", - "mkdirp": "^0.5.1", - "moment": "^2.10.6", - "parse-filepath": "^0.6.3", - "path-exists": "^2.1.0", "printf": "^0.2.3", - "recursive-readdir-sync": "^1.0.6", - "simple-html-tokenizer": "^0.2.0", - "slash": "^1.0.0", "string-padding": "^1.0.2", - "string.prototype.endswith": "^0.2.0", "string.prototype.startswith": "^0.2.0", - "traverse": "^0.6.6", "underscore": "^1.8.3", - "webshot": "^0.16.0", "word-wrap": "^1.1.0", - "xml-escape": "^1.0.0", "yamljs": "^0.2.4" }, "devDependencies": { diff --git a/src/cli/error.js b/src/cli/error.js index 355b179..7b48d49 100644 --- a/src/cli/error.js +++ b/src/cli/error.js @@ -10,18 +10,18 @@ Error-handling routines for HackMyResume. - var HMSTATUS = require('../core/status-codes') + var HMSTATUS = require('hackmyapi/src/core/status-codes') , PKG = require('../../package.json') , FS = require('fs') - , FCMD = require('../hackmyapi') + , FCMD = require('hackmyapi') , PATH = require('path') , WRAP = require('word-wrap') - , M2C = require('../utils/md2chalk.js') + , M2C = require('hackmyapi/src/utils/md2chalk.js') , chalk = require('chalk') , extend = require('extend') , YAML = require('yamljs') , printf = require('printf') - , SyntaxErrorEx = require('../utils/syntax-error-ex'); + , SyntaxErrorEx = require('hackmyapi/src/utils/syntax-error-ex'); require('string.prototype.startswith'); diff --git a/src/cli/main.js b/src/cli/main.js index 07f3525..e853653 100644 --- a/src/cli/main.js +++ b/src/cli/main.js @@ -10,16 +10,16 @@ Definition of the `main` function. - var HMR = require( '../hackmyapi') + var HMR = require( 'hackmyapi') , PKG = require('../../package.json') , FS = require('fs') , EXTEND = require('extend') , chalk = require('chalk') , PATH = require('path') - , HMSTATUS = require('../core/status-codes') - , HME = require('../core/event-codes') - , safeLoadJSON = require('../utils/safe-json-loader') - , StringUtils = require('../utils/string.js') + , HMSTATUS = require('hackmyapi/src/core/status-codes') + , HME = require('hackmyapi/src/core/event-codes') + , safeLoadJSON = require('hackmyapi/src/utils/safe-json-loader') + , StringUtils = require('hackmyapi/src/utils/string.js') , _ = require('underscore') , OUTPUT = require('./out') , PAD = require('string-padding') @@ -143,8 +143,8 @@ Definition of the `main` function. _out.log(chalk.cyan(PAD(' Node.js:',25, null, PAD.RIGHT)) + chalk.cyan.bold( process.version )); _out.log(chalk.cyan(PAD(' HackMyResume:',25, null, PAD.RIGHT)) + chalk.cyan.bold('v' + PKG.version )); _out.log(chalk.cyan(PAD(' FRESCA:',25, null, PAD.RIGHT)) + chalk.cyan.bold( PKG.dependencies.fresca )); - _out.log(chalk.cyan(PAD(' fresh-themes:',25, null, PAD.RIGHT)) + chalk.cyan.bold( PKG.dependencies['fresh-themes'] )); - _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-themes:',25, null, PAD.RIGHT)) + chalk.cyan.bold( PKG.dependencies['fresh-themes'] )); + //_out.log(chalk.cyan(PAD(' fresh-jrs-converter:',25, null, PAD.RIGHT)) + chalk.cyan.bold( PKG.dependencies['fresh-jrs-converter'] )); _out.log(''); } diff --git a/src/cli/out.js b/src/cli/out.js index 6b2207a..30e934a 100644 --- a/src/cli/out.js +++ b/src/cli/out.js @@ -11,10 +11,10 @@ Output routines for HackMyResume. var chalk = require('chalk') - , HME = require('../core/event-codes') + , HME = require('hackmyapi/src/core/event-codes') , _ = require('underscore') - , Class = require('../utils/class.js') - , M2C = require('../utils/md2chalk.js') + , Class = require('hackmyapi/src/utils/class.js') + , M2C = require('hackmyapi/src/utils/md2chalk.js') , PATH = require('path') , LO = require('lodash') , FS = require('fs') diff --git a/src/core/default-formats.js b/src/core/default-formats.js deleted file mode 100644 index 0fefd91..0000000 --- a/src/core/default-formats.js +++ /dev/null @@ -1,21 +0,0 @@ -(function(){ - - /** - 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'))() } - - ]; - -}()); diff --git a/src/core/default-options.js b/src/core/default-options.js deleted file mode 100644 index bc9d6ea..0000000 --- a/src/core/default-options.js +++ /dev/null @@ -1,13 +0,0 @@ -(function(){ - - 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 - } - }; - -}()); diff --git a/src/core/empty-jrs.json b/src/core/empty-jrs.json deleted file mode 100644 index bedfe00..0000000 --- a/src/core/empty-jrs.json +++ /dev/null @@ -1,77 +0,0 @@ -{ - "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": [""] - }] -} diff --git a/src/core/event-codes.js b/src/core/event-codes.js deleted file mode 100644 index ab7703d..0000000 --- a/src/core/event-codes.js +++ /dev/null @@ -1,46 +0,0 @@ -/** -Event code definitions. -@module event-codes.js -@license MIT. See LICENSE.md for details. -*/ - - - -(function(){ - - var val = 0; - - 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 - }; - - - -}()); diff --git a/src/core/fluent-date.js b/src/core/fluent-date.js deleted file mode 100644 index e674f39..0000000 --- a/src/core/fluent-date.js +++ /dev/null @@ -1,95 +0,0 @@ -/** -The HackMyResume date representation. -@license MIT. See LICENSE.md for details. -@module core/fluent-date -*/ - -(function(){ - - var moment = require('moment'); - - /** - 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 - */ - function FluentDate( dt ) { - this.rep = this.fmt( dt ); - } - - FluentDate/*.prototype*/.fmt = function( dt, throws ) { - - throws = (throws === undefined || throws === null) || throws; - - if( (typeof dt === 'string' || 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" - var parts = dt.split(' '); - var month = (months[parts[0]] || abbr[parts[0]]); - var 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) ) { // "", " " - var defTime = { - isNull: true, - isBefore: function( other ) { - return( other && !other.isNull ) ? true : false; - }, - isAfter: function( other ) { - return( other && !other.isNull ) ? false : false; - }, - unix: function() { return 0; }, - format: function() { return ''; }, - diff: function() { return 0; } - }; - return defTime; - } - else { - var 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; - } - }; - - var months = {}, abbr = {}; - moment.months().forEach(function(m,idx){months[m.toLowerCase()]=idx+1;}); - moment.monthsShort().forEach(function(m,idx){abbr[m.toLowerCase()]=idx+1;}); - abbr.sept = 9; - - module.exports = FluentDate; - -}()); diff --git a/src/core/fresh-resume.js b/src/core/fresh-resume.js deleted file mode 100644 index 1a0f6c8..0000000 --- a/src/core/fresh-resume.js +++ /dev/null @@ -1,526 +0,0 @@ -/** -Definition of the FRESHResume class. -@license MIT. See LICENSE.md for details. -@module core/fresh-resume -*/ - - - -(function() { - - - - var 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'); - - - - /** - 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 - */ - function FreshResume() { - - } - - - - /** - Initialize the FreshResume from file. - */ - FreshResume.prototype.open = function( file, opts ) { - var raw = FS.readFileSync( file, 'utf8' ); - var ret = this.parse( raw, opts ); - this.imp.file = file; - return ret; - }; - - - - /** - Initialize the the FreshResume from JSON string data. - */ - FreshResume.prototype.parse = function( stringData, opts ) { - 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 ) { - - // Ignore any element with the 'ignore: true' designator. - var that = this, traverse = require('traverse'), ignoreList = []; - var 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 ); - this.remove(); - } - } - }); - - // Now apply the resume representation onto this object - extend( true, this, scrubbed ); - - // If the resume already has a .imp object, then we are being called from - // the .dupe method, and there's no need to do any post processing - if( !this.imp ) { - // Set up metadata TODO: Clean up metadata on the object model. - opts = opts || { }; - if( opts.imp === undefined || opts.imp ) { - this.imp = this.imp || { }; - this.imp.title = (opts.title || this.imp.title) || this.name; - } - // 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) && (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 ) { - - if( format !== 'JRS' ) { - this.imp.file = filename || this.imp.file; - FS.writeFileSync( this.imp.file, this.stringify(), 'utf8' ); - } - else { - var 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 = extend( true, { }, this ); - var rnew = new FreshResume(); - rnew.parseJSON( jso, { } ); - return rnew; - }; - - - - /** - Convert the supplied FreshResume to a JSON string, sanitizing meta-properties - along the way. - */ - FreshResume.stringify = function( obj ) { - function replacer( key,value ) { // Exclude these keys from stringification - return _.some(['imp', 'warnings', 'computed', 'filt', 'ctrl', 'index', - 'safe', 'result', 'isModified', 'htmlPreview', 'display_progress_bar'], - function( val ) { return key.trim() === val; } - ) ? undefined : value; - } - return JSON.stringify( obj, replacer, 2 ); - }; - - - - /** - 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 = this.dupe(); - var 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() { - - function MDIN( txt ){ - return MD(txt || '' ).replace(/^\s*
|<\/p>\s*$/gi, ''); - } - - function trx(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() { - function trx(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() { - this.imp = (this.imp || { }); - return this.imp; - }; - - - - /** - Return a unique list of all keywords across all skills. - */ - FreshResume.prototype.keywords = function() { - var 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 === undefined) && true) || clearMeta; - clearMeta && (delete this.imp); - 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. - */ - 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; - }; - - - - /** - Get the default (starter) sheet. - */ - FreshResume.default = function() { - return new FreshResume().parseJSON( require('fresh-resume-starter') ); - }; - - - - /** - Add work experience to the sheet. - */ - FreshResume.prototype.add = function( moniker ) { - var defSheet = FreshResume.default(); - var 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 schemaObj = require('fresca'); - var validator = require('is-my-json-valid'); - var validate = validator( schemaObj, { // See Note [1]. - formats: { date: /^\d{4}(?:-(?:0[0-9]{1}|1[0-2]{1})(?:-[0-9]{2})?)?$/ } - }); - var ret = validate( this ); - if( !ret ) { - this.imp = this.imp || { }; - this.imp.validationErrors = validate.errors; - } - return ret; - }; - - - - /** - Calculate the total duration of the sheet. Assumes this.work has been sorted - by start date descending, perhaps via a call to Sheet.sort(). - @returns The total duration of the sheet's work history, that is, the number - of years between the start date of the earliest job on the resume and the - *latest end date of all jobs in the work history*. This last condition is for - sheets that have overlapping jobs. - */ - FreshResume.prototype.duration = function(unit) { - unit = unit || 'years'; - var empHist = __.get(this, 'employment.history'); - if( empHist && empHist.length ) { - var firstJob = _.last( empHist ); - var careerStart = firstJob.start ? firstJob.safe.start : ''; - if ((typeof careerStart === 'string' || careerStart instanceof String) && - !careerStart.trim()) - return 0; - var careerLast = _.max( empHist, function( w ) { - return( w.safe && w.safe.end ) ? w.safe.end.unix() : moment().unix(); - }); - return careerLast.safe.end.diff( careerStart, unit ); - } - return 0; - }; - - - - /** - 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( ) { - - function byDateDesc(a,b) { - return( a.safe.start.isBefore(b.safe.start) ) ? 1 - : ( a.safe.start.isAfter(b.safe.start) && -1 ) || 0; - } - - function sortSection( key ) { - var ar = __.get(this, key); - if( ar && ar.length ) { - var datedThings = obj.filter( function(o) { return o.start; } ); - datedThings.sort( byDateDesc ); - } - } - - sortSection( 'employment.history' ); - sortSection( 'education.history' ); - sortSection( 'service.history' ); - sortSection( 'projects' ); - - // this.awards && this.awards.sort( function(a, b) { - // return( a.safeDate.isBefore(b.safeDate) ) ? 1 - // : ( a.safeDate.isAfter(b.safeDate) && -1 ) || 0; - // }); - this.writing && this.writing.sort( function(a, b) { - return( a.safe.date.isBefore(b.safe.date) ) ? 1 - : ( a.safe.date.isAfter(b.safe.date) && -1 ) || 0; - }); - }; - - - - /** - 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. - */ - function _parseDates() { - - var _fmt = require('./fluent-date').fmt; - var that = this; - - // TODO: refactor recursion - function replaceDatesInObject( obj ) { - - if( !obj ) return; - if( Object.prototype.toString.call( obj ) === '[object Array]' ) { - obj.forEach(function(elem){ - replaceDatesInObject( elem ); - }); - } - else if (typeof obj === 'object') { - if( obj._isAMomentObject || obj.safe ) - return; - Object.keys( obj ).forEach(function(key) { - replaceDatesInObject( obj[key] ); - }); - ['start','end','date'].forEach( function(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'); - } - } - }); - } - } - - Object.keys( this ).forEach(function(member){ - replaceDatesInObject( that[ member ] ); - }); - - } - - - - /** - 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}$/ -// diff --git a/src/core/fresh-theme.js b/src/core/fresh-theme.js deleted file mode 100644 index 994cae2..0000000 --- a/src/core/fresh-theme.js +++ /dev/null @@ -1,338 +0,0 @@ -/** -Definition of the FRESHTheme class. -@module fresh-theme.js -@license MIT. See LICENSE.md for details. -*/ - -(function() { - - - - var 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'); - - - - /** - The FRESHTheme class is a representation of a FRESH theme - asset. See also: JRSTheme. - @class FRESHTheme - */ - function FRESHTheme() { - - } - - - - /** - Open and parse the specified theme. - */ - FRESHTheme.prototype.open = function( themeFolder ) { - - this.folder = themeFolder; - - // Open the [theme-name].json file; should have the same name as folder - var pathInfo = parsePath( themeFolder ); - - // Set up a formats hash for the theme - var formatsHash = { }; - - // Load the theme - var themeFile = PATH.join( themeFolder, 'theme.json' ); - var themeInfo = loadSafeJson( themeFile ); - if( themeInfo.ex ) throw { - fluenterror: themeInfo.ex.operation === 'parse' ? - HMSTATUS.parseError : HMSTATUS.readError, - inner: themeInfo.ex.inner - }; - - var that = this; - - // Move properties from the theme JSON file to the theme object - EXTEND( true, this, themeInfo.json ); - - // Check for an "inherits" entry in the theme JSON. - if( this.inherits ) { - var cached = { }; - _.each( this.inherits, function(th, key) { - var themesFolder = require.resolve('fresh-themes'); - var d = parsePath( themeFolder ).dirname; - var themePath = PATH.join(d, th); - cached[ th ] = cached[th] || new FRESHTheme().open( themePath ); - formatsHash[ key ] = cached[ th ].getFormat( key ); - }); - } - - // Check for an explicit "formats" entry in the theme JSON. If it has one, - // then this theme declares its files explicitly. - if( !!this.formats ) { - formatsHash = loadExplicit.call( this, formatsHash ); - this.explicit = true; - } - else { - formatsHash = loadImplicit.call( this, formatsHash ); - } - - // Cache - this.formats = formatsHash; - - // Set the official theme name - this.name = parsePath( this.folder ).name; - - return this; - }; - - - - /** - Determine if the theme supports the specified output format. - */ - FRESHTheme.prototype.hasFormat = function( fmt ) { - return _.has( this.formats, fmt ); - }; - - - - /** - Determine if the theme supports the specified output format. - */ - FRESHTheme.prototype.getFormat = function( fmt ) { - return this.formats[ fmt ]; - }; - - - /** - Load the theme implicitly, by scanning the theme folder for - files. TODO: Refactor duplicated code with loadExplicit. - */ - function loadImplicit(formatsHash) { - - // Set up a hash of formats supported by this theme. - var that = this; - var major = false; - - // Establish the base theme folder - var tplFolder = PATH.join( this.folder, 'src' ); - - // 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. - var fmts = READFILES(tplFolder).map( function(absPath) { - - // If this file lives in a specific format folder within the theme, - // such as "/latex" or "/html", then that format is the output format - // for all files within the folder. - var pathInfo = parsePath(absPath); - var outFmt = '', isMajor = false; - var portion = pathInfo.dirname.replace(tplFolder,''); - if( portion && portion.trim() ) { - if( portion[1] === '_' ) return; - var reg = /^(?:\/|\\)(html|latex|doc|pdf|png|partials)(?:\/|\\)?/ig; - var res = reg.exec( portion ); - if( res ) { - if( res[1] !== 'partials' ) { - outFmt = res[1]; - } - else { - that.partials = that.partials || []; - that.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 ) { - var idx = pathInfo.name.lastIndexOf('-'); - outFmt = (idx === -1) ? pathInfo.name : pathInfo.name.substr(idx + 1); - isMajor = true; - } - - // We should have a valid output format now. - formatsHash[ outFmt ] = formatsHash[outFmt] || { - outFormat: outFmt, - files: [] - }; - - // Create the file representation object. - var obj = { - action: 'transform', - path: absPath, - major: isMajor, - 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 ); - return obj; - }); - - // Now, get all the CSS files... - (this.cssFiles = fmts.filter(function( fmt ){ - return fmt && (fmt.ext === 'css'); - })) - - // For each CSS file, get its corresponding HTML file. It's possible that - // a theme can have a CSS file but *no* HTML file, as when a theme author - // creates a pure CSS override of an existing theme. - .forEach(function( cssf ) { - - var 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; - 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 }; - } - } - }); - - return formatsHash; - } - - - - /** - Load the theme explicitly, by following the 'formats' hash - in the theme's JSON settings file. - */ - function loadExplicit(formatsHash) { - - // Housekeeping - var tplFolder = PATH.join( this.folder, 'src' ); - var act = null; - var that = this; - - // 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. - var fmts = READFILES( tplFolder ).map( function( absPath ) { - - act = null; - // If this file is mentioned in the theme's JSON file under "transforms" - var pathInfo = parsePath(absPath); - var absPathSafe = absPath.trim().toLowerCase(); - var outFmt = _.find( - Object.keys( that.formats ), - function( fmtKey ) { - var fmtVal = that.formats[ fmtKey ]; - return _.some( fmtVal.transform, function(fpath) { - var absPathB = PATH.join( that.folder, fpath ) - .trim().toLowerCase(); - return absPathB === absPathSafe; - }); - }); - if( outFmt ) { - act = 'transform'; - } - - // If this file lives in a specific format folder within the theme, - // such as "/latex" or "/html", then that format is the output format - // for all files within the folder. - if( !outFmt ) { - var portion = pathInfo.dirname.replace(tplFolder,''); - if( portion && portion.trim() ) { - var reg = /^(?:\/|\\)(html|latex|doc|pdf)(?:\/|\\)?/ig; - var res = reg.exec( portion ); - res && (outFmt = res[1]); - } - } - - // Otherwise, the output format is inferred from the filename, as in - // compact-[outputformat].[extension], for ex, compact-pdf.html. - if( !outFmt ) { - var idx = pathInfo.name.lastIndexOf('-'); - outFmt = (idx === -1) ? pathInfo.name : pathInfo.name.substr(idx + 1); - } - - // We should have a valid output format now. - formatsHash[ outFmt ] = - formatsHash[ outFmt ] || { - outFormat: outFmt, - files: [], - symLinks: that.formats[ outFmt ].symLinks - }; - - // Create the file representation object. - var obj = { - action: act, - orgPath: PATH.relative(that.folder, absPath), - path: 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 ); - return obj; - }); - - // Now, get all the CSS files... - (this.cssFiles = fmts.filter(function( fmt ){ - return fmt.ext === 'css'; - })) - - // For each CSS file, get its corresponding HTML file - .forEach(function( cssf ) { - // For each CSS file, get its corresponding HTML file - var idx = _.findIndex(fmts, function( fmt ) { - return fmt.pre === cssf.pre && fmt.ext === 'html'; - }); - fmts[ idx ].css = cssf.data; - fmts[ idx ].cssPath = cssf.path; - }); - - // Remove CSS files from the formats array - fmts = fmts.filter( function( fmt) { - return fmt.ext !== 'css'; - }); - - return formatsHash; - } - - - - /** - Return a more friendly name for certain formats. - TODO: Refactor - */ - function friendlyName( val ) { - val = val.trim().toLowerCase(); - var friendly = { yml: 'yaml', md: 'markdown', txt: 'text' }; - return friendly[val] || val; - } - - - - module.exports = FRESHTheme; - - - -}()); diff --git a/src/core/jrs-resume.js b/src/core/jrs-resume.js deleted file mode 100644 index 7b63f3f..0000000 --- a/src/core/jrs-resume.js +++ /dev/null @@ -1,454 +0,0 @@ -/** -Definition of the JRSResume class. -@license MIT. See LICENSE.md for details. -@module core/jrs-resume -*/ - - - -(function() { - - - - var 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'); - - - - /** - 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 - */ - function JRSResume() { - - } - - - - /** - Initialize the JSResume from file. - */ - JRSResume.prototype.open = function( file, title ) { - //this.imp = { fileName: file }; <-- schema violation, tuck it into .basics - this.basics = { - imp: { - file: file, - raw: FS.readFileSync( file, 'utf8' ) - } - }; - return this.parse( this.basics.imp.raw, title ); - }; - - - - /** - Initialize the the JSResume from string. - */ - JRSResume.prototype.parse = function( stringData, opts ) { - opts = opts || { }; - var rep = JSON.parse( stringData ); - return this.parseJSON( rep, 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 ) { - opts = opts || { }; - - // Ignore any element with the 'ignore: true' designator. - var that = this, traverse = require('traverse'), ignoreList = []; - var 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 ); - this.remove(); - } - } - }); - - // Extend resume properties onto ourself. - extend( true, this, scrubbed ); - - // Set up metadata - if( opts.imp === undefined || opts.imp ) { - this.basics.imp = this.basics.imp || { }; - this.basics.imp.title = - (opts.title || this.basics.imp.title) || this.basics.name; - this.basics.imp.orgFormat = 'JRS'; - } - // 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) && (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.basics.imp.file = filename || this.basics.imp.file; - FS.writeFileSync(this.basics.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 ) { - - if( format === 'JRS' ) { - this.basics.imp.file = filename || this.basics.imp.file; - FS.writeFileSync( this.basics.imp.file, this.stringify(), 'utf8' ); - } - else { - var newRep = CONVERTER.toFRESH( this ); - var stringRep = CONVERTER.toSTRING( newRep ); - FS.writeFileSync( filename, stringRep, 'utf8' ); - } - return this; - - }; - - - - /** - Return the resume format. - */ - JRSResume.prototype.format = function() { - return 'JRS'; - }; - - - - /** - Convert this object to a JSON string, sanitizing meta-properties along the - way. Don't override .toString(). - */ - JRSResume.stringify = function( obj ) { - function replacer( key,value ) { // Exclude these keys from stringification - return _.some(['imp', 'warnings', 'computed', 'filt', 'ctrl', 'index', - 'safeStartDate', 'safeEndDate', 'safeDate', 'safeReleaseDate', 'result', - 'isModified', 'htmlPreview', 'display_progress_bar'], - function( val ) { return key.trim() === val; } - ) ? undefined : value; - } - return JSON.stringify( obj, replacer, 2 ); - }; - - - - JRSResume.prototype.stringify = function() { - return JRSResume.stringify( this ); - }; - - - - /** - Return a unique list of all keywords across all skills. - */ - JRSResume.prototype.keywords = function() { - var flatSkills = []; - if( this.skills && this.skills.length ) { - this.skills.forEach( function( s ) { - 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() { - this.basics = this.basics || { }; - this.basics.imp = this.basics.imp || { }; - return this.basics.imp; - }; - - - - /** - Reset the sheet to an empty state. - */ - JRSResume.prototype.clear = function( clearMeta ) { - clearMeta = ((clearMeta === undefined) && true) || clearMeta; - clearMeta && (delete this.imp); - 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; - }; - - - - /** - Get the default (empty) sheet. - */ - JRSResume.default = function() { - return new JRSResume().open( - PATH.join( __dirname, 'empty-jrs.json'), 'Empty' - ); - }; - - - - /** - Add work experience to the sheet. - */ - JRSResume.prototype.add = function( moniker ) { - var defSheet = JRSResume.default(); - var 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( ) { // TODO: ↓ fix this path ↓ - var schema = FS.readFileSync( PATH.join( __dirname, 'resume.json' ),'utf8'); - var schemaObj = JSON.parse( schema ); - var validator = require('is-my-json-valid'); - var validate = validator( schemaObj, { // Note [1] - formats: { date: /^\d{4}(?:-(?:0[0-9]{1}|1[0-2]{1})(?:-[0-9]{2})?)?$/ } - }); - var ret = validate( this ); - if( !ret ) { - this.basics.imp = this.basics.imp || { }; - this.basics.imp.validationErrors = validate.errors; - } - return ret; - }; - - - - /** - Calculate the total duration of the sheet. Assumes this.work has been sorted - by start date descending, perhaps via a call to Sheet.sort(). - @returns The total duration of the sheet's work history, that is, the number - of years between the start date of the earliest job on the resume and the - *latest end date of all jobs in the work history*. This last condition is for - sheets that have overlapping jobs. - */ - JRSResume.prototype.duration = function( unit ) { - unit = unit || 'years'; - if( this.work && this.work.length ) { - var careerStart = this.work[ this.work.length - 1].safeStartDate; - if ((typeof careerStart === 'string' || careerStart instanceof String) && - !careerStart.trim()) - return 0; - var careerLast = _.max( this.work, function( w ) { - return w.safeEndDate.unix(); - }).safeEndDate; - return careerLast.diff( careerStart, unit ); - } - return 0; - }; - - - - /** - 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( ) { - - 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) { - return( a.safeDate.isBefore(b.safeDate) ) ? 1 - : ( a.safeDate.isAfter(b.safeDate) && -1 ) || 0; - }); - this.publications && this.publications.sort( function(a, b) { - return( a.safeReleaseDate.isBefore(b.safeReleaseDate) ) ? 1 - : ( a.safeReleaseDate.isAfter(b.safeReleaseDate) && -1 ) || 0; - }); - - function byDateDesc(a,b) { - return( a.safeStartDate.isBefore(b.safeStartDate) ) ? 1 - : ( a.safeStartDate.isAfter(b.safeStartDate) && -1 ) || 0; - } - - }; - - - - JRSResume.prototype.dupe = function() { - var 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 that = this; - var ret = this.dupe(); - - function HD(txt) { - return '@@@@~' + txt + '~@@@@'; - } - - function HDIN(txt){ - //return MD(txt || '' ).replace(/^\s*
|<\/p>\s*$/gi, '');
- return HD(txt);
- }
-
- // TODO: refactor recursion
- function hardenStringsInObject( obj, inline ) {
-
- if( !obj ) return;
- inline = inline === undefined || inline;
-
-
- if( Object.prototype.toString.call( obj ) === '[object Array]' ) {
- obj.forEach(function(elem, idx, ar){
- if( typeof elem === 'string' || elem instanceof String )
- ar[idx] = inline ? HDIN(elem) : HD( elem );
- else
- hardenStringsInObject( elem );
- });
- }
- else if (typeof obj === 'object') {
- Object.keys( obj ).forEach(function(key) {
- var 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] = inline ? HDIN( obj[key] ) : HD( obj[key] );
- }
- else
- hardenStringsInObject( sub );
- });
- }
-
- }
-
- Object.keys( ret ).forEach(function(member){
- hardenStringsInObject( ret[ member ] );
- });
-
- return ret;
- };
-
-
-
- /**
- 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.
- */
- function _parseDates() {
-
- var _fmt = require('./fluent-date').fmt;
-
- this.work && this.work.forEach( function(job) {
- job.safeStartDate = _fmt( job.startDate );
- job.safeEndDate = _fmt( job.endDate );
- });
- this.education && this.education.forEach( function(edu) {
- edu.safeStartDate = _fmt( edu.startDate );
- edu.safeEndDate = _fmt( edu.endDate );
- });
- this.volunteer && this.volunteer.forEach( function(vol) {
- vol.safeStartDate = _fmt( vol.startDate );
- vol.safeEndDate = _fmt( vol.endDate );
- });
- this.awards && this.awards.forEach( function(awd) {
- awd.safeDate = _fmt( awd.date );
- });
- this.publications && this.publications.forEach( function(pub) {
- pub.safeReleaseDate = _fmt( pub.releaseDate );
- });
- }
-
-
-
- /**
- Export the JRSResume function/ctor.
- */
- module.exports = JRSResume;
-
-
-
-}());
diff --git a/src/core/jrs-theme.js b/src/core/jrs-theme.js
deleted file mode 100644
index fd75bc1..0000000
--- a/src/core/jrs-theme.js
+++ /dev/null
@@ -1,108 +0,0 @@
-/**
-Definition of the JRSTheme class.
-@module jrs-theme.js
-@license MIT. See LICENSE.MD for details.
-*/
-
-(function() {
-
-
-
- var _ = 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
- */
- function JRSTheme() {
-
- }
-
-
-
- /**
- Open and parse the specified theme.
- @method open
- */
- JRSTheme.prototype.open = function( thFolder ) {
-
- this.folder = thFolder;
-
- // Open the [theme-name].json file; should have the same
- // name as folder
- var pathInfo = parsePath( thFolder );
-
- // Open and parse the theme's package.json file.
- var pkgJsonPath = PATH.join( thFolder, 'package.json' );
- if( pathExists( pkgJsonPath )) {
-
- var thApi = require( thFolder )
- , thPkg = require( pkgJsonPath );
-
- this.name = thPkg.name;
- this.render = (thApi && thApi.render) || undefined;
- this.engine = 'jrs';
-
- // Create theme formats (HTML and PDF). Just add the bare minimum mix of
- // properties necessary to allow JSON Resume themes to share a rendering
- // path with FRESH themes.
- this.formats = {
- html: { outFormat: 'html', files: [
- {
- action: 'transform',
- render: this.render,
- major: true,
- ext: 'html',
- css: null
- }
- ]},
- pdf: { outFormat: 'pdf', files: [
- {
- action: 'transform',
- render: this.render,
- major: true,
- ext: 'pdf',
- css: null
- }
- ]}
- };
- }
- else {
- throw { fluenterror: HACKMYSTATUS.missingPackageJSON };
- }
-
- 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 ];
- };
-
-
-
- module.exports = JRSTheme;
-
-
-
-}());
diff --git a/src/core/resume-factory.js b/src/core/resume-factory.js
deleted file mode 100644
index 2fbe8a6..0000000
--- a/src/core/resume-factory.js
+++ /dev/null
@@ -1,138 +0,0 @@
-/**
-Definition of the ResumeFactory class.
-@license MIT. See LICENSE.md for details.
-@module resume-factory.js
-*/
-
-
-
-(function(){
-
-
-
- var 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
- */
- var 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 toFormat = opts.format; // Can be null
- var 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
- var info = _parse( src, opts, emitter );
- if( info.fluenterror ) return info;
-
- // Determine the resume format: FRESH or JRS
- var json = info.json;
- var orgFormat = ( json.meta && json.meta.format &&
- json.meta.format.startsWith('FRESH@') ) ?
- 'fresh' : 'jrs';
-
- // Convert between formats if necessary
- if( toFormat && (orgFormat !== toFormat) ) {
- json = ResumeConverter[ 'to' + toFormat.toUpperCase() ]( json );
- }
-
- // Objectify the resume, that is, convert it from JSON to a FRESHResume
- // or JRSResume object.
- var rez;
- if( objectify ) {
- var 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
- };
- }
- };
-
-
-
- function _parse( fileName, opts, eve ) {
-
- var rawData;
- 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 });
- var ret = { json: JSON.parse( rawData ) };
- var 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( e ) {
- // Can be ENOENT, EACCES, SyntaxError, etc.
- var ex = {
- fluenterror: rawData ? HACKMYSTATUS.parseError : HACKMYSTATUS.readError,
- inner: e, raw: rawData, file: fileName, shouldExit: false
- };
- opts.quit && (ex.quit = true);
- eve && eve.err( ex.fluenterror, ex );
- if( opts.throw ) throw ex;
- return ex;
- }
-
- }
-
-
-
-}());
diff --git a/src/core/resume.json b/src/core/resume.json
deleted file mode 100644
index 57bca12..0000000
--- a/src/core/resume.json
+++ /dev/null
@@ -1,380 +0,0 @@
-{
- "$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."
- }
- }
-
- }
- }
- }
-}
diff --git a/src/core/status-codes.js b/src/core/status-codes.js
deleted file mode 100644
index 9dce56a..0000000
--- a/src/core/status-codes.js
+++ /dev/null
@@ -1,37 +0,0 @@
-/**
-Status codes for HackMyResume.
-@module status-codes.js
-@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
- };
-
-}());
diff --git a/src/generators/base-generator.js b/src/generators/base-generator.js
deleted file mode 100644
index b8a9538..0000000
--- a/src/generators/base-generator.js
+++ /dev/null
@@ -1,39 +0,0 @@
-/**
-Definition of the BaseGenerator class.
-@module base-generator.js
-@license MIT. See LICENSE.md for details.
-*/
-
-(function() {
-
- // Use J. Resig's nifty class implementation
- var Class = require( '../utils/class' );
-
- /**
- The BaseGenerator class is the root of the generator hierarchy. Functionality
- common to ALL generators lives here.
- */
-
- var BaseGenerator = module.exports = Class.extend({
-
- /**
- Base-class initialize.
- */
- init: function( outputFormat ) {
- this.format = outputFormat;
- },
-
- /**
- Status codes.
- */
- codes: require('../core/status-codes'),
-
- /**
- Generator options.
- */
- opts: {
-
- }
-
- });
-}());
diff --git a/src/generators/html-generator.js b/src/generators/html-generator.js
deleted file mode 100644
index 34f4fa0..0000000
--- a/src/generators/html-generator.js
+++ /dev/null
@@ -1,34 +0,0 @@
-/**
-Definition of the HTMLGenerator class.
-@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
-@module html-generator.js
-*/
-
-(function() {
-
- var TemplateGenerator = require('./template-generator')
- , FS = require('fs-extra')
- , HTML = require( 'html' )
- , PATH = require('path');
- require('string.prototype.endswith');
-
- var HtmlGenerator = module.exports = TemplateGenerator.extend({
-
- init: function() {
- this._super( 'html' );
- },
-
- /**
- Copy satellite CSS files to the destination and optionally pretty-print
- the HTML resume prior to saving.
- */
- onBeforeSave: function( info ) {
- if( info.outputFile.endsWith('.css') )
- return info.mk;
- return this.opts.prettify ?
- HTML.prettyPrint( info.mk, this.opts.prettify ) : info.mk;
- }
-
- });
-
-}());
diff --git a/src/generators/html-pdf-cli-generator.js b/src/generators/html-pdf-cli-generator.js
deleted file mode 100644
index 35018af..0000000
--- a/src/generators/html-pdf-cli-generator.js
+++ /dev/null
@@ -1,113 +0,0 @@
-/**
-Definition of the HtmlPdfCLIGenerator class.
-@module html-pdf-generator.js
-@license MIT. See LICENSE.md for details.
-*/
-
-
-
-(function() {
-
-
-
- var TemplateGenerator = require('./template-generator')
- , FS = require('fs-extra')
- , HTML = require( 'html' )
- , PATH = require('path')
- , SPAWN = require('../utils/safe-spawn')
- , SLASH = require('slash');
-
-
-
- /**
- 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.
- */
- var HtmlPdfCLIGenerator = module.exports = TemplateGenerator.extend({
-
-
-
- init: function() {
- this._super( 'pdf', 'html' );
- },
-
-
-
- /**
- Generate the binary PDF.
- */
- onBeforeSave: function( info ) {
-
- try {
- var safe_eng = info.opts.pdf || 'wkhtmltopdf';
- if( safe_eng !== 'none' )
- engines[ safe_eng ].call( this, info.mk, info.outputFile );
- return null; // halt further processing
- }
- catch(ex) {
- // { [Error: write EPIPE] code: 'EPIPE', errno: 'EPIPE', ... }
- // { [Error: ENOENT] }
- throw ( ex.inner && ex.inner.code === 'ENOENT' ) ?
- { fluenterror: this.codes.notOnPath, inner: ex.inner, engine: ex.cmd,
- stack: ex.inner && ex.inner.stack } :
- { fluenterror: this.codes.pdfGeneration, inner: ex, stack: ex.stack };
- }
- }
-
-
-
- });
-
-
-
- // TODO: Move each engine to a separate module
- var engines = {
-
-
-
- /**
- Generate a PDF from HTML using wkhtmltopdf's CLI interface.
- Spawns a child process with `wkhtmltopdf