From b38a7c1da28145cd6bbe7511d2ebb29157b1c730 Mon Sep 17 00:00:00 2001 From: devlinjd Date: Sat, 12 Dec 2015 10:48:26 -0500 Subject: [PATCH] Improve conversions and tests. --- src/core/convert.js | 29 ++- src/core/fluent-date.js | 5 +- src/core/fresh-resume.js | 8 +- src/core/jrs-resume.js | 43 +++- tests/resumes/jrs/jane-doe.json | 224 ++++++++++++++++++ .../jrs}/richard-hendriks.json | 0 tests/test-converter.js | 2 +- tests/test-jrs-sheet.js | 22 +- 8 files changed, 300 insertions(+), 33 deletions(-) create mode 100644 tests/resumes/jrs/jane-doe.json rename tests/{jrs-exemplar => resumes/jrs}/richard-hendriks.json (100%) diff --git a/src/core/convert.js b/src/core/convert.js index 15c8f7a..97c8c5f 100644 --- a/src/core/convert.js +++ b/src/core/convert.js @@ -55,10 +55,8 @@ FRESH to JSON Resume conversion routiens. writing: writing( src.publications, true), recognition: recognition( src.awards, true, foreign ), social: social( src.basics.profiles, true ), - - interests: src.interests, - references: src.references, + testimonials: references( src.references, true ), languages: src.languages, disposition: src.disposition // <--> round-trip }; @@ -101,7 +99,7 @@ FRESH to JSON Resume conversion routiens. awards: recognition( src.recognition, false, foreign ), publications: writing( src.writing, false ), interests: src.interests, - references: src.references, + references: references( src.testimonials, false ), samples: foreign ? src.samples : undefined, disposition: foreign ? src.disposition : undefined, languages: src.languages @@ -275,6 +273,27 @@ FRESH to JSON Resume conversion routiens. } } + function references( obj, direction ) { + if( direction ) { + return obj && obj.length && obj.map(function(ref){ + return { + name: ref.name, + flavor: 'professional', + quote: ref.reference, + private: false + }; + }); + } + else { + return obj && obj.length && obj.map(function(ref){ + return { + name: ref.name, + reference: ref.quote + }; + }); + } + } + function writing( obj, direction ) { if( direction ) { return obj.map(function( pub ) { @@ -292,7 +311,7 @@ FRESH to JSON Resume conversion routiens. return obj && obj.length ? obj.map(function(pub){ return { name: pub.title, - publisher: pub.publisher, + publisher: pub.publisher && pub.publisher.name ? pub.publisher.name : pub.publisher, releaseDate: pub.date, website: pub.url, summary: pub.summary diff --git a/src/core/fluent-date.js b/src/core/fluent-date.js index 7a3d967..26ba4be 100644 --- a/src/core/fluent-date.js +++ b/src/core/fluent-date.js @@ -66,7 +66,10 @@ FluentDate/*.prototype*/.fmt = function( dt ) { } } else { - if( dt.isValid && dt.isValid() ) + if( !dt ) { + return moment(); + } + else if( dt.isValid && dt.isValid() ) return dt; throw 'Unknown date object encountered.'; } diff --git a/src/core/fresh-resume.js b/src/core/fresh-resume.js index a0f5287..45698d5 100644 --- a/src/core/fresh-resume.js +++ b/src/core/fresh-resume.js @@ -12,7 +12,8 @@ Definition of the FRESHResume class. , PATH = require('path') , moment = require('moment') , MD = require('marked') - , CONVERTER = require('./convert'); + , CONVERTER = require('./convert') + , JRSResume = require('./jrs-resume'); /** A FRESH-style resume in JSON or YAML. @@ -46,13 +47,14 @@ Definition of the FRESHResume class. Save the sheet to disk in a specific format, either FRESH or JSON Resume. */ FreshResume.prototype.saveAs = function( filename, format ) { - this.imp.fileName = filename || this.imp.fileName; + if( format !== 'JRS' ) { + this.imp.fileName = filename || this.imp.fileName; FS.writeFileSync( this.imp.fileName, this.stringify(), 'utf8' ); } else { var newRep = CONVERTER.toJRS( this ); - FS.writeFileSync( this.imp.fileName, FreshResume.stringify( newRep ), 'utf8' ); + FS.writeFileSync( filename, JRSResume.stringify( newRep ), 'utf8' ); } return this; }; diff --git a/src/core/jrs-resume.js b/src/core/jrs-resume.js index 4ec8749..a1c068a 100644 --- a/src/core/jrs-resume.js +++ b/src/core/jrs-resume.js @@ -30,17 +30,22 @@ Definition of the JRSResume class. consistent format. Then sort each section by startDate descending. */ JRSResume.prototype.open = function( file, title ) { - this.imp = { fileName: file }; - this.imp.raw = FS.readFileSync( file, 'utf8' ); - return this.parse( this.imp.raw, title ); + //this.imp = { fileName: file }; <-- schema violation, tuck it into .basics instead + this.basics = { + imp: { + fileName: file, + raw: FS.readFileSync( file, 'utf8' ) + } + }; + return this.parse( this.basics.imp.raw, title ); }; /** Save the sheet to disk (for environments that have disk access). */ JRSResume.prototype.save = function( filename ) { - this.imp.fileName = filename || this.imp.fileName; - FS.writeFileSync( this.imp.fileName, this.stringify(), 'utf8' ); + this.basics.imp.fileName = filename || this.basics.imp.fileName; + FS.writeFileSync( this.basics.imp.fileName, this.stringify( this ), 'utf8' ); return this; }; @@ -48,7 +53,7 @@ Definition of the JRSResume class. Convert this object to a JSON string, sanitizing meta-properties along the way. Don't override .toString(). */ - JRSResume.prototype.stringify = function() { + 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', @@ -56,7 +61,11 @@ Definition of the JRSResume class. function( val ) { return key.trim() === val; } ) ? undefined : value; } - return JSON.stringify( this, replacer, 2 ); + return JSON.stringify( obj, replacer, 2 ); + }; + + JRSResume.prototype.stringify = function() { + return JRSResume.stringify( this ); }; /** @@ -67,16 +76,17 @@ Definition of the JRSResume class. JRSResume.prototype.parse = function( stringData, opts ) { opts = opts || { }; var rep = JSON.parse( stringData ); + extend( true, this, rep ); // Set up metadata if( opts.imp === undefined || opts.imp ) { - this.imp = this.imp || { }; - this.imp.title = (opts.title || this.imp.title) || this.basics.name; + this.basics.imp = this.basics.imp || { }; + this.basics.imp.title = (opts.title || this.basics.imp.title) || this.basics.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 = { + (opts.compute === undefined || opts.compute) && (this.basics.computed = { numYears: this.duration(), keywords: this.keywords() }); @@ -111,7 +121,7 @@ Definition of the JRSResume class. JRSResume.prototype.clear = function( clearMeta ) { clearMeta = ((clearMeta === undefined) && true) || clearMeta; clearMeta && (delete this.imp); - delete this.computed; // Don't use Object.keys() here + delete this.basics.computed; // Don't use Object.keys() here delete this.work; delete this.volunteer; delete this.education; @@ -169,8 +179,15 @@ Definition of the JRSResume class. 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 ); - return validate( this ); + 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; }; /** diff --git a/tests/resumes/jrs/jane-doe.json b/tests/resumes/jrs/jane-doe.json new file mode 100644 index 0000000..3f98b1f --- /dev/null +++ b/tests/resumes/jrs/jane-doe.json @@ -0,0 +1,224 @@ +{ + "basics": { + "name": "Jane Doe", + "label": "Senior Developer / Code Ninja", + "summary": "**Full-stack software developer with 6+ years industry experience** specializing in scalable cloud architectures for this, that, and the other. A native of southern CA, Jane enjoys hiking, mystery novels, and the company of Rufus, her two-year-old beagle.", + "website": "http://jane-doe.me", + "phone": "1-650-999-7777", + "email": "jdoe@onecoolstartup.io", + "picture": "jane_doe.png", + "location": { + "address": "Jane Doe\n123 Somewhere Rd.\nMountain View, CA 94035", + "postalCode": "94035", + "city": "Mountain View", + "countryCode": "US", + "region": "CA" + }, + "profiles": [ + { + "network": "GitHub", + "username": "jane-doe-was-here", + "url": "https://github.com/jane-doe-was-here" + }, + { + "network": "Twitter", + "username": "jane-doe-was-here", + "url": "https://twitter.com/jane-doe-was-here" + } + ] + }, + "work": [ + { + "company": "One Cool Startup", + "website": "https://onecool.io/does-not-exist", + "position": "Head Code Ninja", + "startDate": "2013-09", + "summary": "Development team manager for OneCoolApp and OneCoolWebsite, a free social network tiddlywink generator and lifestyle portal with over 200,000 users.", + "highlights": [ + "Managed a 5-person development team", + "Accomplishment 2", + "Etc." + ] + }, + { + "company": "Veridian Dynamics", + "website": "https://en.wikipedia.org/wiki/Better_Off_Ted#Plot", + "position": "Principal Developer", + "startDate": "2011-07", + "endDate": "2013-08", + "summary": "Developer on numerous projects culminating in technical lead role for the [Jabberwocky project](http://betteroffted.wikia.com/wiki/Jabberwocky) and promotion to principal developer.", + "highlights": [ + "Managed a 5-person development team", + "Accomplishment 2", + "Etc." + ] + }, + { + "company": "Stark Industries", + "position": "IT Administrator", + "startDate": "2008-10", + "endDate": "2011-06", + "summary": "Junior programmer with heavy code responsibilities. Promoted to intermediate role after 6 months.", + "highlights": [ + "Promoted to intermediate developer after 6 months", + "Accomplishment 2", + "Etc." + ] + }, + { + "company": "Dunder Mifflin", + "position": "Intern", + "startDate": "2008-06", + "endDate": "2008-09", + "summary": "Performed IT administration and deployments for Dunder Mifflin.", + "highlights": [ + "Supervised roll-out of Dunder Mifflin Infinity website.", + "Performed mission-critical system backups and ", + "Etc." + ] + } + ], + "education": [ + { + "institution": "Cornell University", + "gpa": "3.5", + "courses": [ + "Course 1", + "Course 2", + "Course 2" + ], + "startDate": "2005-09", + "endDate": "2008-05" + }, + { + "institution": "Medfield College", + "gpa": "3.2", + "courses": [ + "Course 1", + "Course 2", + "Course 2" + ], + "startDate": "2003-09", + "endDate": "2005-06" + } + ], + "skills": [ + { + "name": "Programming", + "keywords": [ + "C++", + "Ruby", + "Xcode" + ] + }, + { + "name": "Project Management", + "keywords": [ + "Agile" + ] + } + ], + "volunteer": [ + { + "organization": "Technology for Tots", + "position": "Technical Consultant", + "startDate": "2003-11", + "endDate": "2005-06", + "website": "http://technology-for-tots.org", + "summary": "Summary of this volunteer stint.", + "highlights": [ + "Accomplishment 1", + "Accomplishment 2", + "etc" + ] + }, + { + "organization": "US Army Reserves", + "position": "NCO", + "startDate": "1999-11", + "endDate": "2003-06", + "website": "http://www.usar.army.mil/", + "summary": "Summary of this military stint.", + "highlights": [ + "Accomplishment 1", + "Accomplishment 2", + "etc" + ] + } + ], + "awards": [ + { + "title": "Honorable Mention", + "date": "2012", + "awarder": "Google" + }, + { + "title": "Summa cum laude", + "date": "2012", + "awarder": "Cornell University" + } + ], + "publications": [ + { + "name": "Building User Interfaces with Electron and Atom", + "publisher": "Code Project", + "releaseDate": "2011", + "website": "http://codeproject.com/build-ui-electron-atom.aspx" + }, + { + "name": "Jane Doe Unplugged", + "publisher": "self", + "releaseDate": "2011", + "website": "http://jane-doe.me" + }, + { + "name": "Teach Yourself GORFF in 21 Days", + "publisher": "Amazon", + "releaseDate": "2008", + "website": "http://url.to.publication.com/blah", + "summary": "A primer on the programming language of GORFF, whose for loops are coterminous with all of time and space." + } + ], + "interests": [ + { + "name": "reading", + "summary": "Jane is a fan of mystery novels and courtroom dramas including Agatha Christie and John Grisham.", + "keywords": [ + "mystery", + "Agatha Christie", + "John Grisham" + ] + }, + { + "name": "hiking", + "summary": "Jane enjoys hiking, light mountain climbing, and has four summits under her belt!" + }, + { + "name": "yoga" + } + ], + "references": [ + { + "name": "John Davidson", + "reference": "Jane is awesome! I'd hire her again in a heartbeat." + }, + { + "name": "Elias Fullstacker", + "reference": "I worked with Jane on Jabberwocky and can vouch for her awesome technical capabilities and attention to detail. Insta-hire." + }, + { + "name": "Dana Nevins", + "reference": "I've known Jane personally and professionally for almost ten years. She is one in a million." + } + ], + "languages": [ + { + "language": "English", + "level": "Native" + }, + { + "language": "Spanish", + "level": "Moderate" + } + ] +} diff --git a/tests/jrs-exemplar/richard-hendriks.json b/tests/resumes/jrs/richard-hendriks.json similarity index 100% rename from tests/jrs-exemplar/richard-hendriks.json rename to tests/resumes/jrs/richard-hendriks.json diff --git a/tests/test-converter.js b/tests/test-converter.js index 1648f7a..134f086 100644 --- a/tests/test-converter.js +++ b/tests/test-converter.js @@ -17,7 +17,7 @@ describe('FRESH/JRS converter', function () { it('should round-trip from JRS to FRESH to JRS without modifying or losing data', function () { - var fileA = path.join( __dirname, 'jrs-exemplar/richard-hendriks.json' ); + var fileA = path.join( __dirname, 'resumes/jrs/richard-hendriks.json' ); var fileB = path.join( __dirname, 'sandbox/richard-hendriks.json' ); _sheet = new FRESHResume().open( fileA ); diff --git a/tests/test-jrs-sheet.js b/tests/test-jrs-sheet.js index 3afcba8..3bfaea2 100644 --- a/tests/test-jrs-sheet.js +++ b/tests/test-jrs-sheet.js @@ -9,13 +9,14 @@ var chai = require('chai') chai.config.includeStack = false; -describe('fullstack.json (JRS)', function () { +describe('jane-doe.json (JRS)', function () { var _sheet; it('should open without throwing an exception', function () { function tryOpen() { - _sheet = new JRSResume().open( 'node_modules/resample/fullstack/in/resume.json' ); + _sheet = new JRSResume().open( + path.join( __dirname, 'resumes/jrs/jane-doe.json' ) ); } tryOpen.should.not.Throw(); }); @@ -32,28 +33,29 @@ describe('fullstack.json (JRS)', function () { ).to.equal( true ); }); - it('should have a work duration of 11 years', function() { - _sheet.computed.numYears.should.equal( 11 ); + it('should have a work duration of 7 years', function() { + _sheet.basics.computed.numYears.should.equal( 7 ); }); it('should save without throwing an exception', function(){ function trySave() { - _sheet.save( 'tests/sandbox/fullstack.json' ); + _sheet.save( 'tests/sandbox/jane-doe.json' ); } trySave.should.not.Throw(); }); it('should not be modified after saving', function() { - var savedSheet = new JRSResume().open( 'tests/sandbox/fullstack.json' ); + var savedSheet = new JRSResume().open( 'tests/sandbox/jane-doe.json' ); _sheet.stringify().should.equal( savedSheet.stringify() ) }); it('should validate against the JSON Resume schema', function() { - var schemaJson = require('../src/core/resume.json'); - var validate = validator( schemaJson, { verbose: true } ); - var result = validate( JSON.parse( _sheet.imp.raw ) ); + var result = _sheet.isValid(); + // var schemaJson = require('../src/core/resume.json'); + // var validate = validator( schemaJson, { verbose: true } ); + // var result = validate( JSON.parse( _sheet.imp.raw ) ); result || console.log("\n\nOops, resume didn't validate. " + - "Validation errors:\n\n", validate.errors, "\n\n"); + "Validation errors:\n\n", _sheet.basics.imp.validationErrors, "\n\n"); result.should.equal( true ); });