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 `. 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) { - - // Save the markup to a temporary file - var tempFile = fOut.replace(/\.pdf$/i, '.pdf.html'); - FS.writeFileSync( tempFile, markup, 'utf8' ); - var info = SPAWN( 'wkhtmltopdf', [ tempFile, fOut ] ); - - }, - - - - /** - Generate a PDF from HTML using Phantom's CLI interface. - Spawns a child process with `phantomjs