From 1a6d7d5723bc6c1dd43d59337061b7c438896be1 Mon Sep 17 00:00:00 2001 From: Ya Zhuang Date: Fri, 25 Dec 2015 06:59:29 +0800 Subject: [PATCH 001/488] change: package.json enhancement - add `scripts` for something like `npm test` and `npm run grunt` - add missing devDep: `grunt-cli` --- .gitignore | 1 + package.json | 5 + {tests => test}/resumes/jrs-0.0.0/empty.json | 0 .../resumes/jrs-0.0.0/jane-incomplete.json | 0 .../resumes/jrs-0.0.0/jane-q-fullstacker.json | 0 .../resumes/jrs-0.0.0/richard-hendriks.json | 0 test/sandbox/richard-hendriks.json | 130 ++++++++++++++++++ {tests => test}/test-cli.js | 0 {tests => test}/test-converter.js | 0 {tests => test}/test-fresh-sheet.js | 2 +- {tests => test}/test-jrs-sheet.js | 2 +- {tests => test}/test-themes.js | 0 12 files changed, 138 insertions(+), 2 deletions(-) rename {tests => test}/resumes/jrs-0.0.0/empty.json (100%) rename {tests => test}/resumes/jrs-0.0.0/jane-incomplete.json (100%) rename {tests => test}/resumes/jrs-0.0.0/jane-q-fullstacker.json (100%) rename {tests => test}/resumes/jrs-0.0.0/richard-hendriks.json (100%) create mode 100644 test/sandbox/richard-hendriks.json rename {tests => test}/test-cli.js (100%) rename {tests => test}/test-converter.js (100%) rename {tests => test}/test-fresh-sheet.js (97%) rename {tests => test}/test-jrs-sheet.js (97%) rename {tests => test}/test-themes.js (100%) diff --git a/.gitignore b/.gitignore index ce751fe4..ee4ba40a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules/ tests/sandbox/ docs/ +npm-debug.log diff --git a/package.json b/package.json index e8539e25..025892ba 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,10 @@ "type": "git", "url": "https://github.com/hacksalot/HackMyResume.git" }, + "scripts": { + "test": "mocha && grunt", + "grunt": "grunt" + }, "keywords": [ "resume", "CV", @@ -61,6 +65,7 @@ "devDependencies": { "chai": "*", "grunt": "*", + "grunt-cli": "^0.1.13", "grunt-contrib-clean": "^0.7.0", "grunt-contrib-jshint": "^0.11.3", "grunt-contrib-yuidoc": "^0.10.0", diff --git a/tests/resumes/jrs-0.0.0/empty.json b/test/resumes/jrs-0.0.0/empty.json similarity index 100% rename from tests/resumes/jrs-0.0.0/empty.json rename to test/resumes/jrs-0.0.0/empty.json diff --git a/tests/resumes/jrs-0.0.0/jane-incomplete.json b/test/resumes/jrs-0.0.0/jane-incomplete.json similarity index 100% rename from tests/resumes/jrs-0.0.0/jane-incomplete.json rename to test/resumes/jrs-0.0.0/jane-incomplete.json diff --git a/tests/resumes/jrs-0.0.0/jane-q-fullstacker.json b/test/resumes/jrs-0.0.0/jane-q-fullstacker.json similarity index 100% rename from tests/resumes/jrs-0.0.0/jane-q-fullstacker.json rename to test/resumes/jrs-0.0.0/jane-q-fullstacker.json diff --git a/tests/resumes/jrs-0.0.0/richard-hendriks.json b/test/resumes/jrs-0.0.0/richard-hendriks.json similarity index 100% rename from tests/resumes/jrs-0.0.0/richard-hendriks.json rename to test/resumes/jrs-0.0.0/richard-hendriks.json diff --git a/test/sandbox/richard-hendriks.json b/test/sandbox/richard-hendriks.json new file mode 100644 index 00000000..b40fac73 --- /dev/null +++ b/test/sandbox/richard-hendriks.json @@ -0,0 +1,130 @@ +{ + "basics": { + "name": "Richard Hendriks", + "label": "Programmer", + "summary": "Richard hails from Tulsa. He has earned degrees from the University of Oklahoma and Stanford. (Go Sooners and Cardinals!) Before starting Pied Piper, he worked for Hooli as a part time software developer. While his work focuses on applied information theory, mostly optimizing lossless compression schema of both the length-limited and adaptive variants, his non-work interests range widely, everything from quantum computing to chaos theory. He could tell you about it, but THAT would NOT be a “length-limited” conversation!", + "website": "http://richardhendricks.com", + "phone": "(912) 555-4321", + "email": "richard.hendriks@gmail.com", + "picture": "", + "location": { + "address": "2712 Broadway St", + "postalCode": "CA 94115", + "city": "San Francisco", + "countryCode": "US", + "region": "California" + }, + "profiles": [ + { + "network": "Twitter", + "username": "neutralthoughts", + "url": "" + }, + { + "network": "SoundCloud", + "username": "dandymusicnl", + "url": "https://soundcloud.com/dandymusicnl" + } + ] + }, + "work": [ + { + "company": "Pied Piper", + "website": "http://piedpiper.com", + "position": "CEO/President", + "startDate": "2013-12-01", + "endDate": "2014-12-01", + "summary": "Pied Piper is a multi-platform technology based on a proprietary universal compression algorithm that has consistently fielded high Weisman Scores™ that are not merely competitive, but approach the theoretical limit of lossless compression.", + "highlights": [ + "Build an algorithm for artist to detect if their music was violating copy right infringement laws", + "Successfully won Techcrunch Disrupt", + "Optimized an algorithm that holds the current world record for Weisman Scores" + ] + } + ], + "education": [ + { + "institution": "University of Oklahoma", + "gpa": "4.0", + "courses": [ + "DB1101 - Basic SQL", + "CS2011 - Java Introduction" + ], + "startDate": "2011-06-01", + "endDate": "2014-01-01", + "area": "Information Technology", + "studyType": "Bachelor" + } + ], + "skills": [ + { + "name": "Web Development", + "level": "Master", + "keywords": [ + "HTML", + "CSS", + "Javascript" + ] + }, + { + "name": "Compression", + "level": "Master", + "keywords": [ + "Mpeg", + "MP4", + "GIF" + ] + } + ], + "volunteer": [ + { + "organization": "CoderDojo", + "position": "Teacher", + "startDate": "2012-01-01", + "endDate": "2013-01-01", + "website": "http://coderdojo.com/", + "summary": "Global movement of free coding clubs for young people.", + "highlights": [ + "Awarded 'Teacher of the Month'" + ] + } + ], + "awards": [ + { + "title": "Digital Compression Pioneer Award", + "date": "2014-11-01", + "awarder": "Techcrunch", + "summary": "There is no spoon." + } + ], + "publications": [ + { + "name": "Video compression for 3d media", + "publisher": "Hooli", + "releaseDate": "2014-10-01", + "website": "http://en.wikipedia.org/wiki/Silicon_Valley_(TV_series)", + "summary": "Innovative middle-out compression algorithm that changes the way we store data." + } + ], + "interests": [ + { + "name": "Wildlife", + "keywords": [ + "Ferrets", + "Unicorns" + ] + } + ], + "references": [ + { + "name": "Erlich Bachman", + "reference": "It is my pleasure to recommend Richard, his performance working as a consultant for Main St. Company proved that he will be a valuable addition to any company." + } + ], + "languages": [ + { + "language": "English", + "fluency": "Native speaker" + } + ] +} \ No newline at end of file diff --git a/tests/test-cli.js b/test/test-cli.js similarity index 100% rename from tests/test-cli.js rename to test/test-cli.js diff --git a/tests/test-converter.js b/test/test-converter.js similarity index 100% rename from tests/test-converter.js rename to test/test-converter.js diff --git a/tests/test-fresh-sheet.js b/test/test-fresh-sheet.js similarity index 97% rename from tests/test-fresh-sheet.js rename to test/test-fresh-sheet.js index f35487d3..055931a0 100644 --- a/tests/test-fresh-sheet.js +++ b/test/test-fresh-sheet.js @@ -50,7 +50,7 @@ describe('jane-doe.json (FRESH)', function () { it('should not be modified after saving', function() { var savedSheet = new FRESHResume().open('tests/sandbox/jane-q-fullstacker.json'); - _sheet.stringify().should.equal( savedSheet.stringify() ) + _sheet.stringify().should.equal( savedSheet.stringify() ); }); it('should validate against the FRESH resume schema', function() { diff --git a/tests/test-jrs-sheet.js b/test/test-jrs-sheet.js similarity index 97% rename from tests/test-jrs-sheet.js rename to test/test-jrs-sheet.js index acd6f2ef..86bb7abd 100644 --- a/tests/test-jrs-sheet.js +++ b/test/test-jrs-sheet.js @@ -45,7 +45,7 @@ function testResume( opts ) { it('should not be modified after saving', function() { var savedSheet = new JRSResume().open( 'tests/sandbox/' + opts.title + '.json' ); - _sheet.stringify().should.equal( savedSheet.stringify() ) + _sheet.stringify().should.equal( savedSheet.stringify() ); }); it('should ' + (opts.isValid ? '' : 'NOT ') + 'validate against the JSON Resume schema', function() { diff --git a/tests/test-themes.js b/test/test-themes.js similarity index 100% rename from tests/test-themes.js rename to test/test-themes.js From 68628e33044a80921a14fbe8b6538774ebef7af0 Mon Sep 17 00:00:00 2001 From: Ya Zhuang Date: Fri, 25 Dec 2015 07:05:58 +0800 Subject: [PATCH 002/488] add: travis yml and badge --- .travis.yml | 6 ++++++ README.md | 4 ++++ 2 files changed, 10 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..1eecbcd0 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,6 @@ +language: node_js +node_js: + - "4.0" + - "4.1" + - "4.2" + - "5.0" diff --git a/README.md b/README.md index a7d2e725..9b8ebfca 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ HackMyResume ============ + +[![Build status][travis-image]][travis-url] + *Create polished résumés and CVs in multiple formats from your command line or shell. Author in clean Markdown and JSON, export to Word, HTML, PDF, LaTeX, plain text, and other arbitrary formats. Fight the power, save trees. Compatible @@ -302,3 +305,4 @@ MIT. Go crazy. See [LICENSE.md][1] for details. [fresh]: https://github.com/fluentdesk/FRESH [fresca]: https://github.com/fluentdesk/FRESCA [dry]: https://en.wikipedia.org/wiki/Don%27t_repeat_yourself +[travis-url]: https://travis-ci.org/hacksalot/HackMyResume From 1ad297ec7abc9e465c198e3015b23fecc5329631 Mon Sep 17 00:00:00 2001 From: Ya Zhuang Date: Fri, 25 Dec 2015 07:08:17 +0800 Subject: [PATCH 003/488] add: missing travis-image url --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 9b8ebfca..141d7d89 100644 --- a/README.md +++ b/README.md @@ -305,4 +305,5 @@ MIT. Go crazy. See [LICENSE.md][1] for details. [fresh]: https://github.com/fluentdesk/FRESH [fresca]: https://github.com/fluentdesk/FRESCA [dry]: https://en.wikipedia.org/wiki/Don%27t_repeat_yourself +[travis-image]: https://img.shields.io/travis/palomajs/paloma.svg?style=flat-square [travis-url]: https://travis-ci.org/hacksalot/HackMyResume From 1265ecab9fed8b5ec3d19a33b4d8609a097c6319 Mon Sep 17 00:00:00 2001 From: Ya Zhuang Date: Sat, 26 Dec 2015 19:13:15 +0800 Subject: [PATCH 004/488] chore: remove generated resume, more node ci - node 0.11 0.12 - remove and ignore `test/sandbox` from git --- .travis.yml | 2 + test/.gitignore | 1 + test/sandbox/richard-hendriks.json | 130 ----------------------------- 3 files changed, 3 insertions(+), 130 deletions(-) create mode 100644 test/.gitignore delete mode 100644 test/sandbox/richard-hendriks.json diff --git a/.travis.yml b/.travis.yml index 1eecbcd0..e0bc653f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,7 @@ language: node_js node_js: + - "0.11" + - "0.12" - "4.0" - "4.1" - "4.2" diff --git a/test/.gitignore b/test/.gitignore new file mode 100644 index 00000000..97f3e312 --- /dev/null +++ b/test/.gitignore @@ -0,0 +1 @@ +sandbox/ diff --git a/test/sandbox/richard-hendriks.json b/test/sandbox/richard-hendriks.json deleted file mode 100644 index b40fac73..00000000 --- a/test/sandbox/richard-hendriks.json +++ /dev/null @@ -1,130 +0,0 @@ -{ - "basics": { - "name": "Richard Hendriks", - "label": "Programmer", - "summary": "Richard hails from Tulsa. He has earned degrees from the University of Oklahoma and Stanford. (Go Sooners and Cardinals!) Before starting Pied Piper, he worked for Hooli as a part time software developer. While his work focuses on applied information theory, mostly optimizing lossless compression schema of both the length-limited and adaptive variants, his non-work interests range widely, everything from quantum computing to chaos theory. He could tell you about it, but THAT would NOT be a “length-limited” conversation!", - "website": "http://richardhendricks.com", - "phone": "(912) 555-4321", - "email": "richard.hendriks@gmail.com", - "picture": "", - "location": { - "address": "2712 Broadway St", - "postalCode": "CA 94115", - "city": "San Francisco", - "countryCode": "US", - "region": "California" - }, - "profiles": [ - { - "network": "Twitter", - "username": "neutralthoughts", - "url": "" - }, - { - "network": "SoundCloud", - "username": "dandymusicnl", - "url": "https://soundcloud.com/dandymusicnl" - } - ] - }, - "work": [ - { - "company": "Pied Piper", - "website": "http://piedpiper.com", - "position": "CEO/President", - "startDate": "2013-12-01", - "endDate": "2014-12-01", - "summary": "Pied Piper is a multi-platform technology based on a proprietary universal compression algorithm that has consistently fielded high Weisman Scores™ that are not merely competitive, but approach the theoretical limit of lossless compression.", - "highlights": [ - "Build an algorithm for artist to detect if their music was violating copy right infringement laws", - "Successfully won Techcrunch Disrupt", - "Optimized an algorithm that holds the current world record for Weisman Scores" - ] - } - ], - "education": [ - { - "institution": "University of Oklahoma", - "gpa": "4.0", - "courses": [ - "DB1101 - Basic SQL", - "CS2011 - Java Introduction" - ], - "startDate": "2011-06-01", - "endDate": "2014-01-01", - "area": "Information Technology", - "studyType": "Bachelor" - } - ], - "skills": [ - { - "name": "Web Development", - "level": "Master", - "keywords": [ - "HTML", - "CSS", - "Javascript" - ] - }, - { - "name": "Compression", - "level": "Master", - "keywords": [ - "Mpeg", - "MP4", - "GIF" - ] - } - ], - "volunteer": [ - { - "organization": "CoderDojo", - "position": "Teacher", - "startDate": "2012-01-01", - "endDate": "2013-01-01", - "website": "http://coderdojo.com/", - "summary": "Global movement of free coding clubs for young people.", - "highlights": [ - "Awarded 'Teacher of the Month'" - ] - } - ], - "awards": [ - { - "title": "Digital Compression Pioneer Award", - "date": "2014-11-01", - "awarder": "Techcrunch", - "summary": "There is no spoon." - } - ], - "publications": [ - { - "name": "Video compression for 3d media", - "publisher": "Hooli", - "releaseDate": "2014-10-01", - "website": "http://en.wikipedia.org/wiki/Silicon_Valley_(TV_series)", - "summary": "Innovative middle-out compression algorithm that changes the way we store data." - } - ], - "interests": [ - { - "name": "Wildlife", - "keywords": [ - "Ferrets", - "Unicorns" - ] - } - ], - "references": [ - { - "name": "Erlich Bachman", - "reference": "It is my pleasure to recommend Richard, his performance working as a consultant for Main St. Company proved that he will be a valuable addition to any company." - } - ], - "languages": [ - { - "language": "English", - "fluency": "Native speaker" - } - ] -} \ No newline at end of file From 0514f7805c359a885b958ec15404ac609a9ea694 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Sat, 26 Dec 2015 22:47:39 -0500 Subject: [PATCH 005/488] Add contributors to package.json. Contributors first, author last. --- package.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/package.json b/package.json index 025892ba..9ab84f59 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,11 @@ "template" ], "author": "hacksalot (https://github.com/hacksalot)", + "contributors": [ + "Edmund Jorgensen (https://github.com/tomheon)", + "Ya Zhuang (https://github.com/zhuangya)", + "hacksalot (https://github.com/hacksalot)" + ], "license": "MIT", "preferGlobal": "true", "bugs": { From ec511483748f3a1df61b438854cc699ae7c5784f Mon Sep 17 00:00:00 2001 From: hacksalot Date: Sun, 27 Dec 2015 00:08:45 -0500 Subject: [PATCH 006/488] Introduce interim contribution guidelines. --- CONTRIBUTING.md | 58 +++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 7 ++++++ 2 files changed, 65 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..b6c64530 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,58 @@ +Contributing +============ + +*Note: HackMyResume is also available as [FluentCV][fcv]. Contributors are +credited in both.* + +HackMyResume needs your help! Our contribution workflow is based on [GitHub +Flow][flow] and we respond to all pull requests and issues, usually within 24 +hours. HackMyResume has no corporate affiliation and no commercial basis, which +allows the project to maintain a strict user-first policy, rapid development +velocity, and a liberal stance on contributions and exotic functionality in +keeping with the spirit (and name) of the tool. + +In short, your code is welcome here. + +## How To Contribute + +1. Optional: [**open an issue**][iss] identifying the feature or bug you'd like +to implement or fix. This step isn't required — you can start hacking away on +HackMyResume without clearing it with us — but helps avoid duplication of work +and ensures that your changes will be accepted once submitted. +2. **Fork and clone** the HackMyResume project. +3. Ideally, **create a new feature branch** (eg, `feat/new-awesome-feature` or +similar; call it whatever you like) to perform your work in. +4. **Install dependencies** by running `npm install` in the top-level +HackMyResume folder. +5. Make your **commits** as usual. +6. **Verify** your changes locally with `npm test`. +7. **Push** your commits. +7. **Submit a pull request** from your feature branch to the HackMyResume `dev` +branch. +8. We'll typically **respond** within 24 hours. +9. Your awesome changes will be **merged** after verification. + +## Project Maintainers + +HackMyResume is currently maintained by [hacksalot][ha] with assistance from +[tomheon][th] and our awesome [contributors][awesome]. Please direct all official +or internal inquiries to: + +``` +admin@hackmyresume.com +``` + +You can reach hacksalot directly at: + +``` +hacksalot@indevious.com +``` + +Thanks! See you out there in the trenches. + +[fcv]: https://github.com/fluentdesk/fluentcv +[flow]: https://guides.github.com/introduction/flow/ +[iss]: https://github.com/hacksalot/HackMyResume/issues +[ha]: https://github.com/hacksalot +[th]: https://github.com/tomheon +[awesome]: https://github.com/hacksalot/HackMyResume/graphs/contributors diff --git a/README.md b/README.md index 141d7d89..9e35161a 100644 --- a/README.md +++ b/README.md @@ -288,6 +288,12 @@ hackmyresume generate resume.json -o someFile.all -s hackmyresume generate resume.json -o someFile.all --silent ``` +## Contributing + +HackMyResume is a community-driven free and open source project under the MIT +License. Contributions are encouraged and we respond to all PRs and issues, +usually within 24 hours. See [CONTRIBUTING.md][contribute] for details. + ## License MIT. Go crazy. See [LICENSE.md][1] for details. @@ -307,3 +313,4 @@ MIT. Go crazy. See [LICENSE.md][1] for details. [dry]: https://en.wikipedia.org/wiki/Don%27t_repeat_yourself [travis-image]: https://img.shields.io/travis/palomajs/paloma.svg?style=flat-square [travis-url]: https://travis-ci.org/hacksalot/HackMyResume +[contribute]: CONTRIBUTING.md From 07b303e53000c98971b9523e9f3d1468cbb6ce07 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Mon, 28 Dec 2015 03:51:39 -0500 Subject: [PATCH 007/488] Bump version to 1.3.0. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9ab84f59..1ac5aca7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hackmyresume", - "version": "1.2.2", + "version": "1.3.0", "description": "Generate polished résumés and CVs in HTML, Markdown, LaTeX, MS Word, PDF, plain text, JSON, XML, YAML, smoke signal, and carrier pigeon.", "repository": { "type": "git", From 9423a1984236147edbafad289071e8aa133d59ec Mon Sep 17 00:00:00 2001 From: hacksalot Date: Mon, 28 Dec 2015 04:01:30 -0500 Subject: [PATCH 008/488] Remove extraneous references to "tests" plural. --- Gruntfile.js | 4 ++-- test/test-cli.js | 12 ++++++------ test/test-fresh-sheet.js | 4 ++-- test/test-jrs-sheet.js | 4 ++-- test/test-themes.js | 2 +- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 45cd6ca9..3e6d20af 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -14,10 +14,10 @@ module.exports = function (grunt) { ui: 'bdd', reporter: 'spec' }, - all: { src: ['tests/*.js'] } + all: { src: ['test/*.js'] } }, - clean: ['tests/sandbox'], + clean: ['test/sandbox'], yuidoc: { compile: { diff --git a/test/test-cli.js b/test/test-cli.js index 93be452a..7c43fc9a 100644 --- a/test/test-cli.js +++ b/test/test-cli.js @@ -43,15 +43,15 @@ describe('Testing CLI interface', function () { silent: true }; - run( 'new', ['tests/sandbox/new-fresh-resume.json'], [], opts, ' (FRESH format)' ); - run( 'new', ['tests/sandbox/new-jrs-resume.json'], [], opts2, ' (JRS format)' ); - run( 'new', ['tests/sandbox/new-1.json', 'tests/sandbox/new-2.json', 'tests/sandbox/new-3.json'], [], opts, ' (multiple FRESH resumes)' ); - run( 'new', ['tests/sandbox/new-jrs-1.json', 'tests/sandbox/new-jrs-2.json', 'tests/sandbox/new-jrs-3.json'], [], opts, ' (multiple JRS resumes)' ); - run( 'new', ['tests/sandbox/new-jrs-resume.json'], [], opts2, ' (JRS format)' ); + run( 'new', ['test/sandbox/new-fresh-resume.json'], [], opts, ' (FRESH format)' ); + run( 'new', ['test/sandbox/new-jrs-resume.json'], [], opts2, ' (JRS format)' ); + run( 'new', ['test/sandbox/new-1.json', 'test/sandbox/new-2.json', 'test/sandbox/new-3.json'], [], opts, ' (multiple FRESH resumes)' ); + run( 'new', ['test/sandbox/new-jrs-1.json', 'test/sandbox/new-jrs-2.json', 'test/sandbox/new-jrs-3.json'], [], opts, ' (multiple JRS resumes)' ); + run( 'new', ['test/sandbox/new-jrs-resume.json'], [], opts2, ' (JRS format)' ); fail( 'new', [], [], opts, " (when a filename isn't specified)" ); run( 'validate', ['node_modules/jane-q-fullstacker/resume/jane-resume.json'], [], opts, ' (FRESH format)' ); - run( 'validate', ['tests/sandbox/new-fresh-resume.json'], [], opts, ' (FRESH format)' ); + run( 'validate', ['test/sandbox/new-fresh-resume.json'], [], opts, ' (FRESH format)' ); function run( verb, src, dst, opts, msg ) { msg = msg || '.'; diff --git a/test/test-fresh-sheet.js b/test/test-fresh-sheet.js index 055931a0..4d285592 100644 --- a/test/test-fresh-sheet.js +++ b/test/test-fresh-sheet.js @@ -43,13 +43,13 @@ describe('jane-doe.json (FRESH)', function () { it('should save without throwing an exception', function(){ function trySave() { - _sheet.save( 'tests/sandbox/jane-q-fullstacker.json' ); + _sheet.save( 'test/sandbox/jane-q-fullstacker.json' ); } trySave.should.not.Throw(); }); it('should not be modified after saving', function() { - var savedSheet = new FRESHResume().open('tests/sandbox/jane-q-fullstacker.json'); + var savedSheet = new FRESHResume().open('test/sandbox/jane-q-fullstacker.json'); _sheet.stringify().should.equal( savedSheet.stringify() ); }); diff --git a/test/test-jrs-sheet.js b/test/test-jrs-sheet.js index 86bb7abd..ec0a0517 100644 --- a/test/test-jrs-sheet.js +++ b/test/test-jrs-sheet.js @@ -38,13 +38,13 @@ function testResume( opts ) { it('should save without throwing an exception', function() { var that = this; function trySave() { - _sheet.save( 'tests/sandbox/' + opts.title + '.json' ); + _sheet.save( 'test/sandbox/' + opts.title + '.json' ); } trySave.should.not.Throw(); }); it('should not be modified after saving', function() { - var savedSheet = new JRSResume().open( 'tests/sandbox/' + opts.title + '.json' ); + var savedSheet = new JRSResume().open( 'test/sandbox/' + opts.title + '.json' ); _sheet.stringify().should.equal( savedSheet.stringify() ); }); diff --git a/test/test-themes.js b/test/test-themes.js index 9d27120e..44fd1431 100644 --- a/test/test-themes.js +++ b/test/test-themes.js @@ -30,7 +30,7 @@ describe('Testing themes', function () { it( themeName.toUpperCase() + ' theme should generate without throwing an exception', function () { function tryOpen() { var src = ['node_modules/jane-q-fullstacker/resume/jane-resume.json']; - var dst = ['tests/sandbox/' + themeName + '/resume.all']; + var dst = ['test/sandbox/' + themeName + '/resume.all']; var opts = { theme: themeName, format: 'FRESH', From db31744c983b4d2c5bd34b8abfe581d802b9e175 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Mon, 28 Dec 2015 04:16:53 -0500 Subject: [PATCH 009/488] Adjust "npm test" command. Fix issue with tests being run twice and run tests through Grunt for LINTing and other pre/post processing. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1ac5aca7..fceeae0a 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "url": "https://github.com/hacksalot/HackMyResume.git" }, "scripts": { - "test": "mocha && grunt", + "test": "grunt test", "grunt": "grunt" }, "keywords": [ From 547b87afc6d7f89ddab1fdb4f01ef66d6de2aa7f Mon Sep 17 00:00:00 2001 From: hacksalot Date: Mon, 28 Dec 2015 04:17:48 -0500 Subject: [PATCH 010/488] LINT prior to running tests. --- Gruntfile.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 3e6d20af..ae15fac7 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -51,9 +51,9 @@ module.exports = function (grunt) { grunt.loadNpmTasks('grunt-contrib-clean'); grunt.registerTask('test', 'Test the HackMyResume library.', - function( config ) { grunt.task.run( ['clean','simplemocha:all'] ); }); + function( config ) { grunt.task.run( ['clean','jshint','simplemocha:all'] ); }); grunt.registerTask('document', 'Generate HackMyResume library documentation.', function( config ) { grunt.task.run( ['yuidoc'] ); }); - grunt.registerTask('default', [ 'jshint', 'test', 'yuidoc' ]); + grunt.registerTask('default', [ 'test', 'yuidoc' ]); }; From 3c1ae4cbd1e6dc1bde1459e1995a05fef697d00f Mon Sep 17 00:00:00 2001 From: hacksalot Date: Mon, 28 Dec 2015 04:37:42 -0500 Subject: [PATCH 011/488] Add baseline support for local generation of JSON Resume themes. --- package.json | 1 + src/core/fresh-resume.js | 18 +++++---- src/core/jrs-resume.js | 8 ++++ src/core/load-source-resumes.js | 13 ------- src/core/resume-factory.js | 43 +++++++++++++++++++++ src/core/theme.js | 25 +++++++++--- src/verbs/convert.js | 4 +- src/verbs/generate.js | 67 ++++++++++++++++----------------- src/verbs/validate.js | 4 +- 9 files changed, 119 insertions(+), 64 deletions(-) delete mode 100644 src/core/load-source-resumes.js create mode 100644 src/core/resume-factory.js diff --git a/package.json b/package.json index fceeae0a..ba19457a 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,7 @@ "path-exists": "^2.1.0", "recursive-readdir-sync": "^1.0.6", "simple-html-tokenizer": "^0.2.0", + "string.prototype.startswith": "^0.2.0", "underscore": "^1.8.3", "wkhtmltopdf": "^0.1.5", "xml-escape": "^1.0.0", diff --git a/src/core/fresh-resume.js b/src/core/fresh-resume.js index 4754194f..58c97a51 100644 --- a/src/core/fresh-resume.js +++ b/src/core/fresh-resume.js @@ -143,15 +143,12 @@ Definition of the FRESHResume class. }; /** - Open and parse the specified JSON resume sheet. Merge the JSON object model - onto this Sheet instance with extend() and convert sheet dates to a safe & + Initialize the FreshResume from JSON data. + 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. */ - FreshResume.prototype.parse = function( stringData, opts ) { - - // Parse the incoming JSON representation - var rep = JSON.parse( stringData ); - + FreshResume.prototype.parseJSON = function( rep, opts ) { // Convert JSON Resume to FRESH if necessary if( rep.basics ) { rep = CONVERTER.toFRESH( rep ); @@ -178,6 +175,13 @@ Definition of the FRESHResume class. return this; }; + /** + Initialize the the FreshResume from string data. + */ + FreshResume.prototype.parse = function( stringData, opts ) { + return this.parseJSON( JSON.parse( stringData ), opts ); + }; + /** Return a unique list of all keywords across all skills. */ diff --git a/src/core/jrs-resume.js b/src/core/jrs-resume.js index c9311a07..619e83a4 100644 --- a/src/core/jrs-resume.js +++ b/src/core/jrs-resume.js @@ -70,6 +70,7 @@ Definition of the JRSResume class. }; /** + Initialize the JRS Resume from string data. Open and parse the specified JSON resume sheet. 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. @@ -77,7 +78,14 @@ Definition of the JRSResume class. JRSResume.prototype.parse = function( stringData, opts ) { opts = opts || { }; var rep = JSON.parse( stringData ); + return this.parseJSON( rep, opts ); + }; + /** + Initialize the JRSRume from JSON data. + */ + JRSResume.prototype.parseJSON = function( rep, opts ) { + opts = opts || { }; extend( true, this, rep ); // Set up metadata if( opts.imp === undefined || opts.imp ) { diff --git a/src/core/load-source-resumes.js b/src/core/load-source-resumes.js deleted file mode 100644 index b26ae70d..00000000 --- a/src/core/load-source-resumes.js +++ /dev/null @@ -1,13 +0,0 @@ -(function(){ - - var FRESHResume = require('../core/fresh-resume'); - - module.exports = function loadSourceResumes( src, log, fn ) { - return src.map( function( res ) { - log( 'Reading '.info + 'SOURCE'.infoBold + ' resume: '.info + - res.cyan.bold ); - return (fn && fn(res)) || (new FRESHResume()).open( res ); - }); - }; - -}()); diff --git a/src/core/resume-factory.js b/src/core/resume-factory.js new file mode 100644 index 00000000..b72f8a90 --- /dev/null +++ b/src/core/resume-factory.js @@ -0,0 +1,43 @@ +/** +Core resume-loading logic for HackMyResume. +@module resume-factory.js +*/ + +(function(){ + + require('string.prototype.startswith'); + var FS = require('fs'); + var ResumeConverter = require('./convert'); + + /** + A simple factory class for FRESH and JSON Resumes. + @class ResumeFactory + */ + module.exports = { + + /** + Load one or more resumes in a specific source format. + */ + load: function ( src, log, fn, toFormat ) { + + toFormat = toFormat && (toFormat.toLowerCase().trim()) || 'fresh'; + var ResumeClass = require('../core/' + toFormat + '-resume'); + + return src.map( function( res ) { + var rezJson = JSON.parse( FS.readFileSync( res ) ); + var orgFormat = ( rezJson.meta && rezJson.meta.format && + rezJson.meta.format.startsWith('FRESH@') ) ? + 'fresh' : 'jrs'; + if(orgFormat !== toFormat) { + rezJson = ResumeConverter[ 'to' + toFormat.toUpperCase() ]( rezJson ); + } + // TODO: Core should not log + log( 'Reading '.info + orgFormat.toUpperCase().infoBold + ' resume: '.info + res.cyan.bold ); + return (fn && fn(res)) || (new ResumeClass()).parseJSON( rezJson ); + }); + + } + + }; + +}()); diff --git a/src/core/theme.js b/src/core/theme.js index 3b5f729b..6e8356a8 100644 --- a/src/core/theme.js +++ b/src/core/theme.js @@ -1,6 +1,6 @@ /** Definition of the Theme class. -@license MIT. Copyright (c) 2015 James Devlin / FluentDesk. +@license MIT. Copyright (c) 2015 hacksalot / FluentDesk. @module theme.js */ @@ -12,6 +12,7 @@ Definition of the Theme class. , _ = require('underscore') , PATH = require('path') , parsePath = require('parse-filepath') + , pathExists = require('path-exists').sync , EXTEND = require('../utils/extend') , moment = require('moment') , RECURSIVE_READ_DIR = require('recursive-readdir-sync'); @@ -29,9 +30,26 @@ Definition of the Theme class. */ Theme.prototype.open = function( themeFolder ) { - // Open the [theme-name].json file; should have the same name as folder 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 = { }; + + // See if the theme has a package.json. If so, load it. + var packageJsonPath = PATH.join(themeFolder, 'package.json'); + if( pathExists( packageJsonPath ) ) { + var themePack = require( themeFolder ); + var themePkgJson = require( packageJsonPath ); + this.name = themePkgJson.name; + this.render = (themePack && themePack.render) || undefined; + this.formats = { html: { title: 'html', outFormat: 'html', ext: 'html', path: null, data: null } }; + return this; + } + + // Otherwise, do a full theme load var themeFile = PATH.join( themeFolder, pathInfo.basename + '.json' ); var themeInfo = JSON.parse( FS.readFileSync( themeFile, 'utf8' ) ); var that = this; @@ -39,9 +57,6 @@ Definition of the Theme class. // Move properties from the theme JSON file to the theme object EXTEND( true, this, themeInfo ); - // Set up a formats has for the theme - var formatsHash = { }; - // Check for an explicit "formats" entry in the theme JSON. If it has one, // then this theme declares its files explicitly. if( !!this.formats ) { diff --git a/src/verbs/convert.js b/src/verbs/convert.js index b6c12390..7386072c 100644 --- a/src/verbs/convert.js +++ b/src/verbs/convert.js @@ -1,6 +1,6 @@ (function(){ - var loadSourceResumes = require('../core/load-source-resumes'); + var ResumeFactory = require('../core/resume-factory'); /** Convert between FRESH and JRS formats. @@ -16,7 +16,7 @@ if( src && dst && src.length && dst.length && src.length !== dst.length ) { throw { fluenterror: 7 }; } - var sheets = loadSourceResumes( src, _log ); + var sheets = ResumeFactory.load( src, _log ); sheets.forEach(function(sheet, idx){ var sourceFormat = sheet.imp.orgFormat === 'JRS' ? 'JRS' : 'FRESH'; var targetFormat = sourceFormat === 'JRS' ? 'FRESH' : 'JRS'; diff --git a/src/verbs/generate.js b/src/verbs/generate.js index d6629d29..0061ab2c 100644 --- a/src/verbs/generate.js +++ b/src/verbs/generate.js @@ -1,11 +1,12 @@ (function() { var PATH = require('path') + , FS = require('fs') , parsePath = require('parse-filepath') , MKDIRP = require('mkdirp') , _opts = require('../core/default-options') , FluentTheme = require('../core/theme') - , loadSourceResumes = require('../core/load-source-resumes') + , ResumeFactory = require('../core/resume-factory') , _ = require('underscore') , _fmts = require('../core/default-formats') , _err, _log, rez; @@ -36,24 +37,11 @@ _opts.theme = (opts.theme && opts.theme.toLowerCase().trim())|| 'modern'; _opts.prettify = opts.prettify === true ? _opts.prettify : false; - // Load input resumes... - if( !src || !src.length ) { throw { fluenterror: 3 }; } - var sheets = loadSourceResumes( src, _log ); - - // Merge input resumes... - var msg = ''; - rez = _.reduceRight( sheets, function( a, b, idx ) { - msg += ((idx == sheets.length - 2) ? - 'Merging '.gray+ a.imp.fileName : '') + ' onto '.gray + b.imp.fileName; - return extend( true, b, a ); - }); - msg && _log(msg); - // Verify the specified theme name/path var relativeThemeFolder = '../../node_modules/fluent-themes/themes'; var tFolder = PATH.resolve( __dirname, relativeThemeFolder, _opts.theme); var exists = require('path-exists').sync; - if (!exists( tFolder )) { + if( !exists( tFolder ) ) { tFolder = PATH.resolve( _opts.theme ); if (!exists( tFolder )) { throw { fluenterror: 1, data: _opts.theme }; @@ -63,8 +51,22 @@ // Load the theme var theTheme = (new FluentTheme()).open( tFolder ); _opts.themeObj = theTheme; + var numFormats = theTheme.formats ? Object.keys(theTheme.formats).length : 2; _log( 'Applying '.info + theTheme.name.toUpperCase().infoBold + - (' theme (' + Object.keys(theTheme.formats).length + ' formats)').info); + (' theme (' + numFormats + ' formats)').info); + + // Load input resumes... + if( !src || !src.length ) { throw { fluenterror: 3 }; } + var sheets = ResumeFactory.load( src, _log, null, theTheme.render ? 'JRS' : 'FRESH' ); + + // Merge input resumes... + var msg = ''; + rez = _.reduceRight( sheets, function( a, b, idx ) { + msg += ((idx == sheets.length - 2) ? + 'Merging '.gray+ a.imp.fileName : '') + ' onto '.gray + b.imp.fileName; + return extend( true, b, a ); + }); + msg && _log(msg); // Expand output resumes... (can't use map() here) var targets = [], that = this; @@ -74,11 +76,15 @@ pa = parsePath(to), fmat = pa.extname || '.all'; - targets.push.apply(targets, fmat === '.all' ? + targets.push.apply( + targets, fmat === '.all' ? + Object.keys( theTheme.formats ).map(function(k){ var z = theTheme.formats[k]; return { file: to.replace(/all$/g,z.outFormat), fmt: z }; - }) : [{ file: to, fmt: theTheme.getFormat( fmat.slice(1) ) }]); + }) : + + [{ file: to, fmt: theTheme.getFormat( fmat.slice(1) ) }]); }); @@ -113,22 +119,6 @@ function(fmt) { return fmt.name === targInfo.fmt.outFormat; })[0]; MKDIRP.sync( PATH.dirname( f ) ); // Ensure dest folder exists; theFormat.gen.generate( rez, f, _opts ); - - // targInfo.fmt.files.forEach( function( form ) { - // - // if( form.action === 'transform' ) { - // var theFormat = _fmts.filter( function( fmt ) { - // return fmt.name === targInfo.fmt.outFormat; - // })[0]; - // MKDIRP.sync( PATH.dirname( f ) ); // Ensure dest folder exists; - // theFormat.gen.generate( rez, f, _opts ); - // } - // else if( form.action === null ) { - // // Copy the file - // } - // - // }); - } // Otherwise the theme has no files section else { @@ -139,7 +129,14 @@ theFormat = _fmts.filter( function(fmt) { return fmt.name === targInfo.fmt.outFormat; })[0]; MKDIRP.sync( PATH.dirname( f ) ); // Ensure dest folder exists; - theFormat.gen.generate( rez, f, _opts ); + + if( theme.render ) { + var rezHtml = theme.render( rez ); + FS.writeFileSync( f, rezHtml ); + } + else { + theFormat.gen.generate( rez, f, _opts ); + } } } catch( ex ) { diff --git a/src/verbs/validate.js b/src/verbs/validate.js index 52bd6dac..239576c9 100644 --- a/src/verbs/validate.js +++ b/src/verbs/validate.js @@ -1,7 +1,7 @@ (function() { var FS = require('fs'); - var loadSourceResumes = require('../core/load-source-resumes'); + var ResumeFactory = require('../core/resume-factory'); module.exports = @@ -20,7 +20,7 @@ }; // Load input resumes... - var sheets = loadSourceResumes(src, _log, function( res ) { + var sheets = ResumeFactory.load(src, _log, function( res ) { try { return { file: res, From 6bc6b3262e8ca9c93ac2ae83dc60502269d32b47 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Mon, 28 Dec 2015 04:39:03 -0500 Subject: [PATCH 012/488] Add tests for FRESH/JRS cross-generation. Ability to generate JSON Resume themes from FRESH format resumes and vice-versa. --- package.json | 4 ++++ test/test-themes.js | 45 ++++++++++++++++++++++++++++++++------------- 2 files changed, 36 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index ba19457a..aa960109 100644 --- a/package.json +++ b/package.json @@ -77,6 +77,10 @@ "grunt-contrib-yuidoc": "^0.10.0", "grunt-simple-mocha": "*", "jane-q-fullstacker": "fluentdesk/jane-q-fullstacker", + "jsonresume-theme-boilerplate": "^0.1.2", + "jsonresume-theme-classy": "^1.0.9", + "jsonresume-theme-modern": "0.0.18", + "jsonresume-theme-sceptile": "^1.0.5", "mocha": "*", "resample": "fluentdesk/resample" } diff --git a/test/test-themes.js b/test/test-themes.js index 44fd1431..8d195b34 100644 --- a/test/test-themes.js +++ b/test/test-themes.js @@ -26,28 +26,47 @@ describe('Testing themes', function () { useful: 'green', }); - function genTheme( themeName ) { - it( themeName.toUpperCase() + ' theme should generate without throwing an exception', function () { + function genTheme( fmt, src, themeName, themeLoc, testTitle ) { + themeLoc = themeLoc || themeName; + testTitle = themeName.toUpperCase() + ' theme should generate without throwing an exception'; + it( testTitle, function () { function tryOpen() { - var src = ['node_modules/jane-q-fullstacker/resume/jane-resume.json']; - var dst = ['test/sandbox/' + themeName + '/resume.all']; + //var src = ['node_modules/jane-q-fullstacker/resume/jane-resume.json']; + var dst = ['test/sandbox/' + fmt + '/' + themeName + '/resume.all']; var opts = { - theme: themeName, - format: 'FRESH', + theme: themeLoc, + format: fmt, prettify: true, silent: true }; - FCMD.verbs.build( src, dst, opts, function() { } ); + FCMD.verbs.build( src, dst, opts, function() {} ); } tryOpen.should.not.Throw(); }); } - genTheme('hello-world'); - genTheme('compact'); - genTheme('modern'); - genTheme('minimist'); - genTheme('awesome'); - genTheme('positive'); + var src = ['node_modules/jane-q-fullstacker/resume/jane-resume.json']; + genTheme('FRESH', src, 'hello-world'); + genTheme('FRESH', src, 'compact'); + genTheme('FRESH', src, 'modern'); + genTheme('FRESH', src, 'minimist'); + genTheme('FRESH', src, 'awesome'); + genTheme('FRESH', src, 'positive'); + genTheme('FRESH', src, 'jsonresume-theme-boilerplate', 'node_modules/jsonresume-theme-boilerplate' ); + genTheme('FRESH', src, 'jsonresume-theme-sceptile', 'node_modules/jsonresume-theme-sceptile' ); + genTheme('FRESH', src, 'jsonresume-theme-modern', 'node_modules/jsonresume-theme-modern' ); + genTheme('FRESH', src, 'jsonresume-theme-classy', 'node_modules/jsonresume-theme-classy' ); + + src = ['test/resumes/jrs-0.0.0/richard-hendriks.json']; + genTheme('JRS', src, 'hello-world'); + genTheme('JRS', src, 'compact'); + genTheme('JRS', src, 'modern'); + genTheme('JRS', src, 'minimist'); + genTheme('JRS', src, 'awesome'); + genTheme('JRS', src, 'positive'); + genTheme('JRS', src, 'jsonresume-theme-boilerplate', 'node_modules/jsonresume-theme-boilerplate' ); + genTheme('JRS', src, 'jsonresume-theme-sceptile', 'node_modules/jsonresume-theme-sceptile' ); + genTheme('JRS', src, 'jsonresume-theme-modern', 'node_modules/jsonresume-theme-modern' ); + genTheme('JRS', src, 'jsonresume-theme-classy', 'node_modules/jsonresume-theme-classy' ); }); From 6b0ea0c7bddab028f149bfdaac6e407a26daa9b3 Mon Sep 17 00:00:00 2001 From: Ya Zhuang Date: Tue, 29 Dec 2015 03:29:13 +0800 Subject: [PATCH 013/488] add: png format --- package.json | 1 + src/core/default-formats.js | 2 ++ src/core/theme.js | 2 +- src/eng/handlebars-generator.js | 2 +- src/eng/underscore-generator.js | 2 +- src/gen/html-png-generator.js | 48 +++++++++++++++++++++++++++++++++ src/hackmyapi.js | 3 ++- 7 files changed, 56 insertions(+), 4 deletions(-) create mode 100644 src/gen/html-png-generator.js diff --git a/package.json b/package.json index 025892ba..ea6ef343 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "recursive-readdir-sync": "^1.0.6", "simple-html-tokenizer": "^0.2.0", "underscore": "^1.8.3", + "webshot": "^0.16.0", "wkhtmltopdf": "^0.1.5", "xml-escape": "^1.0.0", "yamljs": "^0.2.4" diff --git a/src/core/default-formats.js b/src/core/default-formats.js index a9620bf9..ce344c33 100644 --- a/src/core/default-formats.js +++ b/src/core/default-formats.js @@ -10,10 +10,12 @@ { name: 'txt', ext: 'txt', gen: new FLUENT.TextGenerator() }, { name: 'doc', ext: 'doc', fmt: 'xml', gen: new FLUENT.WordGenerator() }, { name: 'pdf', ext: 'pdf', fmt: 'html', is: false, gen: new FLUENT.HtmlPdfGenerator() }, + { name: 'png', ext: 'png', fmt: 'html', is: false, gen: new FLUENT.HtmlPngGenerator() }, { name: 'md', ext: 'md', fmt: 'txt', gen: new FLUENT.MarkdownGenerator() }, { name: 'json', ext: 'json', gen: new FLUENT.JsonGenerator() }, { name: 'yml', ext: 'yml', fmt: 'yml', gen: new FLUENT.JsonYamlGenerator() }, { name: 'latex', ext: 'tex', fmt: 'latex', gen: new FLUENT.LaTeXGenerator() } + ]; }()); diff --git a/src/core/theme.js b/src/core/theme.js index 3b5f729b..d2ef9095 100644 --- a/src/core/theme.js +++ b/src/core/theme.js @@ -102,7 +102,7 @@ Definition of the Theme class. var portion = pathInfo.dirname.replace(tplFolder,''); if( portion && portion.trim() ) { if( portion[1] === '_' ) return; - var reg = /^(?:\/|\\)(html|latex|doc|pdf|partials)(?:\/|\\)?/ig; + var reg = /^(?:\/|\\)(html|latex|doc|pdf|png|partials)(?:\/|\\)?/ig; var res = reg.exec( portion ); if( res ) { if( res[1] !== 'partials' ) { diff --git a/src/eng/handlebars-generator.js b/src/eng/handlebars-generator.js index b244a3fc..71dbf58b 100644 --- a/src/eng/handlebars-generator.js +++ b/src/eng/handlebars-generator.js @@ -36,7 +36,7 @@ Definition of the HandlebarsGenerator class. // Compile and run the Handlebars template. var template = HANDLEBARS.compile(jst); return template({ - r: format === 'html' || format === 'pdf' ? json.markdownify() : json, + r: format === 'html' || format === 'pdf' || format === 'png' ? json.markdownify() : json, RAW: json, filt: opts.filters, cssInfo: cssInfo, diff --git a/src/eng/underscore-generator.js b/src/eng/underscore-generator.js index fe2fb36e..0a9be84c 100644 --- a/src/eng/underscore-generator.js +++ b/src/eng/underscore-generator.js @@ -35,7 +35,7 @@ Definition of the UnderscoreGenerator class. // Compile and run the template. TODO: avoid unnecessary recompiles. var compiled = _.template(jst); var ret = compiled({ - r: format === 'html' || format === 'pdf' ? json.markdownify() : json, + r: format === 'html' || format === 'pdf' || format === 'png' ? json.markdownify() : json, filt: opts.filters, XML: require('xml-escape'), RAW: json, diff --git a/src/gen/html-png-generator.js b/src/gen/html-png-generator.js new file mode 100644 index 00000000..3265066d --- /dev/null +++ b/src/gen/html-png-generator.js @@ -0,0 +1,48 @@ +/** +Definition of the HtmlPngGenerator class. +@license MIT. Copyright (c) 2015 James Devlin / FluentDesk. +@module html-png-generator.js +*/ + +(function() { + + var TemplateGenerator = require('./template-generator') + , FS = require('fs-extra') + , HTML = require( 'html' ); + + /** + An HTML-based PDF resume generator for HackMyResume. + */ + var HtmlPngGenerator = module.exports = TemplateGenerator.extend({ + + init: function() { +console.log('png generator init'); + this._super( 'png', 'html' ); + }, + + /** + Generate the binary PDF. + */ + onBeforeSave: function( info ) { +console.log('png generator onBeforeSave'); + png( info.mk, info.outputFile ); + return null; // halt further processing + } + + }); + + /** + Generate a PDF from HTML. + */ + function png( markup, fOut ) { + + console.log('>> #png()'); + console.log(markup); + console.log(fOut); + + require('webshot')( markup , { encoding: 'binary', siteType: 'html' } ) + .pipe( FS.createWriteStream( fOut ) ); + + } + +}()); diff --git a/src/hackmyapi.js b/src/hackmyapi.js index 6d96368d..f36c40ab 100644 --- a/src/hackmyapi.js +++ b/src/hackmyapi.js @@ -18,5 +18,6 @@ module.exports = { JsonGenerator: require('./gen/json-generator'), YamlGenerator: require('./gen/yaml-generator'), JsonYamlGenerator: require('./gen/json-yaml-generator'), - LaTeXGenerator: require('./gen/latex-generator') + LaTeXGenerator: require('./gen/latex-generator'), + HtmlPngGenerator: require('./gen/html-png-generator') }; From 5dd3d1a3b44e19706a01faaed63434e7a9d3c622 Mon Sep 17 00:00:00 2001 From: Ya Zhuang Date: Tue, 29 Dec 2015 03:40:42 +0800 Subject: [PATCH 014/488] chore: remove debugging console logs --- src/gen/html-png-generator.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/gen/html-png-generator.js b/src/gen/html-png-generator.js index 3265066d..4d9b5b9e 100644 --- a/src/gen/html-png-generator.js +++ b/src/gen/html-png-generator.js @@ -16,7 +16,6 @@ Definition of the HtmlPngGenerator class. var HtmlPngGenerator = module.exports = TemplateGenerator.extend({ init: function() { -console.log('png generator init'); this._super( 'png', 'html' ); }, @@ -24,7 +23,6 @@ console.log('png generator init'); Generate the binary PDF. */ onBeforeSave: function( info ) { -console.log('png generator onBeforeSave'); png( info.mk, info.outputFile ); return null; // halt further processing } @@ -36,10 +34,6 @@ console.log('png generator onBeforeSave'); */ function png( markup, fOut ) { - console.log('>> #png()'); - console.log(markup); - console.log(fOut); - require('webshot')( markup , { encoding: 'binary', siteType: 'html' } ) .pipe( FS.createWriteStream( fOut ) ); From bf5c040971382b253a5d5ae04f3e68b04781c1d4 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Tue, 29 Dec 2015 03:10:26 -0500 Subject: [PATCH 015/488] Copy JRS theme assets to target. --- package.json | 1 + src/verbs/generate.js | 17 +++++++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index aa960109..36504f8c 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "homepage": "https://github.com/hacksalot/HackMyResume", "dependencies": { "colors": "^1.1.2", + "copy": "^0.1.3", "fluent-themes": "~0.7.1-beta", "fresca": "~0.2.2", "fs-extra": "^0.24.0", diff --git a/src/verbs/generate.js b/src/verbs/generate.js index 0061ab2c..5c07ee97 100644 --- a/src/verbs/generate.js +++ b/src/verbs/generate.js @@ -57,7 +57,8 @@ // Load input resumes... if( !src || !src.length ) { throw { fluenterror: 3 }; } - var sheets = ResumeFactory.load( src, _log, null, theTheme.render ? 'JRS' : 'FRESH' ); + var sheets = ResumeFactory.load( src, _log, null, + theTheme.render ? 'JRS' : 'FRESH' ); // Merge input resumes... var msg = ''; @@ -128,9 +129,21 @@ theFormat = _fmts.filter( function(fmt) { return fmt.name === targInfo.fmt.outFormat; })[0]; - MKDIRP.sync( PATH.dirname( f ) ); // Ensure dest folder exists; + var outFolder = PATH.dirname( f ); + MKDIRP.sync( outFolder ); // Ensure dest folder exists; + + // TODO: refactor if( theme.render ) { + var COPY = require('copy'); + var globs = [ /*'**',*/ '*.css', '*.js', '*.png', '*.jpg', '*.gif', '*.bmp' ]; + COPY.sync( globs , outFolder, { + cwd: theme.folder, nodir: true, + ignore: ['node_modules/','node_modules/**'] + // rewrite: function(p1, p2) { + // return PATH.join(p2, p1); + // } + }); var rezHtml = theme.render( rez ); FS.writeFileSync( f, rezHtml ); } From e65c0e128e83dc41050681180bb44259c1899f6d Mon Sep 17 00:00:00 2001 From: hacksalot Date: Tue, 29 Dec 2015 03:50:00 -0500 Subject: [PATCH 016/488] Fix tests glitch. --- Gruntfile.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index ae15fac7..536fe8da 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -38,7 +38,7 @@ module.exports = function (grunt) { laxcomma: true, expr: true }, - all: ['Gruntfile.js', 'src/**/*.js', 'test/**/*.js'] + all: ['Gruntfile.js', 'src/**/*.js', 'test/*.js'] } }; diff --git a/package.json b/package.json index 36504f8c..6658c7ff 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "url": "https://github.com/hacksalot/HackMyResume.git" }, "scripts": { - "test": "grunt test", + "test": "grunt clean && mocha", "grunt": "grunt" }, "keywords": [ From 13430bcad52e52c36d6591cfb57287b95f6b6f62 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Tue, 29 Dec 2015 05:09:05 -0500 Subject: [PATCH 017/488] Refactor status codes. --- src/core/status-codes.js | 21 ++++++++++++++++ src/gen/base-generator.js | 9 +------ src/index.js | 50 +++++++++++++++++++++++++++++++-------- 3 files changed, 62 insertions(+), 18 deletions(-) create mode 100644 src/core/status-codes.js diff --git a/src/core/status-codes.js b/src/core/status-codes.js new file mode 100644 index 00000000..d0467c77 --- /dev/null +++ b/src/core/status-codes.js @@ -0,0 +1,21 @@ +/** +Status codes for HackMyResume. +@module status-codes.js +*/ + +(function(){ + + module.exports = { + success: 0, + themeNotFound: 1, + copyCss: 2, + resumeNotFound: 3, + missingCommand: 4, + invalidCommand: 5, + resumeNotFoundAlt: 6, + inputOutputParity: 7, + createNameMissing: 8, + wkhtmltopdf: 9 + }; + +}()); diff --git a/src/gen/base-generator.js b/src/gen/base-generator.js index 9e1bc899..5d117775 100644 --- a/src/gen/base-generator.js +++ b/src/gen/base-generator.js @@ -26,14 +26,7 @@ Definition of the BaseGenerator class. /** Status codes. */ - codes: { - success: 0, - themeNotFound: 1, - copyCss: 2, - resumeNotFound: 3, - missingCommand: 4, - invalidCommand: 5 - }, + codes: require('../core/status-codes'), /** Generator options. diff --git a/src/index.js b/src/index.js index 5b65b713..07081da7 100644 --- a/src/index.js +++ b/src/index.js @@ -12,6 +12,7 @@ var ARGS = require( 'minimist' ) , COLORS = require('colors') , FS = require('fs') , PATH = require('path') + , HACKMYSTATUS = require('./core/status-codes') , opts = { } , title = ('\n*** HackMyResume v' + PKG.version + ' ***').bold.white , _ = require('underscore'); @@ -99,20 +100,49 @@ function handleError( ex ) { if( ex.fluenterror ){ switch( ex.fluenterror ) { // TODO: Remove magic numbers - case 1: msg = "The specified theme couldn't be found: " + ex.data; break; - case 2: msg = "Couldn't copy CSS file to destination folder"; break; - case 3: msg = 'Please '.guide + 'specify a valid input resume'.guide.bold + ' in FRESH or JSON Resume format.'.guide; break; - case 4: msg = title + "\nPlease ".guide + "specify a command".guide.bold + " (".guide + + + case HACKMYSTATUS.themeNotFound: + msg = "The specified theme couldn't be found: " + ex.data; + break; + + case HACKMYSTATUS.copyCSS: + msg = "Couldn't copy CSS file to destination folder"; + break; + + case HACKMYSTATUS.resumeNotFound: + msg = 'Please '.guide + 'specify a valid input resume'.guide.bold + + ' in FRESH or JSON Resume format.'.guide; + break; + + case HACKMYSTATUS.missingCommand: + msg = title + "\nPlease ".guide + "specify a command".guide.bold + " (".guide + Object.keys( FCMD.verbs ).map( function(v, idx, ar) { return (idx === ar.length - 1 ? 'or '.guide : '') + v.toUpperCase().guide; - }).join(', '.guide) + ").\n\n".guide + FS.readFileSync( PATH.join(__dirname, 'use.txt'), 'utf8' ).info.bold; + }).join(', '.guide) + ").\n\n".guide + + FS.readFileSync( PATH.join(__dirname, 'use.txt'), 'utf8' ).info.bold; + break; + + case HACKMYSTATUS.invalidCommand: + msg = 'Please '.guide + 'specify the output resume file'.guide.bold + + ' that should be created.'.guide; + break; + + case HACKMYSTATUS.resumeNotFoundAlt: + msg = 'Please '.guide + 'specify a valid input resume'.guide.bold + + ' in either FRESH or JSON Resume format.'.guide; + break; + + case HACKMYSTATUS.inputOutputParity: + msg = 'Please '.guide + 'specify an output file name'.guide.bold + + ' for every input file you wish to convert.'.guide; + break; + + case HACKMYSTATUS.createNameMissing: + msg = 'Please '.guide + 'specify the filename of the resume'.guide.bold + + ' to create.'.guide; break; - //case 4: msg = title + '\n' + ; break; - case 5: msg = 'Please '.guide + 'specify the output resume file'.guide.bold + ' that should be created.'.guide; break; - case 6: msg = 'Please '.guide + 'specify a valid input resume'.guide.bold + ' in either FRESH or JSON Resume format.'.guide; break; - case 7: msg = 'Please '.guide + 'specify an output file name'.guide.bold + ' for every input file you wish to convert.'.guide; break; - case 8: msg = 'Please '.guide + 'specify the filename of the resume'.guide.bold + ' to create.'.guide; break; + } exitCode = ex.fluenterror; From 02ef2b2241a1c42d1f3ac8fdd38f74814c20f35f Mon Sep 17 00:00:00 2001 From: hacksalot Date: Tue, 29 Dec 2015 06:35:55 -0500 Subject: [PATCH 018/488] Improve error handling. Better support for spawn errors encountered during generation (for ex, PDFs through wkhtml) + general refactoring. --- src/core/error-handler.js | 93 +++++++++++++++++++++++++++++++++++ src/core/spawn-watch.js | 22 +++++++++ src/gen/html-pdf-generator.js | 76 +++++++++++++++------------- src/gen/template-generator.js | 2 +- src/index.js | 83 +++---------------------------- test/test-themes.js | 5 +- 6 files changed, 169 insertions(+), 112 deletions(-) create mode 100644 src/core/error-handler.js create mode 100644 src/core/spawn-watch.js diff --git a/src/core/error-handler.js b/src/core/error-handler.js new file mode 100644 index 00000000..53aa014e --- /dev/null +++ b/src/core/error-handler.js @@ -0,0 +1,93 @@ +/** +@module error-handler.js +*/ + +(function() { + + var HACKMYSTATUS = require('./status-codes') + , PKG = require('../../package.json') + , title = ('\n*** HackMyResume v' + PKG.version + ' ***').bold.white; + + var ErrorHandler = module.exports = { + + + err: function( ex, shouldExit ) { + var msg = '', exitCode; + + if( ex.fluenterror ){ + switch( ex.fluenterror ) { // TODO: Remove magic numbers + + case HACKMYSTATUS.themeNotFound: + msg = "The specified theme couldn't be found: " + ex.data; + break; + + case HACKMYSTATUS.copyCSS: + msg = "Couldn't copy CSS file to destination folder"; + break; + + case HACKMYSTATUS.resumeNotFound: + msg = 'Please '.guide + 'specify a valid input resume'.guide.bold + + ' in FRESH or JSON Resume format.'.guide; + break; + + case HACKMYSTATUS.missingCommand: + msg = title + "\nPlease ".guide + "specify a command".guide.bold + " (".guide + + Object.keys( FCMD.verbs ).map( function(v, idx, ar) { + return (idx === ar.length - 1 ? 'or '.guide : '') + + v.toUpperCase().guide; + }).join(', '.guide) + ").\n\n".guide + + FS.readFileSync( PATH.join(__dirname, 'use.txt'), 'utf8' ).info.bold; + break; + + case HACKMYSTATUS.invalidCommand: + msg = 'Please '.guide + 'specify the output resume file'.guide.bold + + ' that should be created.'.guide; + break; + + case HACKMYSTATUS.resumeNotFoundAlt: + msg = 'Please '.guide + 'specify a valid input resume'.guide.bold + + ' in either FRESH or JSON Resume format.'.guide; + break; + + case HACKMYSTATUS.inputOutputParity: + msg = 'Please '.guide + 'specify an output file name'.guide.bold + + ' for every input file you wish to convert.'.guide; + break; + + case HACKMYSTATUS.createNameMissing: + msg = 'Please '.guide + 'specify the filename of the resume'.guide.bold + + ' to create.'.guide; + break; + + case HACKMYSTATUS.wkhtmltopdf: + msg = 'ERROR: PDF generation failed. '.red.bold + ('Make sure wkhtmltopdf is ' + + 'installed and accessible from your path.').red; + break; + + } + exitCode = ex.fluenterror; + + } + else { + msg = ex.toString(); + exitCode = 4; + } + + var idx = msg.indexOf('Error: '); + var trimmed = idx === -1 ? msg : msg.substring( idx + 7 ); + if( !ex.fluenterror || ex.fluenterror < 3 ) { // TODO: magic #s + console.log( ('ERROR: ' + trimmed.toString()).red.bold ); + console.log( ex.stack.gray); + } + else { + console.log( trimmed.toString() ); + } + + if( shouldExit ) + process.exit( exitCode ); + + } + + }; + +}()); diff --git a/src/core/spawn-watch.js b/src/core/spawn-watch.js new file mode 100644 index 00000000..ac06849b --- /dev/null +++ b/src/core/spawn-watch.js @@ -0,0 +1,22 @@ +/** +@module spawn-watch.js +*/ + +(function() { + +// Catch various out-of-band child process errors such as ENOENT for PDFs +// http://stackoverflow.com/q/27688804 +var SpawnWatcher = module.exports = function() { + var childProcess = require("child_process"); + var oldSpawn = childProcess.spawn; + childProcess.spawn = function() { + return oldSpawn.apply(this, arguments) + .on('error', function(err) { + require('./error-handler').err( err, false ); + }); + }; +}(); + +//SpawnWatcher(); + +}()); diff --git a/src/gen/html-pdf-generator.js b/src/gen/html-pdf-generator.js index 853de90b..705c4b34 100644 --- a/src/gen/html-pdf-generator.js +++ b/src/gen/html-pdf-generator.js @@ -1,6 +1,5 @@ /** Definition of the HtmlPdfGenerator class. -@license MIT. Copyright (c) 2015 James Devlin / FluentDesk. @module html-pdf-generator.js */ @@ -23,7 +22,7 @@ Definition of the HtmlPdfGenerator class. Generate the binary PDF. */ onBeforeSave: function( info ) { - pdf( info.mk, info.outputFile ); + pdf.call( this, info.mk, info.outputFile ); return null; // halt further processing } @@ -34,39 +33,50 @@ Definition of the HtmlPdfGenerator class. */ function pdf( markup, fOut ) { - var pdfCount = 0; - if( false ) { //( _opts.pdf === 'phantom' || _opts.pdf == 'all' ) { - pdfCount++; - require('phantom').create( function( ph ) { - ph.createPage( function( page ) { - page.setContent( markup ); - page.set('paperSize', { - format: 'A4', - orientation: 'portrait', - margin: '1cm' - }); - page.set("viewportSize", { - width: 1024, // TODO: option-ify - height: 768 // TODO: Use "A" sizes - }); - page.set('onLoadFinished', function(success) { - page.render( fOut ); - pdfCount++; - ph.exit(); - }); - }, - { dnodeOpts: { weak: false } } ); - }); + pdf_wkhtmltopdf.call( this, markup, fOut ); + + } + + /** + Generate a PDF from HTML using wkhtmltopdf. + */ + function pdf_wkhtmltopdf( markup, fOut ) { + var wk; + try { + wk = require('wkhtmltopdf'); + wk( markup, { pageSize: 'letter' } ) + .pipe( FS.createWriteStream( fOut ) ); } - if( true ) { // _opts.pdf === 'wkhtmltopdf' || _opts.pdf == 'all' ) { - var fOut2 = fOut; - if( pdfCount == 1 ) { - fOut2 = fOut2.replace(/\.pdf$/g, '.b.pdf'); - } - require('wkhtmltopdf')( markup, { pageSize: 'letter' } ) - .pipe( FS.createWriteStream( fOut2 ) ); - pdfCount++; + catch(ex) { + // { [Error: write EPIPE] code: 'EPIPE', errno: 'EPIPE', syscall: 'write' } + // { [Error: ENOENT] } + throw { fluenterror: this.codes.wkhtmltopdf }; } } + + // function pdf_phantom() { + // pdfCount++; + // require('phantom').create( function( ph ) { + // ph.createPage( function( page ) { + // page.setContent( markup ); + // page.set('paperSize', { + // format: 'A4', + // orientation: 'portrait', + // margin: '1cm' + // }); + // page.set("viewportSize", { + // width: 1024, // TODO: option-ify + // height: 768 // TODO: Use "A" sizes + // }); + // page.set('onLoadFinished', function(success) { + // page.render( fOut ); + // pdfCount++; + // ph.exit(); + // }); + // }, + // { dnodeOpts: { weak: false } } ); + // }); + // } + }()); diff --git a/src/gen/template-generator.js b/src/gen/template-generator.js index 86687e82..60a7a6f3 100644 --- a/src/gen/template-generator.js +++ b/src/gen/template-generator.js @@ -156,7 +156,7 @@ Definition of the TemplateGenerator class. { outputFile: fileName, mk: file.data } ); } catch(ex) { - console.log(ex); + require('../core/error-handler').err(ex, false); } } else if( file.info.action === null/* && theme.explicit*/ ) { diff --git a/src/index.js b/src/index.js index 07081da7..48d25031 100644 --- a/src/index.js +++ b/src/index.js @@ -2,11 +2,14 @@ /** Command-line interface (CLI) for HackMyResume. -@license MIT. Copyright (c) 2015 James M. Devlin / FluentDesk. +@license MIT. Copyright (c) 2015 hacksalot (https://github.com/hacksalot) @module index.js */ -var ARGS = require( 'minimist' ) + + +var SPAWNW = require('./core/spawn-watch') + , ARGS = require( 'minimist' ) , FCMD = require( './hackmycmd') , PKG = require('../package.json') , COLORS = require('colors') @@ -19,11 +22,12 @@ var ARGS = require( 'minimist' ) + try { main(); } catch( ex ) { - handleError( ex ); + require('./core/error-handler').err( ex, true ); } @@ -91,76 +95,3 @@ function getOpts( args ) { silent: args.s || args.silent }; } - -// TODO: refactor -function handleError( ex ) { - var msg = '', exitCode; - - - - if( ex.fluenterror ){ - switch( ex.fluenterror ) { // TODO: Remove magic numbers - - case HACKMYSTATUS.themeNotFound: - msg = "The specified theme couldn't be found: " + ex.data; - break; - - case HACKMYSTATUS.copyCSS: - msg = "Couldn't copy CSS file to destination folder"; - break; - - case HACKMYSTATUS.resumeNotFound: - msg = 'Please '.guide + 'specify a valid input resume'.guide.bold + - ' in FRESH or JSON Resume format.'.guide; - break; - - case HACKMYSTATUS.missingCommand: - msg = title + "\nPlease ".guide + "specify a command".guide.bold + " (".guide + - Object.keys( FCMD.verbs ).map( function(v, idx, ar) { - return (idx === ar.length - 1 ? 'or '.guide : '') + - v.toUpperCase().guide; - }).join(', '.guide) + ").\n\n".guide + - FS.readFileSync( PATH.join(__dirname, 'use.txt'), 'utf8' ).info.bold; - break; - - case HACKMYSTATUS.invalidCommand: - msg = 'Please '.guide + 'specify the output resume file'.guide.bold + - ' that should be created.'.guide; - break; - - case HACKMYSTATUS.resumeNotFoundAlt: - msg = 'Please '.guide + 'specify a valid input resume'.guide.bold + - ' in either FRESH or JSON Resume format.'.guide; - break; - - case HACKMYSTATUS.inputOutputParity: - msg = 'Please '.guide + 'specify an output file name'.guide.bold + - ' for every input file you wish to convert.'.guide; - break; - - case HACKMYSTATUS.createNameMissing: - msg = 'Please '.guide + 'specify the filename of the resume'.guide.bold + - ' to create.'.guide; - break; - - } - exitCode = ex.fluenterror; - - } - else { - msg = ex.toString(); - exitCode = 4; - } - - var idx = msg.indexOf('Error: '); - var trimmed = idx === -1 ? msg : msg.substring( idx + 7 ); - if( !ex.fluenterror || ex.fluenterror < 3 ) { // TODO: magic #s - console.log( ('ERROR: ' + trimmed.toString()).red.bold ); - console.log( ex.stack.gray); - } - else - console.log( trimmed.toString() ); - - process.exit( exitCode ); - -} diff --git a/test/test-themes.js b/test/test-themes.js index 8d195b34..5a6ee1d7 100644 --- a/test/test-themes.js +++ b/test/test-themes.js @@ -1,5 +1,6 @@ -var chai = require('chai') +var SPAWNWATCHER = require('../src/core/spawn-watch') + , chai = require('chai') , expect = chai.expect , should = chai.should() , path = require('path') @@ -64,7 +65,7 @@ describe('Testing themes', function () { genTheme('JRS', src, 'minimist'); genTheme('JRS', src, 'awesome'); genTheme('JRS', src, 'positive'); - genTheme('JRS', src, 'jsonresume-theme-boilerplate', 'node_modules/jsonresume-theme-boilerplate' ); + genTheme('JRS', src, 'jsonresume-theme-boilerplate', 'node_modules/jsonresume-theme-boilerplate' ); genTheme('JRS', src, 'jsonresume-theme-sceptile', 'node_modules/jsonresume-theme-sceptile' ); genTheme('JRS', src, 'jsonresume-theme-modern', 'node_modules/jsonresume-theme-modern' ); genTheme('JRS', src, 'jsonresume-theme-classy', 'node_modules/jsonresume-theme-classy' ); From 483207e5a084d41a9f3074edc7712c3c842855ea Mon Sep 17 00:00:00 2001 From: hacksalot Date: Tue, 29 Dec 2015 10:01:45 -0500 Subject: [PATCH 019/488] Improve Markdown support for JSON Resume themes. --- src/core/jrs-resume.js | 65 ++++++++++++++++++++++++++++++++++++++++++ src/verbs/generate.js | 25 +++++++++++++++- 2 files changed, 89 insertions(+), 1 deletion(-) diff --git a/src/core/jrs-resume.js b/src/core/jrs-resume.js index 619e83a4..49a8d86b 100644 --- a/src/core/jrs-resume.js +++ b/src/core/jrs-resume.js @@ -11,6 +11,7 @@ Definition of the JRSResume class. , validator = require('is-my-json-valid') , _ = require('underscore') , PATH = require('path') + , MD = require('marked') , moment = require('moment'); /** @@ -238,6 +239,70 @@ Definition of the JRSResume class. }; + 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 diff --git a/src/verbs/generate.js b/src/verbs/generate.js index 5c07ee97..2291f539 100644 --- a/src/verbs/generate.js +++ b/src/verbs/generate.js @@ -3,6 +3,7 @@ var PATH = require('path') , FS = require('fs') , parsePath = require('parse-filepath') + , MD = require('marked') , MKDIRP = require('mkdirp') , _opts = require('../core/default-options') , FluentTheme = require('../core/theme') @@ -102,6 +103,11 @@ "/foo/bar/resume.pdf" or "c:\foo\bar\resume.txt". */ function single( targInfo, theme ) { + + function MDIN(txt) { + return MD(txt || '' ).replace(/^\s*

|<\/p>\s*$/gi, ''); + } + try { var f = targInfo.file , fType = targInfo.fmt.outFormat @@ -144,7 +150,24 @@ // return PATH.join(p2, p1); // } }); - var rezHtml = theme.render( rez ); + + // Prevent JSON Resume theme .js from chattering + var consoleLog = console.log; + console.log = function() { }; + + // Call the theme's render method + var rezDupe = rez.harden(); + var rezHtml = theme.render( rezDupe ); + + // Turn logging back on + console.log = consoleLog; + + // Unharden + rezHtml = rezHtml.replace( /@@@@~.+?~@@@@/g, function(val){ + return MDIN( val.replace( /~@@@@/gm,'' ).replace( /@@@@~/gm,'' ) ); + }); + + // Save the file FS.writeFileSync( f, rezHtml ); } else { From b803eba934f1481d94299f6cdf691f1d86f2c558 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Tue, 29 Dec 2015 10:26:30 -0500 Subject: [PATCH 020/488] Scrub string.js. Will probably be retired in favor of Node reusables. --- src/utils/string.js | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/utils/string.js b/src/utils/string.js index 9da084fd..c5b759fb 100644 --- a/src/utils/string.js +++ b/src/utils/string.js @@ -1,6 +1,5 @@ /** Definitions of string utility functions. -@license MIT. Copyright (c) 2015 James Devlin / FluentDesk. @module string.js */ @@ -10,14 +9,18 @@ See: http://stackoverflow.com/a/32800728/4942583 @method isNullOrWhitespace */ -String.isNullOrWhitespace = function( input ) { - return !input || !input.trim(); -}; +(function() { -String.prototype.endsWith = function(suffix) { - return this.indexOf(suffix, this.length - suffix.length) !== -1; -}; + String.isNullOrWhitespace = function( input ) { + return !input || !input.trim(); + }; -String.is = function( val ) { - return typeof val === 'string' || val instanceof String; -}; + String.prototype.endsWith = function(suffix) { + return this.indexOf(suffix, this.length - suffix.length) !== -1; + }; + + String.is = function( val ) { + return typeof val === 'string' || val instanceof String; + }; + +}()); From 7593afa58630d9f0607615c3b53337d2bceffa59 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Tue, 29 Dec 2015 17:33:16 -0500 Subject: [PATCH 021/488] Adjust package.json versions. Relax to v1.3.0-beta and bump fluent-themes version. --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 6658c7ff..3786e9d5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hackmyresume", - "version": "1.3.0", + "version": "1.3.0-beta", "description": "Generate polished résumés and CVs in HTML, Markdown, LaTeX, MS Word, PDF, plain text, JSON, XML, YAML, smoke signal, and carrier pigeon.", "repository": { "type": "git", @@ -47,7 +47,7 @@ "dependencies": { "colors": "^1.1.2", "copy": "^0.1.3", - "fluent-themes": "~0.7.1-beta", + "fluent-themes": "~0.8.0-beta", "fresca": "~0.2.2", "fs-extra": "^0.24.0", "handlebars": "^4.0.5", From 0c570f8512e693441f33d49d355da890e0e740ab Mon Sep 17 00:00:00 2001 From: hacksalot Date: Tue, 29 Dec 2015 17:43:27 -0500 Subject: [PATCH 022/488] Update README. --- README.md | 95 ++++++++++++++++++++++++++++++++----------------------- 1 file changed, 55 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index 9e35161a..e58b0054 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ or Windows. - Support for multiple input and output resumes. - Use from your command line or [desktop][7]. - Free and open-source through the MIT license. +- Updated daily. ## Install @@ -42,8 +43,36 @@ Install HackMyResume with NPM: ``` Note: for PDF generation you'll need to install a copy of [wkhtmltopdf][3] for -your platform. For LaTeX generation you'll need a valid LaTeX environment with -access to `xelatex` and similar. +your platform. + +## Installing Themes + +HackMyResume supports both [FRESH][fresh-themes] and [JSON Resume][jrst]-style +résumé themes. + +- FRESH themes currently come preinstalled with HackMyResume. +- JSON Resume themes can be installed from NPM, GitHub, or manually. + +To install a JSON Resume theme, just `cd` to the folder where you want to store +your themes and run one of: + +```bash +# Install with NPM +npm install jsonresume-theme-[theme-name] + +# Install with GitHub +git clone https://github.com/[user-or-org]/[repo-name] +``` + +Then when you're ready to generate your resume, just reference the location of +the theme folder as you installed it: + +```bash +hackmyresume BUILD resume.json TO out/resume.all -t node_modules/jsonresume-theme-classy +``` + +Note: You can use install themes anywhere on your file system. You don't need a +package.json or other NPM/Node infrastructure. ## Getting Started @@ -98,9 +127,9 @@ Output Format | Ext | Notes ------------- | --- | ----- HTML | .html | A standard HTML 5 + CSS resume format that can be viewed in a browser, deployed to a website, etc. Markdown | .md | A structured Markdown document that can be used as-is or used to generate HTML. -LaTeX | .tex | A structured LaTeX document (or collection of documents). -MS Word | .doc | A Microsoft Word office document. -Adobe Acrobat (PDF) | .pdf | A binary PDF document driven by an HTML theme. +LaTeX | .tex | A structured LaTeX document (or collection of documents) that can be processed with pdflatex, xelatex, and similar tools. +MS Word | .doc | A Microsoft Word office document (XML-driven; WordProcessingML). +Adobe Acrobat (PDF) | .pdf | A binary PDF document driven by an HTML theme (through wkhtmltopdf). plain text | .txt | A formatted plain text document appropriate for emails or copy-paste. JSON | .json | A JSON representation of the resume. YAML | .yml | A YAML representation of the resume. @@ -108,15 +137,6 @@ RTF | .rtf | Forthcoming. Textile | .textile | Forthcoming. image | .png, .bmp | Forthcoming. -## Install - -HackMyResume requires a recent version of [Node.js][4] and [NPM][5]. Then: - -1. Install the latest official [wkhtmltopdf][3] binary for your platform. -2. Optionally install an updated LaTeX environment (LaTeX resumes only). -2. Install **HackMyResume** with `[sudo] npm install hackmyresume -g`. -3. You're ready to go. - ## Use Assuming you've got a JSON-formatted resume handy, generating resumes in @@ -132,19 +152,19 @@ theme (default to Modern). For example: ```bash # Generate all resume formats (HTML, PDF, DOC, TXT, YML, etc.) -hackmyresume build resume.json -o out/resume.all -t modern +hackmyresume BUILD resume.json TO out/resume.all -t modern # Generate a specific resume format -hackmyresume build resume.json TO out/resume.html -hackmyresume build resume.json TO out/resume.pdf -hackmyresume build resume.json TO out/resume.md -hackmyresume build resume.json TO out/resume.doc -hackmyresume build resume.json TO out/resume.json -hackmyresume build resume.json TO out/resume.txt -hackmyresume build resume.json TO out/resume.yml +hackmyresume BUILD resume.json TO out/resume.html +hackmyresume BUILD resume.json TO out/resume.pdf +hackmyresume BUILD resume.json TO out/resume.md +hackmyresume BUILD resume.json TO out/resume.doc +hackmyresume BUILD resume.json TO out/resume.json +hackmyresume BUILD resume.json TO out/resume.txt +hackmyresume BUILD resume.json TO out/resume.yml # Specify 2 inputs and 3 outputs -hackmyresume build in1.json in2.json TO out.html out.doc out.pdf +hackmyresume BUILD in1.json in2.json TO out.html out.doc out.pdf ``` You should see something to the effect of: @@ -171,8 +191,8 @@ For a predefined theme, include the theme name. For a custom theme, include the path to the custom theme's folder. ```bash -hackmyresume build resume.json -t modern -hackmyresume build resume.json -t ~/foo/bar/my-custom-theme/ +hackmyresume BUILD resume.json TO out/rez.all -t modern +hackmyresume BUILD resume.json TO OUT.rez.all -t ~/foo/bar/my-custom-theme/ ``` As of v1.0.0, available predefined themes are `positive`, `modern`, `compact`, @@ -185,7 +205,7 @@ most generic to most specific: ```bash # Merge specific.json onto base.json and generate all formats -hackmyresume build base.json specific.json -o resume.all +hackmyresume BUILD base.json specific.json TO resume.all ``` This can be useful for overriding a base (generic) resume with information from @@ -196,7 +216,7 @@ resume. Merging follows conventional [extend()][9]-style behavior and there's no arbitrary limit to how many resumes you can merge: ```bash -hackmyresume build in1.json in2.json in3.json in4.json TO out.html out.doc +hackmyresume BUILD in1.json in2.json in3.json in4.json TO out.html out.doc Reading JSON resume: in1.json Reading JSON resume: in2.json Reading JSON resume: in3.json @@ -212,14 +232,7 @@ You can specify **multiple output targets** and HackMyResume will build them: ```bash # Generate out1.doc, out1.pdf, and foo.txt from me.json. -hackmyresume build me.json -o out1.doc -o out1.pdf -o foo.txt -``` - -You can also omit the output file(s) and/or theme completely: - -```bash -# Equivalent to "hackmyresume resume.json resume.all -t modern" -hackmyresume build resume.json +hackmyresume BUILD me.json TO out1.doc out1.pdf foo.txt ``` ### Using .all @@ -229,7 +242,7 @@ formats for the given resume. For example, this... ```bash # Generate all resume formats (HTML, PDF, DOC, TXT, etc.) -hackmyresume build me.json -o out/resume.all +hackmyresume BUILD me.json TO out/resume.all ``` ..tells HackMyResume to read `me.json` and generate `out/resume.md`, @@ -244,7 +257,7 @@ resumes, use the `validate` command: ```bash # Validate myresume.json against either the FRESH or JSON Resume schema. -hackmyresume validate resumeA.json resumeB.json +hackmyresume VALIDATE resumeA.json resumeB.json ``` HackMyResume will validate each specified resume in turn: @@ -276,7 +289,7 @@ HTML-formatted resumes. To disable prettification, the `--nopretty` or `-n` flag can be used: ```bash -hackmyresume generate resume.json out.all --nopretty +hackmyresume BUILD resume.json out.all --nopretty ``` ### Silent Mode @@ -284,8 +297,8 @@ hackmyresume generate resume.json out.all --nopretty Use `-s` or `--silent` to run in silent mode: ```bash -hackmyresume generate resume.json -o someFile.all -s -hackmyresume generate resume.json -o someFile.all --silent +hackmyresume BUILD resume.json -o someFile.all -s +hackmyresume BUILD resume.json -o someFile.all --silent ``` ## Contributing @@ -314,3 +327,5 @@ MIT. Go crazy. See [LICENSE.md][1] for details. [travis-image]: https://img.shields.io/travis/palomajs/paloma.svg?style=flat-square [travis-url]: https://travis-ci.org/hacksalot/HackMyResume [contribute]: CONTRIBUTING.md +[fresh-themes]: https://github.com/fluentdesk/fluent-themes +[jrst]: https://www.npmjs.com/search?q=jsonresume-theme From 6280a18c14ba41e5ed0b13c30637627cc7e2dd1d Mon Sep 17 00:00:00 2001 From: Ya Zhuang Date: Wed, 30 Dec 2015 19:20:22 +0800 Subject: [PATCH 023/488] fix: missing extend method fix #68 --- src/verbs/generate.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/verbs/generate.js b/src/verbs/generate.js index d6629d29..0769abfb 100644 --- a/src/verbs/generate.js +++ b/src/verbs/generate.js @@ -8,6 +8,7 @@ , loadSourceResumes = require('../core/load-source-resumes') , _ = require('underscore') , _fmts = require('../core/default-formats') + , extend = require('../utils/extend') , _err, _log, rez; /** From b21fd93d66731db3fc5c19ce2a12b3734f4d4cf7 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Wed, 30 Dec 2015 12:08:46 -0500 Subject: [PATCH 024/488] Introduce JRSTheme class. Start splitting out logic into dedicated abstractions for both FRESH and JSON Resume themes given the different structure and use cases of each. --- src/core/jrs-theme.js | 70 ++++++++++++++++++++++++++++++++++++++++ src/core/status-codes.js | 4 ++- 2 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 src/core/jrs-theme.js diff --git a/src/core/jrs-theme.js b/src/core/jrs-theme.js new file mode 100644 index 00000000..26904c21 --- /dev/null +++ b/src/core/jrs-theme.js @@ -0,0 +1,70 @@ +/** +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. + */ + JRSTheme.prototype.open = function( themeFolder ) { + + this.folder = themeFolder; + + // Open the [theme-name].json file; should have the same name as folder + var pathInfo = parsePath( themeFolder ); + + // Open and parse the theme's package.json file. + var packageJsonPath = PATH.join(themeFolder, 'package.json'); + if( pathExists( packageJsonPath ) ) { + var themePack = require( themeFolder ); + var themePkgJson = require( packageJsonPath ); + this.name = themePkgJson.name; + this.render = (themePack && themePack.render) || undefined; + this.formats = { + html: { title: 'html', outFormat: 'html', ext: 'html' } + }; + } + else { + throw { fluenterror: 10 }; + } + + return this; + }; + + /** + Determine if the theme supports the specified output format. + */ + JRSTheme.prototype.hasFormat = function( fmt ) { + return _.has( this.formats, fmt ); + }; + + /** + Determine if the theme supports the specified output format. + */ + JRSTheme.prototype.getFormat = function( fmt ) { + return this.formats[ fmt ]; + }; + + module.exports = JRSTheme; + +}()); diff --git a/src/core/status-codes.js b/src/core/status-codes.js index d0467c77..400467f5 100644 --- a/src/core/status-codes.js +++ b/src/core/status-codes.js @@ -1,6 +1,7 @@ /** Status codes for HackMyResume. @module status-codes.js +@license MIT. See LICENSE.MD for details. */ (function(){ @@ -15,7 +16,8 @@ Status codes for HackMyResume. resumeNotFoundAlt: 6, inputOutputParity: 7, createNameMissing: 8, - wkhtmltopdf: 9 + wkhtmltopdf: 9, + missingPackageJSON: 10 }; }()); From 3e7d9c04112d613e6db2b0ce7f8e329f7dd9ea20 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Wed, 30 Dec 2015 12:37:26 -0500 Subject: [PATCH 025/488] Integrate JRSTheme class. --- src/core/{theme.js => fresh-theme.js} | 0 src/gen/template-generator.js | 23 ++++++++++++++++------- src/hackmyapi.js | 3 ++- src/verbs/generate.js | 6 +++++- 4 files changed, 23 insertions(+), 9 deletions(-) rename src/core/{theme.js => fresh-theme.js} (100%) diff --git a/src/core/theme.js b/src/core/fresh-theme.js similarity index 100% rename from src/core/theme.js rename to src/core/fresh-theme.js diff --git a/src/gen/template-generator.js b/src/gen/template-generator.js index 60a7a6f3..20c3ea62 100644 --- a/src/gen/template-generator.js +++ b/src/gen/template-generator.js @@ -17,7 +17,8 @@ Definition of the TemplateGenerator class. , MKDIRP = require('mkdirp') , BaseGenerator = require( './base-generator' ) , EXTEND = require('../utils/extend') - , Theme = require('../core/theme'); + , FRESHTheme = require('../core/fresh-theme') + , JRSTheme = require('../core/jrs-theme'); @@ -220,21 +221,29 @@ Definition of the TemplateGenerator class. Given a theme title, load the corresponding theme. */ function themeFromMoniker() { + // Verify the specified theme name/path var tFolder = PATH.join( parsePath( require.resolve('fluent-themes') ).dirname, this.opts.theme ); - var exists = require('path-exists').sync; - if( !exists( tFolder ) ) { - tFolder = PATH.resolve( this.opts.theme ); + + var t; + if( this.opts.theme.startsWith('jsonresume-theme-') ) { + console.log('LOADING JSON RESUME'); + t = new JRSTheme().open( tFolder ); + } + else { + var exists = require('path-exists').sync; if( !exists( tFolder ) ) { - throw { fluenterror: this.codes.themeNotFound, data: this.opts.theme}; + tFolder = PATH.resolve( this.opts.theme ); + if( !exists( tFolder ) ) { + throw { fluenterror: this.codes.themeNotFound, data: this.opts.theme}; + } } + t = this.opts.themeObj || new FRESHTheme().open( tFolder ); } - var t = this.opts.themeObj || new Theme().open( tFolder ); - // Load the theme and format return { theme: t, diff --git a/src/hackmyapi.js b/src/hackmyapi.js index f36c40ab..a1180283 100644 --- a/src/hackmyapi.js +++ b/src/hackmyapi.js @@ -8,7 +8,8 @@ module.exports = { Sheet: require('./core/fresh-resume'), FRESHResume: require('./core/fresh-resume'), JRSResume: require('./core/jrs-resume'), - Theme: require('./core/theme'), + FRESHTheme: require('./core/fresh-theme'), + JRSTheme: require('./core/jrs-theme'), FluentDate: require('./core/fluent-date'), HtmlGenerator: require('./gen/html-generator'), TextGenerator: require('./gen/text-generator'), diff --git a/src/verbs/generate.js b/src/verbs/generate.js index 2291f539..344a9bad 100644 --- a/src/verbs/generate.js +++ b/src/verbs/generate.js @@ -6,10 +6,12 @@ , MD = require('marked') , MKDIRP = require('mkdirp') , _opts = require('../core/default-options') - , FluentTheme = require('../core/theme') + , FluentTheme = require('../core/fresh-theme') + , JRSTheme = require('../core/jrs-theme') , ResumeFactory = require('../core/resume-factory') , _ = require('underscore') , _fmts = require('../core/default-formats') + , unused = require('string.prototype.startswith') , _err, _log, rez; /** @@ -51,6 +53,8 @@ // Load the theme var theTheme = (new FluentTheme()).open( tFolder ); + var theTheme = _opts.theme.startsWith('jsonresume-theme') ? + new JRSTheme().open(tFolder) : new FluentTheme().open( tFolder ); _opts.themeObj = theTheme; var numFormats = theTheme.formats ? Object.keys(theTheme.formats).length : 2; _log( 'Applying '.info + theTheme.name.toUpperCase().infoBold + From 3c7868a750102c3475d392ee9a7166de6f9ff6dd Mon Sep 17 00:00:00 2001 From: hacksalot Date: Wed, 30 Dec 2015 12:38:01 -0500 Subject: [PATCH 026/488] Scrub. --- src/gen/base-generator.js | 2 +- src/verbs/generate.js | 22 ++++++++++++---------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/gen/base-generator.js b/src/gen/base-generator.js index 5d117775..b8a9538f 100644 --- a/src/gen/base-generator.js +++ b/src/gen/base-generator.js @@ -1,7 +1,7 @@ /** Definition of the BaseGenerator class. -@license MIT. Copyright (c) 2015 James Devlin / FluentDesk. @module base-generator.js +@license MIT. See LICENSE.md for details. */ (function() { diff --git a/src/verbs/generate.js b/src/verbs/generate.js index 344a9bad..f9f1a763 100644 --- a/src/verbs/generate.js +++ b/src/verbs/generate.js @@ -24,8 +24,8 @@ module.exports = /** - Given a source JSON resume, a destination resume path, and a theme file, - generate 0..N resumes in the desired formats. + Given a source resume in FRESH or JRS format, a destination resume path, and a + theme file, generate 0..N resumes in the desired formats. @param src Path to the source JSON resume file: "rez/resume.json". @param dst An array of paths to the target resume file(s). @param theme Friendly name of the resume theme. Defaults to "modern". @@ -52,7 +52,6 @@ } // Load the theme - var theTheme = (new FluentTheme()).open( tFolder ); var theTheme = _opts.theme.startsWith('jsonresume-theme') ? new JRSTheme().open(tFolder) : new FluentTheme().open( tFolder ); _opts.themeObj = theTheme; @@ -118,8 +117,8 @@ , fName = PATH.basename(f, '.' + fType) , theFormat; - // If targInfo.fmt.files exists, this theme has an explicit "files" - // section in its theme.json file. + // If targInfo.fmt.files exists, this format is backed by a document. + // Fluent/FRESH themes are handled here. if( targInfo.fmt.files && targInfo.fmt.files.length ) { _log( 'Generating '.useful + @@ -131,19 +130,22 @@ MKDIRP.sync( PATH.dirname( f ) ); // Ensure dest folder exists; theFormat.gen.generate( rez, f, _opts ); } - // Otherwise the theme has no files section + + // Otherwise this is either a) a JSON Resume theme or b) an ad-hoc format + // (JSON, YML, or PNG) that every theme gets "for free". else { _log( 'Generating '.useful + targInfo.fmt.outFormat.toUpperCase().useful.bold + ' resume: '.useful + PATH.relative(process.cwd(), f ).replace(/\\/g,'/').useful.bold); - theFormat = _fmts.filter( - function(fmt) { return fmt.name === targInfo.fmt.outFormat; })[0]; + theFormat = _fmts.filter( function(fmt) { + return fmt.name === targInfo.fmt.outFormat; + })[0]; var outFolder = PATH.dirname( f ); MKDIRP.sync( outFolder ); // Ensure dest folder exists; - // TODO: refactor + // JSON Resume themes have a 'render' method that needs to be called if( theme.render ) { var COPY = require('copy'); var globs = [ /*'**',*/ '*.css', '*.js', '*.png', '*.jpg', '*.gif', '*.bmp' ]; @@ -155,7 +157,7 @@ // } }); - // Prevent JSON Resume theme .js from chattering + // Prevent JSON Resume theme .js from chattering (TODO: redirect IO) var consoleLog = console.log; console.log = function() { }; From a0c356941c20513db7808ca410d39a823194577b Mon Sep 17 00:00:00 2001 From: hacksalot Date: Wed, 30 Dec 2015 12:44:16 -0500 Subject: [PATCH 027/488] Remove unnecessary line. --- src/index.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/index.js b/src/index.js index 48d25031..c48d9604 100644 --- a/src/index.js +++ b/src/index.js @@ -74,10 +74,9 @@ function main() { var src = a._.slice(1, splitAt === -1 ? undefined : splitAt ); var dst = splitAt === -1 ? [] : a._.slice( splitAt + 1 ); ( splitAt === -1 ) && src.length > 1 && dst.push( src.pop() ); // Allow omitting TO keyword - var parms = [ src, dst, opts, logMsg ]; // Invoke the action - FCMD.verbs[ verb ].apply( null, parms ); + FCMD.verbs[ verb ].apply( null, [ src, dst, opts, logMsg ] ); } From e3cb949992561b1043452e792c9629ae10a25204 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Wed, 30 Dec 2015 12:59:21 -0500 Subject: [PATCH 028/488] Fix: Exception when HMR is run without params. --- src/core/error-handler.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/core/error-handler.js b/src/core/error-handler.js index 53aa014e..1bbc8ced 100644 --- a/src/core/error-handler.js +++ b/src/core/error-handler.js @@ -6,6 +6,9 @@ var HACKMYSTATUS = require('./status-codes') , PKG = require('../../package.json') + , FS = require('fs') + , FCMD = require('../hackmycmd') + , PATH = require('path') , title = ('\n*** HackMyResume v' + PKG.version + ' ***').bold.white; var ErrorHandler = module.exports = { @@ -36,7 +39,7 @@ return (idx === ar.length - 1 ? 'or '.guide : '') + v.toUpperCase().guide; }).join(', '.guide) + ").\n\n".guide + - FS.readFileSync( PATH.join(__dirname, 'use.txt'), 'utf8' ).info.bold; + FS.readFileSync( PATH.resolve(__dirname, '../use.txt'), 'utf8' ).info.bold; break; case HACKMYSTATUS.invalidCommand: From 1bcc2f7d0cc0eb8d3ee204b9498eeaa7af6363aa Mon Sep 17 00:00:00 2001 From: hacksalot Date: Wed, 30 Dec 2015 13:00:30 -0500 Subject: [PATCH 029/488] Add formal support for aliases. new/create and build/generate --- src/hackmycmd.js | 20 ++++++++++++-------- src/index.js | 4 ++-- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/hackmycmd.js b/src/hackmycmd.js index 0082f8ed..e4449da2 100644 --- a/src/hackmycmd.js +++ b/src/hackmycmd.js @@ -22,15 +22,19 @@ Internal resume generation logic for HackMyResume. /** Internal module interface. Used by FCV Desktop and HMR. */ + var v = { + build: require('./verbs/generate'), + validate: require('./verbs/validate'), + convert: require('./verbs/convert'), + new: require('./verbs/create'), + help: help + } + return { - verbs: { - generate: require('./verbs/generate'), - build: require('./verbs/generate'), - validate: require('./verbs/validate'), - convert: require('./verbs/convert'), - create: require('./verbs/create'), - new: require('./verbs/create'), - help: help + verbs: v, + alias: { + generate: v.build, + create: v.build }, lib: require('./hackmyapi'), options: require('./core/default-options'), diff --git a/src/index.js b/src/index.js index c48d9604..d2f99cfa 100644 --- a/src/index.js +++ b/src/index.js @@ -55,7 +55,7 @@ function main() { // Get the action to be performed var params = a._.map( function(p){ return p.toLowerCase().trim(); }); var verb = params[0]; - if( !FCMD.verbs[ verb ] ) { + if( !FCMD.verbs[ verb ] && !FCMD.alias[ verb ] ) { logMsg('Invalid command: "'.warn + verb.warn.bold + '"'.warn); return; } @@ -76,7 +76,7 @@ function main() { ( splitAt === -1 ) && src.length > 1 && dst.push( src.pop() ); // Allow omitting TO keyword // Invoke the action - FCMD.verbs[ verb ].apply( null, [ src, dst, opts, logMsg ] ); + (FCMD.verbs[verb] || FCMD.alias[verb]).apply(null, [src, dst, opts, logMsg]); } From 7a1eadb3fc21a0ad822757fde62fec666a4fbbb3 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Wed, 30 Dec 2015 13:12:51 -0500 Subject: [PATCH 030/488] Tweak error messages. Stay away from language like "please specify a valid input resume". The fluentcv fork can use corporate-speak. HackMyResume is more like a gremlin -- feed it, but never after midnight. --- src/core/error-handler.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/core/error-handler.js b/src/core/error-handler.js index 1bbc8ced..a2ade774 100644 --- a/src/core/error-handler.js +++ b/src/core/error-handler.js @@ -29,17 +29,21 @@ break; case HACKMYSTATUS.resumeNotFound: - msg = 'Please '.guide + 'specify a valid input resume'.guide.bold + + msg = 'Please '.guide + 'feed me a resume'.guide.bold + ' in FRESH or JSON Resume format.'.guide; break; case HACKMYSTATUS.missingCommand: - msg = title + "\nPlease ".guide + "specify a command".guide.bold + " (".guide + - Object.keys( FCMD.verbs ).map( function(v, idx, ar) { + msg = title + "\nPlease ".guide + "give me a command".guide.bold + + " (".guide; + + msg += Object.keys( FCMD.verbs ).map( function(v, idx, ar) { return (idx === ar.length - 1 ? 'or '.guide : '') + v.toUpperCase().guide; - }).join(', '.guide) + ").\n\n".guide + - FS.readFileSync( PATH.resolve(__dirname, '../use.txt'), 'utf8' ).info.bold; + }).join(', '.guide) + ").\n\n".guide; + + msg += FS.readFileSync( + PATH.resolve(__dirname, '../use.txt'), 'utf8' ).info.bold; break; case HACKMYSTATUS.invalidCommand: @@ -48,7 +52,7 @@ break; case HACKMYSTATUS.resumeNotFoundAlt: - msg = 'Please '.guide + 'specify a valid input resume'.guide.bold + + msg = 'Please '.guide + 'feed me a resume'.guide.bold + ' in either FRESH or JSON Resume format.'.guide; break; From 39d61c66b92a9fac1b4e4790923cac40a16155b3 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Wed, 30 Dec 2015 13:22:18 -0500 Subject: [PATCH 031/488] Finish Theme --> FreshTheme rename. --- src/core/fresh-theme.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/core/fresh-theme.js b/src/core/fresh-theme.js index b9fe152d..1d8fdc27 100644 --- a/src/core/fresh-theme.js +++ b/src/core/fresh-theme.js @@ -1,7 +1,7 @@ /** -Definition of the Theme class. -@license MIT. Copyright (c) 2015 hacksalot / FluentDesk. -@module theme.js +Definition of the FRESHTheme class. +@module fresh-theme.js +@license MIT. See LICENSE.md for details. */ (function() { @@ -18,17 +18,17 @@ Definition of the Theme class. , RECURSIVE_READ_DIR = require('recursive-readdir-sync'); /** - The Theme class is a representation of a HackMyResume theme asset. - @class Theme + The FRESHTheme class is a representation of a HackMyResume theme asset. + @class FRESHTheme */ - function Theme() { + function FRESHTheme() { } /** Open and parse the specified theme. */ - Theme.prototype.open = function( themeFolder ) { + FRESHTheme.prototype.open = function( themeFolder ) { this.folder = themeFolder; @@ -83,14 +83,14 @@ Definition of the Theme class. /** Determine if the theme supports the specified output format. */ - Theme.prototype.hasFormat = function( fmt ) { + FRESHTheme.prototype.hasFormat = function( fmt ) { return _.has( this.formats, fmt ); }; /** Determine if the theme supports the specified output format. */ - Theme.prototype.getFormat = function( fmt ) { + FRESHTheme.prototype.getFormat = function( fmt ) { return this.formats[ fmt ]; }; @@ -285,6 +285,6 @@ Definition of the Theme class. return friendly[val] || val; } - module.exports = Theme; + module.exports = FRESHTheme; }()); From 97c9ba08d002bed74322a2a124dc3a097d443fd5 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Wed, 30 Dec 2015 14:00:09 -0500 Subject: [PATCH 032/488] Fix: Broken HELP command. --- src/hackmycmd.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/hackmycmd.js b/src/hackmycmd.js index e4449da2..ad62e4e7 100644 --- a/src/hackmycmd.js +++ b/src/hackmycmd.js @@ -8,15 +8,16 @@ Internal resume generation logic for HackMyResume. module.exports = function () { var unused = require('./utils/string') - , PATH = require('path'); + , PATH = require('path') + , FS = require('fs'); /** Display help documentation. */ function help() { - console.log( FS.readFileSync( PATH.join(__dirname, 'use.txt'), 'utf8' ) - .useful.bold ); + var manPage = FS.readFileSync( PATH.join(__dirname, 'use.txt'), 'utf8' ); + console.log( manPage.useful.bold ); } /** @@ -28,7 +29,7 @@ Internal resume generation logic for HackMyResume. convert: require('./verbs/convert'), new: require('./verbs/create'), help: help - } + }; return { verbs: v, From 5e51beddf70d205aa25ca12ed0bbee774626e0b0 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Wed, 30 Dec 2015 14:48:22 -0500 Subject: [PATCH 033/488] Refactor. --- src/gen/template-generator.js | 13 ++- src/verbs/generate.js | 161 ++++++++++++++++++++++------------ 2 files changed, 115 insertions(+), 59 deletions(-) diff --git a/src/gen/template-generator.js b/src/gen/template-generator.js index 20c3ea62..8d3629c5 100644 --- a/src/gen/template-generator.js +++ b/src/gen/template-generator.js @@ -1,6 +1,6 @@ /** Definition of the TemplateGenerator class. -@license MIT. Copyright (c) 2015 James Devlin / FluentDesk. +@license MIT. See LICENSE.md for details. @module template-generator.js */ @@ -78,7 +78,14 @@ Definition of the TemplateGenerator class. @method invoke @param rez A FreshResume object. @param opts Generator options. - @returns An array of strings representing generated output files. + @returns An array of objects representing the generated output files. Each + object has this format: + + { + files: [ { info: { }, data: [ ] }, { ... } ], + themeInfo: { } + } + */ invoke: function( rez, opts ) { @@ -183,6 +190,8 @@ Definition of the TemplateGenerator class. }); } + return genInfo; + }, diff --git a/src/verbs/generate.js b/src/verbs/generate.js index f9f1a763..276fdf45 100644 --- a/src/verbs/generate.js +++ b/src/verbs/generate.js @@ -1,19 +1,27 @@ +/** +Implementation of the 'generate' verb for HackMyResume. +@module generate.js +@license MIT. See LICENSE.md for details. +*/ + (function() { var PATH = require('path') , FS = require('fs') - , parsePath = require('parse-filepath') , MD = require('marked') , MKDIRP = require('mkdirp') + , EXTEND = require('../utils/extend') + , parsePath = require('parse-filepath') , _opts = require('../core/default-options') , FluentTheme = require('../core/fresh-theme') , JRSTheme = require('../core/jrs-theme') , ResumeFactory = require('../core/resume-factory') , _ = require('underscore') , _fmts = require('../core/default-formats') - , unused = require('string.prototype.startswith') , _err, _log, rez; + + /** Handle an exception. */ @@ -21,7 +29,7 @@ throw ex; } - module.exports = + /** Given a source resume in FRESH or JRS format, a destination resume path, and a @@ -31,33 +39,18 @@ @param theme Friendly name of the resume theme. Defaults to "modern". @param logger Optional logging override. */ - function generate( src, dst, opts, logger, errHandler ) { + function build( src, dst, opts, logger, errHandler ) { + // Housekeeping... _log = logger || console.log; _err = errHandler || error; - //_opts = extend( true, _opts, opts ); _opts.theme = (opts.theme && opts.theme.toLowerCase().trim())|| 'modern'; _opts.prettify = opts.prettify === true ? _opts.prettify : false; - // Verify the specified theme name/path - var relativeThemeFolder = '../../node_modules/fluent-themes/themes'; - var tFolder = PATH.resolve( __dirname, relativeThemeFolder, _opts.theme); - var exists = require('path-exists').sync; - if( !exists( tFolder ) ) { - tFolder = PATH.resolve( _opts.theme ); - if (!exists( tFolder )) { - throw { fluenterror: 1, data: _opts.theme }; - } - } - - // Load the theme - var theTheme = _opts.theme.startsWith('jsonresume-theme') ? - new JRSTheme().open(tFolder) : new FluentTheme().open( tFolder ); - _opts.themeObj = theTheme; - var numFormats = theTheme.formats ? Object.keys(theTheme.formats).length : 2; - _log( 'Applying '.info + theTheme.name.toUpperCase().infoBold + - (' theme (' + numFormats + ' formats)').info); + // Load the theme... + var tFolder = verify_theme( _opts.theme ); + var theTheme = load_theme( tFolder ); // Load input resumes... if( !src || !src.length ) { throw { fluenterror: 3 }; } @@ -73,41 +66,28 @@ }); msg && _log(msg); - // Expand output resumes... (can't use map() here) - var targets = [], that = this; - ( (dst && dst.length && dst) || ['resume.all'] ).forEach( function(t) { - - var to = PATH.resolve(t), - pa = parsePath(to), - fmat = pa.extname || '.all'; - - targets.push.apply( - targets, fmat === '.all' ? - - Object.keys( theTheme.formats ).map(function(k){ - var z = theTheme.formats[k]; - return { file: to.replace(/all$/g,z.outFormat), fmt: z }; - }) : - - [{ file: to, fmt: theTheme.getFormat( fmat.slice(1) ) }]); - - }); + // Expand output resumes... + var targets = expand( dst, theTheme ); // Run the transformation! - var finished = targets.map( function(t) { return single(t, theTheme); }); + var finished = targets.map( function(t) { + return EXTEND(true, t, { markup: single(t, theTheme) }); + }); // Don't send the client back empty-handed return { sheet: rez, targets: targets, processed: finished }; - }; + } + + /** - Generate a single resume of a specific format. - @param f Full path to the destination resume to generate, for example, - "/foo/bar/resume.pdf" or "c:\foo\bar\resume.txt". + Generate a single resume of a specific format. TODO: Refactor. + @param targInfo Information for the target resume. + @param theme A FRESHTheme or JRSTheme object. */ function single( targInfo, theme ) { - function MDIN(txt) { + function MDIN(txt) { // TODO: Move this return MD(txt || '' ).replace(/^\s*

|<\/p>\s*$/gi, ''); } @@ -117,14 +97,13 @@ , fName = PATH.basename(f, '.' + fType) , theFormat; - // If targInfo.fmt.files exists, this format is backed by a document. - // Fluent/FRESH themes are handled here. - if( targInfo.fmt.files && targInfo.fmt.files.length ) { - _log( 'Generating '.useful + targInfo.fmt.outFormat.toUpperCase().useful.bold + - ' resume: '.useful + PATH.relative(process.cwd(), f ).replace(/\\/g,'/').useful.bold); + ' resume: '.useful + PATH.relative(process.cwd(), f ).useful.bold ); + // If targInfo.fmt.files exists, this format is backed by a document. + // Fluent/FRESH themes are handled here. + if( targInfo.fmt.files && targInfo.fmt.files.length ) { theFormat = _fmts.filter( function(fmt) { return fmt.name === targInfo.fmt.outFormat; })[0]; MKDIRP.sync( PATH.dirname( f ) ); // Ensure dest folder exists; @@ -134,9 +113,6 @@ // Otherwise this is either a) a JSON Resume theme or b) an ad-hoc format // (JSON, YML, or PNG) that every theme gets "for free". else { - _log( 'Generating '.useful + - targInfo.fmt.outFormat.toUpperCase().useful.bold + - ' resume: '.useful + PATH.relative(process.cwd(), f ).replace(/\\/g,'/').useful.bold); theFormat = _fmts.filter( function(fmt) { return fmt.name === targInfo.fmt.outFormat; @@ -175,9 +151,12 @@ // Save the file FS.writeFileSync( f, rezHtml ); + + // Return markup to the client + return rezHtml; } else { - theFormat.gen.generate( rez, f, _opts ); + return theFormat.gen.generate( rez, f, _opts ); } } } @@ -186,4 +165,72 @@ } } + + /** + Expand output files. + */ + function expand( dst, theTheme ) { + var targets = []; + // (can't use map() here). + ( (dst && dst.length && dst) || ['resume.all'] ).forEach( function(t) { + + var to = PATH.resolve(t), + pa = parsePath(to), + fmat = pa.extname || '.all'; + + targets.push.apply( + targets, fmat === '.all' ? + + Object.keys( theTheme.formats ).map(function(k){ + var z = theTheme.formats[k]; + return { file: to.replace(/all$/g,z.outFormat), fmt: z }; + }) : + + [{ file: to, fmt: theTheme.getFormat( fmat.slice(1) ) }]); + + }); + return targets; + } + + + /** + Verify the specified theme name/path. + */ + function verify_theme( themeNameOrPath ) { + var tFolder = PATH.resolve( + __dirname, + '../../node_modules/fluent-themes/themes', + themeNameOrPath + ); + var exists = require('path-exists').sync; + if( !exists( tFolder ) ) { + tFolder = PATH.resolve( themeNameOrPath ); + if( !exists( tFolder ) ) { + throw { fluenterror: 1, data: _opts.theme }; + } + } + return tFolder; + } + + + + /** + Load the specified theme. + */ + function load_theme( tFolder ) { + var theTheme = _opts.theme.indexOf('jsonresume-theme-') > -1 ? + new JRSTheme().open(tFolder) : new FluentTheme().open( tFolder ); + _opts.themeObj = theTheme; + var numFormats = Object.keys(theTheme.formats).length; + _log( 'Applying '.info + theTheme.name.toUpperCase().infoBold + + (' theme (' + numFormats + ' formats)').info); + return theTheme; + } + + + + module.exports = build; + + + }()); From ccadb0416f8d82fbfe8e117acfd4a125494adf4c Mon Sep 17 00:00:00 2001 From: hacksalot Date: Wed, 30 Dec 2015 15:03:26 -0500 Subject: [PATCH 034/488] Move freebie formats out of theme class. --- src/core/fresh-theme.js | 4 ---- src/verbs/generate.js | 16 ++++++++++++++++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/core/fresh-theme.js b/src/core/fresh-theme.js index 1d8fdc27..bf2b414b 100644 --- a/src/core/fresh-theme.js +++ b/src/core/fresh-theme.js @@ -67,10 +67,6 @@ Definition of the FRESHTheme class. formatsHash = loadImplicit.call( this ); } - // Add freebie formats every theme gets - formatsHash.json = { title: 'json', outFormat: 'json', pre: 'json', ext: 'json', path: null, data: null }; - formatsHash.yml = { title: 'yaml', outFormat: 'yml', pre: 'yml', ext: 'yml', path: null, data: null }; - // Cache this.formats = formatsHash; diff --git a/src/verbs/generate.js b/src/verbs/generate.js index 276fdf45..c0cd8da7 100644 --- a/src/verbs/generate.js +++ b/src/verbs/generate.js @@ -218,9 +218,25 @@ Implementation of the 'generate' verb for HackMyResume. Load the specified theme. */ function load_theme( tFolder ) { + + // Create a FRESH or JRS theme object var theTheme = _opts.theme.indexOf('jsonresume-theme-') > -1 ? new JRSTheme().open(tFolder) : new FluentTheme().open( tFolder ); + + // Add freebie formats every theme gets + theTheme.formats.json = theTheme.formats.json || { + title: 'json', outFormat: 'json', pre: 'json', + ext: 'json', path: null, data: null + }; + theTheme.formats.yml = theTheme.formats.yml || { + title: 'yaml', outFormat: 'yml', pre: 'yml', + ext: 'yml', path: null, data: null + }; + + // Cache the theme object _opts.themeObj = theTheme; + + // Output a message TODO: core should not log var numFormats = Object.keys(theTheme.formats).length; _log( 'Applying '.info + theTheme.name.toUpperCase().infoBold + (' theme (' + numFormats + ' formats)').info); From 2ff912e687d7f446b1c506d72c677a000cebee25 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Wed, 30 Dec 2015 15:11:18 -0500 Subject: [PATCH 035/488] Scrub. --- src/core/jrs-theme.js | 43 +++++++++++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/src/core/jrs-theme.js b/src/core/jrs-theme.js index 26904c21..ccf3300f 100644 --- a/src/core/jrs-theme.js +++ b/src/core/jrs-theme.js @@ -16,32 +16,37 @@ Definition of the JRSTheme class. /** - The JRSTheme class is a representation of a JSON Resume theme asset. + 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( themeFolder ) { + JRSTheme.prototype.open = function( thFolder ) { - this.folder = themeFolder; + this.folder = thFolder; - // Open the [theme-name].json file; should have the same name as folder - var pathInfo = parsePath( themeFolder ); + // 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 packageJsonPath = PATH.join(themeFolder, 'package.json'); - if( pathExists( packageJsonPath ) ) { - var themePack = require( themeFolder ); - var themePkgJson = require( packageJsonPath ); - this.name = themePkgJson.name; - this.render = (themePack && themePack.render) || undefined; + 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.formats = { - html: { title: 'html', outFormat: 'html', ext: 'html' } + html: { title:'html', outFormat:'html', ext:'html' } }; } else { @@ -51,20 +56,30 @@ Definition of the JRSTheme class. return this; }; + + /** - Determine if the theme supports the specified output format. + Determine if the theme supports the output format. + @method hasFormat */ JRSTheme.prototype.hasFormat = function( fmt ) { return _.has( this.formats, fmt ); }; + + /** - Determine if the theme supports the specified output format. + Return the requested output format. + @method getFormat */ JRSTheme.prototype.getFormat = function( fmt ) { return this.formats[ fmt ]; }; + + module.exports = JRSTheme; + + }()); From 72de1bbd335cb2176eb7f808bf22dc04af47a5f1 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Wed, 30 Dec 2015 15:21:58 -0500 Subject: [PATCH 036/488] Scrub. --- src/core/fresh-theme.js | 91 +++++++++++++++++++++++++++-------------- src/core/jrs-theme.js | 2 +- 2 files changed, 61 insertions(+), 32 deletions(-) diff --git a/src/core/fresh-theme.js b/src/core/fresh-theme.js index bf2b414b..bbb816c1 100644 --- a/src/core/fresh-theme.js +++ b/src/core/fresh-theme.js @@ -6,6 +6,8 @@ Definition of the FRESHTheme class. (function() { + + var FS = require('fs') , extend = require('../utils/extend') , validator = require('is-my-json-valid') @@ -15,16 +17,21 @@ Definition of the FRESHTheme class. , pathExists = require('path-exists').sync , EXTEND = require('../utils/extend') , moment = require('moment') - , RECURSIVE_READ_DIR = require('recursive-readdir-sync'); + , READFILES = require('recursive-readdir-sync'); + + /** - The FRESHTheme class is a representation of a HackMyResume theme asset. + The FRESHTheme class is a representation of a FRESH theme + asset. See also: JRSTheme. @class FRESHTheme */ function FRESHTheme() { } + + /** Open and parse the specified theme. */ @@ -38,18 +45,7 @@ Definition of the FRESHTheme class. // Set up a formats hash for the theme var formatsHash = { }; - // See if the theme has a package.json. If so, load it. - var packageJsonPath = PATH.join(themeFolder, 'package.json'); - if( pathExists( packageJsonPath ) ) { - var themePack = require( themeFolder ); - var themePkgJson = require( packageJsonPath ); - this.name = themePkgJson.name; - this.render = (themePack && themePack.render) || undefined; - this.formats = { html: { title: 'html', outFormat: 'html', ext: 'html', path: null, data: null } }; - return this; - } - - // Otherwise, do a full theme load + // Load the theme var themeFile = PATH.join( themeFolder, pathInfo.basename + '.json' ); var themeInfo = JSON.parse( FS.readFileSync( themeFile, 'utf8' ) ); var that = this; @@ -76,6 +72,8 @@ Definition of the FRESHTheme class. return this; }; + + /** Determine if the theme supports the specified output format. */ @@ -83,6 +81,8 @@ Definition of the FRESHTheme class. return _.has( this.formats, fmt ); }; + + /** Determine if the theme supports the specified output format. */ @@ -90,6 +90,11 @@ Definition of the FRESHTheme class. return this.formats[ fmt ]; }; + + /** + Load the theme implicitly, by scanning the theme folder for + files. TODO: Refactor duplicated code with loadExplicit. + */ function loadImplicit() { // Set up a hash of formats supported by this theme. @@ -103,7 +108,7 @@ Definition of the FRESHTheme class. // 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 = RECURSIVE_READ_DIR( tplFolder ).map( function( absPath ) { + 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 @@ -131,7 +136,7 @@ Definition of the FRESHTheme class. // 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 ); + outFmt = (idx === -1) ? pathInfo.name : pathInfo.name.substr(idx + 1); isMajor = true; } @@ -161,9 +166,13 @@ Definition of the FRESHTheme class. }); // Now, get all the CSS files... - (this.cssFiles = fmts.filter(function( fmt ){ return fmt && (fmt.ext === 'css'); })) + (this.cssFiles = fmts.filter(function( fmt ){ + return fmt && (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 && fmt.pre === cssf.pre && fmt.ext === 'html'; }); @@ -180,16 +189,19 @@ Definition of the FRESHTheme class. return formatsHash; } + + + /** + Load the theme explicitly, by following the 'formats' hash + in the theme's JSON settings file. + */ function loadExplicit() { - var that = this; - // Set up a hash of formats supported by this theme. + // Housekeeping var formatsHash = { }; - - // Establish the base theme folder 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 @@ -200,13 +212,16 @@ Definition of the FRESHTheme class. // 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; + 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'; } @@ -227,7 +242,7 @@ Definition of the FRESHTheme class. // 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 ); + outFmt = (idx === -1) ? pathInfo.name : pathInfo.name.substr(idx + 1); } // We should have a valid output format now. @@ -257,7 +272,11 @@ Definition of the FRESHTheme class. }); // Now, get all the CSS files... - (this.cssFiles = fmts.filter(function( fmt ){ return fmt.ext === 'css'; })) + (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 ) { @@ -275,12 +294,22 @@ Definition of the FRESHTheme class. 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-theme.js b/src/core/jrs-theme.js index ccf3300f..4f2942a9 100644 --- a/src/core/jrs-theme.js +++ b/src/core/jrs-theme.js @@ -17,7 +17,7 @@ Definition of the JRSTheme class. /** The JRSTheme class is a representation of a JSON Resume - theme asset. + theme asset. See also: FRESHTheme. @class JRSTheme */ function JRSTheme() { From 22554c61c5b749817acfd00b9dde9772e5b88f8d Mon Sep 17 00:00:00 2001 From: hacksalot Date: Wed, 30 Dec 2015 18:18:11 -0500 Subject: [PATCH 037/488] Rename and bump fluent-themes dependency. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8eb6ba22..b3719d66 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "dependencies": { "colors": "^1.1.2", "copy": "^0.1.3", - "fluent-themes": "~0.8.0-beta", + "fresh-themes": "~0.9.0-beta", "fresca": "~0.2.2", "fs-extra": "^0.24.0", "handlebars": "^4.0.5", From d4e0a0fa055c583e3a1f1b14582ddd23cce32df7 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Wed, 30 Dec 2015 18:19:00 -0500 Subject: [PATCH 038/488] Add {{styleSheet}} helper (placeholder). --- src/eng/generic-helpers.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/eng/generic-helpers.js b/src/eng/generic-helpers.js index ab9c1f68..26c03dcd 100644 --- a/src/eng/generic-helpers.js +++ b/src/eng/generic-helpers.js @@ -1,6 +1,6 @@ /** -Generic template helper definitions for FluentCV. -@license MIT. Copyright (c) 2015 James Devlin / FluentDesk. +Generic template helper definitions for HackMyResume / FluentCV. +@license MIT. See LICENSE.md for details. @module generic-helpers.js */ @@ -114,6 +114,14 @@ Generic template helper definitions for FluentCV. if (lhs || rhs) return options.fn(this); }, + /** + Conditional stylesheet link. Either display the link or embed the stylesheet + via tag. + */ + styleSheet: function( file ) { + return ''; + }, + /** Perform a generic comparison. See: http://doginthehat.com.au/2012/02/comparison-block-helper-for-handlebars-templates From d9010470439108df66b89fe470198e89ef87746d Mon Sep 17 00:00:00 2001 From: hacksalot Date: Wed, 30 Dec 2015 18:50:58 -0500 Subject: [PATCH 039/488] Update fluent-themes --> fresh-themes. --- README.md | 2 +- src/gen/template-generator.js | 2 +- src/verbs/generate.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e58b0054..4e0aa3b1 100644 --- a/README.md +++ b/README.md @@ -327,5 +327,5 @@ MIT. Go crazy. See [LICENSE.md][1] for details. [travis-image]: https://img.shields.io/travis/palomajs/paloma.svg?style=flat-square [travis-url]: https://travis-ci.org/hacksalot/HackMyResume [contribute]: CONTRIBUTING.md -[fresh-themes]: https://github.com/fluentdesk/fluent-themes +[fresh-themes]: https://github.com/fluentdesk/fresh-themes [jrst]: https://www.npmjs.com/search?q=jsonresume-theme diff --git a/src/gen/template-generator.js b/src/gen/template-generator.js index 8d3629c5..257f4772 100644 --- a/src/gen/template-generator.js +++ b/src/gen/template-generator.js @@ -233,7 +233,7 @@ Definition of the TemplateGenerator class. // Verify the specified theme name/path var tFolder = PATH.join( - parsePath( require.resolve('fluent-themes') ).dirname, + parsePath( require.resolve('fresh-themes') ).dirname, this.opts.theme ); diff --git a/src/verbs/generate.js b/src/verbs/generate.js index c0cd8da7..e4f06c94 100644 --- a/src/verbs/generate.js +++ b/src/verbs/generate.js @@ -199,7 +199,7 @@ Implementation of the 'generate' verb for HackMyResume. function verify_theme( themeNameOrPath ) { var tFolder = PATH.resolve( __dirname, - '../../node_modules/fluent-themes/themes', + '../../node_modules/fresh-themes/themes', themeNameOrPath ); var exists = require('path-exists').sync; From 558a321fe80bc39d02ce71653ef157c5a927220e Mon Sep 17 00:00:00 2001 From: hacksalot Date: Wed, 30 Dec 2015 18:52:41 -0500 Subject: [PATCH 040/488] Refactor generator logic. --- package.json | 2 +- src/gen/html-png-generator.js | 31 +++++++------ src/verbs/generate.js | 82 ++++++++++++++++++++++++----------- 3 files changed, 76 insertions(+), 39 deletions(-) diff --git a/package.json b/package.json index b3719d66..79c689c5 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "dependencies": { "colors": "^1.1.2", "copy": "^0.1.3", - "fresh-themes": "~0.9.0-beta", + "fresh-themes": "~0.9.2-beta", "fresca": "~0.2.2", "fs-extra": "^0.24.0", "handlebars": "^4.0.5", diff --git a/src/gen/html-png-generator.js b/src/gen/html-png-generator.js index 4d9b5b9e..2ec793e9 100644 --- a/src/gen/html-png-generator.js +++ b/src/gen/html-png-generator.js @@ -1,6 +1,6 @@ /** Definition of the HtmlPngGenerator class. -@license MIT. Copyright (c) 2015 James Devlin / FluentDesk. +@license MIT. See LICENSE.MD for details. @module html-png-generator.js */ @@ -11,7 +11,7 @@ Definition of the HtmlPngGenerator class. , HTML = require( 'html' ); /** - An HTML-based PDF resume generator for HackMyResume. + An HTML-based PNG resume generator for HackMyResume. */ var HtmlPngGenerator = module.exports = TemplateGenerator.extend({ @@ -19,24 +19,29 @@ Definition of the HtmlPngGenerator class. this._super( 'png', 'html' ); }, - /** - Generate the binary PDF. - */ - onBeforeSave: function( info ) { - png( info.mk, info.outputFile ); - return null; // halt further processing + invoke: function( rez, themeMarkup, cssInfo, opts ) { + //return YAML.stringify( JSON.parse( rez.stringify() ), Infinity, 2 ); + }, + + generate: function( rez, f, opts ) { + var htmlResults = opts.targets.filter(function(t){ + return t.fmt.outFormat === 'html'; + }); + var htmlFile = htmlResults[0].final.files.filter(function(fl){ + return fl.info.ext === 'html'; + }); + png(htmlFile[0].data, f); } }); /** - Generate a PDF from HTML. + Generate a PNG from HTML. */ function png( markup, fOut ) { - - require('webshot')( markup , { encoding: 'binary', siteType: 'html' } ) - .pipe( FS.createWriteStream( fOut ) ); - + // require('webshot')( markup , { encoding: 'binary', siteType: 'html' } ) + // .pipe( FS.createWriteStream( fOut ) ); + require('webshot')( markup , fOut, { siteType: 'html' }, function(err) { } ); } }()); diff --git a/src/verbs/generate.js b/src/verbs/generate.js index e4f06c94..eecdc9ac 100644 --- a/src/verbs/generate.js +++ b/src/verbs/generate.js @@ -6,6 +6,8 @@ Implementation of the 'generate' verb for HackMyResume. (function() { + + var PATH = require('path') , FS = require('fs') , MD = require('marked') @@ -70,22 +72,23 @@ Implementation of the 'generate' verb for HackMyResume. var targets = expand( dst, theTheme ); // Run the transformation! - var finished = targets.map( function(t) { - return EXTEND(true, t, { markup: single(t, theTheme) }); + targets.forEach( function(t) { + t.final = single( t, theTheme, targets ); }); // Don't send the client back empty-handed - return { sheet: rez, targets: targets, processed: finished }; + return { sheet: rez, targets: targets, processed: targets }; } /** - Generate a single resume of a specific format. TODO: Refactor. + Generate a single target resume such as "out/rez.html" or "out/rez.doc". @param targInfo Information for the target resume. @param theme A FRESHTheme or JRSTheme object. + @returns */ - function single( targInfo, theme ) { + function single( targInfo, theme, finished ) { function MDIN(txt) { // TODO: Move this return MD(txt || '' ).replace(/^\s*

|<\/p>\s*$/gi, ''); @@ -107,7 +110,8 @@ Implementation of the 'generate' verb for HackMyResume. theFormat = _fmts.filter( function(fmt) { return fmt.name === targInfo.fmt.outFormat; })[0]; MKDIRP.sync( PATH.dirname( f ) ); // Ensure dest folder exists; - theFormat.gen.generate( rez, f, _opts ); + _opts.targets = finished; + return theFormat.gen.generate( rez, f, _opts ); } // Otherwise this is either a) a JSON Resume theme or b) an ad-hoc format @@ -166,29 +170,67 @@ Implementation of the 'generate' verb for HackMyResume. } + /** - Expand output files. + Expand output files. For example, "foo.all" should be expanded to + ["foo.html", "foo.doc", "foo.pdf", "etc"]. + @param dst An array of output files as specified by the user. + @param theTheme A FRESHTheme or JRSTheme object. */ function expand( dst, theTheme ) { + + // Add freebie formats (JSON, YAML, PNG) every theme gets... + // Add HTML-driven PNG only if the theme has an HTML format. + theTheme.formats.json = theTheme.formats.json || { + freebie: true, title: 'json', outFormat: 'json', pre: 'json', + ext: 'json', path: null, data: null + }; + theTheme.formats.yml = theTheme.formats.yml || { + freebie: true, title: 'yaml', outFormat: 'yml', pre: 'yml', + ext: 'yml', path: null, data: null + }; + theTheme.formats.png = theTheme.formats.png || + ( theTheme.formats.html && { + freebie: true, title: 'png', outFormat: 'png', + ext: 'yml', path: null, data: null + }); + + // Set up the destination collection. It's either the array of files passed + // by the user or 'out/resume.all' if no targets were specified. + var destColl = (dst && dst.length && dst) || + [PATH.normalize('out/resume.all')] + + // Assemble an array of expanded target files... (can't use map() here) var targets = []; - // (can't use map() here). - ( (dst && dst.length && dst) || ['resume.all'] ).forEach( function(t) { + destColl.forEach( function(t) { - var to = PATH.resolve(t), - pa = parsePath(to), - fmat = pa.extname || '.all'; + var to = PATH.resolve(t), pa = parsePath(to),fmat = pa.extname || '.all'; + + var explicitFormats = _.omit( theTheme.formats, function(val) { + return !val.freebie; + }); + var implicitFormats = _.omit( theTheme.formats, function(val) { + return val.freebie; + }); targets.push.apply( targets, fmat === '.all' ? - - Object.keys( theTheme.formats ).map(function(k){ + Object.keys( implicitFormats ).map( function( k ) { var z = theTheme.formats[k]; - return { file: to.replace(/all$/g,z.outFormat), fmt: z }; + return { file: to.replace( /all$/g, z.outFormat ), fmt: z }; }) : + [{ file: to, fmt: theTheme.getFormat( fmat.slice(1) ) }]); + targets.push.apply( + targets, fmat === '.all' ? + Object.keys( explicitFormats ).map( function( k ) { + var z = theTheme.formats[k]; + return { file: to.replace( /all$/g, z.outFormat ), fmt: z }; + }) : [{ file: to, fmt: theTheme.getFormat( fmat.slice(1) ) }]); }); + return targets; } @@ -223,16 +265,6 @@ Implementation of the 'generate' verb for HackMyResume. var theTheme = _opts.theme.indexOf('jsonresume-theme-') > -1 ? new JRSTheme().open(tFolder) : new FluentTheme().open( tFolder ); - // Add freebie formats every theme gets - theTheme.formats.json = theTheme.formats.json || { - title: 'json', outFormat: 'json', pre: 'json', - ext: 'json', path: null, data: null - }; - theTheme.formats.yml = theTheme.formats.yml || { - title: 'yaml', outFormat: 'yml', pre: 'yml', - ext: 'yml', path: null, data: null - }; - // Cache the theme object _opts.themeObj = theTheme; From a280d8acb22b089b0afa708142178bb7ddd7d86f Mon Sep 17 00:00:00 2001 From: hacksalot Date: Wed, 30 Dec 2015 19:45:50 -0500 Subject: [PATCH 041/488] Support CSS embedding vs. linking. --- src/eng/generic-helpers.js | 6 ++++-- src/eng/handlebars-generator.js | 1 + src/index.js | 3 ++- src/verbs/generate.js | 1 + 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/eng/generic-helpers.js b/src/eng/generic-helpers.js index 26c03dcd..a9309337 100644 --- a/src/eng/generic-helpers.js +++ b/src/eng/generic-helpers.js @@ -118,8 +118,10 @@ Generic template helper definitions for HackMyResume / FluentCV. Conditional stylesheet link. Either display the link or embed the stylesheet via tag. */ - styleSheet: function( file ) { - return ''; + styleSheet: function( file, options ) { + return ( this.opts.css === 'link') ? + '' : + ''; }, /** diff --git a/src/eng/handlebars-generator.js b/src/eng/handlebars-generator.js index 71dbf58b..27a8f7db 100644 --- a/src/eng/handlebars-generator.js +++ b/src/eng/handlebars-generator.js @@ -40,6 +40,7 @@ Definition of the HandlebarsGenerator class. RAW: json, filt: opts.filters, cssInfo: cssInfo, + opts: opts, headFragment: opts.headFragment || '' }); diff --git a/src/index.js b/src/index.js index d2f99cfa..9352d49f 100644 --- a/src/index.js +++ b/src/index.js @@ -91,6 +91,7 @@ function getOpts( args ) { theme: args.t || 'modern', format: args.f || 'FRESH', prettify: !noPretty, - silent: args.s || args.silent + silent: args.s || args.silent, + css: args.css || 'embed' }; } diff --git a/src/verbs/generate.js b/src/verbs/generate.js index eecdc9ac..d3819a7a 100644 --- a/src/verbs/generate.js +++ b/src/verbs/generate.js @@ -49,6 +49,7 @@ Implementation of the 'generate' verb for HackMyResume. //_opts = extend( true, _opts, opts ); _opts.theme = (opts.theme && opts.theme.toLowerCase().trim())|| 'modern'; _opts.prettify = opts.prettify === true ? _opts.prettify : false; + _opts.css = opts.css; // Load the theme... var tFolder = verify_theme( _opts.theme ); From 55943bf49a02317373475ac281e2c8f3cc92e1f9 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Wed, 30 Dec 2015 20:09:39 -0500 Subject: [PATCH 042/488] Fix missing semicolon. --- src/verbs/generate.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/verbs/generate.js b/src/verbs/generate.js index d3819a7a..c8984d18 100644 --- a/src/verbs/generate.js +++ b/src/verbs/generate.js @@ -199,7 +199,7 @@ Implementation of the 'generate' verb for HackMyResume. // Set up the destination collection. It's either the array of files passed // by the user or 'out/resume.all' if no targets were specified. var destColl = (dst && dst.length && dst) || - [PATH.normalize('out/resume.all')] + [PATH.normalize('out/resume.all')]; // Assemble an array of expanded target files... (can't use map() here) var targets = []; From 76cafa42491b7157d982d4f37209aeecede1d0f0 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Wed, 30 Dec 2015 20:10:14 -0500 Subject: [PATCH 043/488] Fix reference error in explicit themes. --- src/core/fresh-theme.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/fresh-theme.js b/src/core/fresh-theme.js index bbb816c1..dc01975e 100644 --- a/src/core/fresh-theme.js +++ b/src/core/fresh-theme.js @@ -206,7 +206,7 @@ Definition of the FRESHTheme class. // 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 = RECURSIVE_READ_DIR( tplFolder ).map( function( absPath ) { + var fmts = READFILES( tplFolder ).map( function( absPath ) { act = null; // If this file is mentioned in the theme's JSON file under "transforms" @@ -275,7 +275,7 @@ Definition of the FRESHTheme class. (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 From 3acf648eb486218dd3fd8a46cdfa81c85363525e Mon Sep 17 00:00:00 2001 From: hacksalot Date: Wed, 30 Dec 2015 20:11:09 -0500 Subject: [PATCH 044/488] Expose helpers to Underscore engine. Get the same set of helpers working for Underscore and Handlebars engines. Needs refactoring. --- src/eng/underscore-generator.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/eng/underscore-generator.js b/src/eng/underscore-generator.js index 0a9be84c..eaa8eed1 100644 --- a/src/eng/underscore-generator.js +++ b/src/eng/underscore-generator.js @@ -11,7 +11,6 @@ Definition of the UnderscoreGenerator class. var _ = require('underscore'); - /** Perform template-based resume generation using Underscore.js. @class UnderscoreGenerator @@ -32,6 +31,10 @@ Definition of the UnderscoreGenerator class. // Strip {# comments #} jst = jst.replace( delims.comment, ''); + var helpers = require('./generic-helpers'); + helpers.opts = opts; + helpers.cssInfo = cssInfo; + // Compile and run the template. TODO: avoid unnecessary recompiles. var compiled = _.template(jst); var ret = compiled({ @@ -40,13 +43,15 @@ Definition of the UnderscoreGenerator class. XML: require('xml-escape'), RAW: json, cssInfo: cssInfo, - headFragment: opts.headFragment || '' + headFragment: opts.headFragment || '', + opts: opts, + h: helpers }); return ret; } }; - + }()); From 3cf24cfb402a2cbaf977e5ee0a762af944159890 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Wed, 30 Dec 2015 20:11:21 -0500 Subject: [PATCH 045/488] Fix PNG generation glitch. --- src/verbs/generate.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/verbs/generate.js b/src/verbs/generate.js index c8984d18..55011e3a 100644 --- a/src/verbs/generate.js +++ b/src/verbs/generate.js @@ -190,11 +190,12 @@ Implementation of the 'generate' verb for HackMyResume. freebie: true, title: 'yaml', outFormat: 'yml', pre: 'yml', ext: 'yml', path: null, data: null }; - theTheme.formats.png = theTheme.formats.png || - ( theTheme.formats.html && { - freebie: true, title: 'png', outFormat: 'png', - ext: 'yml', path: null, data: null - }); + if( theTheme.formats.html && !theTheme.formats.png ) { + theTheme.formats.png = { + freebie: true, title: 'png', outFormat: 'png', + ext: 'yml', path: null, data: null + }; + } // Set up the destination collection. It's either the array of files passed // by the user or 'out/resume.all' if no targets were specified. @@ -207,7 +208,7 @@ Implementation of the 'generate' verb for HackMyResume. var to = PATH.resolve(t), pa = parsePath(to),fmat = pa.extname || '.all'; - var explicitFormats = _.omit( theTheme.formats, function(val) { + var explicitFormats = _.omit( theTheme.formats, function(val, key) { return !val.freebie; }); var implicitFormats = _.omit( theTheme.formats, function(val) { From df27924ac20d254307aed919408fbafde4397f74 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Wed, 30 Dec 2015 21:07:28 -0500 Subject: [PATCH 046/488] Add Johnny Trouble to tests. --- package.json | 3 +- test/test-fresh-sheet.js | 107 +++++++++++++++++++-------------------- test/test-themes.js | 47 ++++++++--------- 3 files changed, 74 insertions(+), 83 deletions(-) diff --git a/package.json b/package.json index 79c689c5..bef8d59f 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "dependencies": { "colors": "^1.1.2", "copy": "^0.1.3", - "fresh-themes": "~0.9.2-beta", + "fresh-themes": "~0.9.3-beta", "fresca": "~0.2.2", "fs-extra": "^0.24.0", "handlebars": "^4.0.5", @@ -79,6 +79,7 @@ "grunt-contrib-yuidoc": "^0.10.0", "grunt-simple-mocha": "*", "jane-q-fullstacker": "fluentdesk/jane-q-fullstacker", + "johnny-trouble-resume": "fluentdesk/johnny-trouble-resume", "jsonresume-theme-boilerplate": "^0.1.2", "jsonresume-theme-classy": "^1.0.9", "jsonresume-theme-modern": "0.0.18", diff --git a/test/test-fresh-sheet.js b/test/test-fresh-sheet.js index 4d285592..eb2ec7c2 100644 --- a/test/test-fresh-sheet.js +++ b/test/test-fresh-sheet.js @@ -9,59 +9,54 @@ var chai = require('chai') chai.config.includeStack = false; -describe('jane-doe.json (FRESH)', function () { - - var _sheet; - - it('should open without throwing an exception', function () { - function tryOpen() { - _sheet = new FRESHResume().open( - 'node_modules/jane-q-fullstacker/resume/jane-resume.json' ); - } - tryOpen.should.not.Throw(); - }); - - it('should have one or more of each section', function() { - expect( - //(_sheet.basics) && - _sheet.name && _sheet.info && _sheet.location && _sheet.contact && - (_sheet.employment.history && _sheet.employment.history.length > 0) && - (_sheet.skills && _sheet.skills.list.length > 0) && - (_sheet.education.history && _sheet.education.history.length > 0) && - (_sheet.service.history && _sheet.service.history.length > 0) && - (_sheet.writing && _sheet.writing.length > 0) && - (_sheet.recognition && _sheet.recognition.length > 0) && - (_sheet.samples && _sheet.samples.length > 0) && - (_sheet.references && _sheet.references.length > 0) && - (_sheet.interests && _sheet.interests.length > 0) - ).to.equal( true ); - }); - - it('should have a work duration of 7 years', function() { - _sheet.computed.numYears.should.equal( 7 ); - }); - - it('should save without throwing an exception', function(){ - function trySave() { - _sheet.save( 'test/sandbox/jane-q-fullstacker.json' ); - } - trySave.should.not.Throw(); - }); - - it('should not be modified after saving', function() { - var savedSheet = new FRESHResume().open('test/sandbox/jane-q-fullstacker.json'); - _sheet.stringify().should.equal( savedSheet.stringify() ); - }); - - it('should validate against the FRESH resume schema', function() { - var result = _sheet.isValid(); - // var schemaJson = require('fresca'); - // 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", _sheet.imp.validationErrors, "\n\n"); - result.should.equal( true ); - }); - - -}); +function testResume(opts) { + + describe( opts.title + ' (FRESH)', function () { + + var _sheet; + + it('should open without throwing an exception', function () { + function tryOpen() { + _sheet = new FRESHResume().open( opts.path ); + } + tryOpen.should.not.Throw(); + }); + + it('should have one or more of each section', function() { + var newObj = _.pick( _sheet, opts.sections ); + expect( Object.keys(newObj).length ).to.equal( opts.sections.length ); + }); + + it('should have a work duration of ' + opts.duration + ' years', function() { + _sheet.computed.numYears.should.equal( opts.duration ); + }); + + it('should save without throwing an exception', function(){ + function trySave() { + _sheet.save( 'test/sandbox/' + opts.title + '.json' ); + } + trySave.should.not.Throw(); + }); + + it('should not be modified after saving', function() { + var savedSheet = new FRESHResume().open('test/sandbox/' + opts.title + '.json'); + _sheet.stringify().should.equal( savedSheet.stringify() ); + }); + + it('should validate against the FRESH resume schema', function() { + var result = _sheet.isValid(); + // var schemaJson = require('fresca'); + // 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", _sheet.imp.validationErrors, "\n\n"); + result.should.equal( true ); + }); + + + }); +} + +var sects = [ 'info', 'employment', 'service', 'skills', 'education', 'writing', 'recognition', 'references' ]; +testResume({ title: 'jane-q-fullstacker', path: 'node_modules/jane-q-fullstacker/resume/jane-resume.json', duration: 7, sections: sects }); +testResume({ title: 'johnny-trouble-resume', path: 'node_modules/johnny-trouble-resume/src/johnny-trouble.fresh.json', duration: 3, sections: sects }); diff --git a/test/test-themes.js b/test/test-themes.js index 5a6ee1d7..ba9496a5 100644 --- a/test/test-themes.js +++ b/test/test-themes.js @@ -12,7 +12,9 @@ var SPAWNWATCHER = require('../src/core/spawn-watch') chai.config.includeStack = false; -describe('Testing themes', function () { +function genThemes( title, src, fmt ) { + + describe('Testing themes against ' + title.toUpperCase() + ' resume ' + '(' + fmt + ')' , function () { var _sheet; @@ -29,11 +31,11 @@ describe('Testing themes', function () { function genTheme( fmt, src, themeName, themeLoc, testTitle ) { themeLoc = themeLoc || themeName; - testTitle = themeName.toUpperCase() + ' theme should generate without throwing an exception'; + testTitle = themeName.toUpperCase() + ' theme (' + fmt + ') should generate without throwing an exception'; it( testTitle, function () { function tryOpen() { //var src = ['node_modules/jane-q-fullstacker/resume/jane-resume.json']; - var dst = ['test/sandbox/' + fmt + '/' + themeName + '/resume.all']; + var dst = ['test/sandbox/' + fmt + '/' + title + '/' + themeName + '/resume.all']; var opts = { theme: themeLoc, format: fmt, @@ -46,28 +48,21 @@ describe('Testing themes', function () { }); } - var src = ['node_modules/jane-q-fullstacker/resume/jane-resume.json']; - genTheme('FRESH', src, 'hello-world'); - genTheme('FRESH', src, 'compact'); - genTheme('FRESH', src, 'modern'); - genTheme('FRESH', src, 'minimist'); - genTheme('FRESH', src, 'awesome'); - genTheme('FRESH', src, 'positive'); - genTheme('FRESH', src, 'jsonresume-theme-boilerplate', 'node_modules/jsonresume-theme-boilerplate' ); - genTheme('FRESH', src, 'jsonresume-theme-sceptile', 'node_modules/jsonresume-theme-sceptile' ); - genTheme('FRESH', src, 'jsonresume-theme-modern', 'node_modules/jsonresume-theme-modern' ); - genTheme('FRESH', src, 'jsonresume-theme-classy', 'node_modules/jsonresume-theme-classy' ); + genTheme(fmt, src, 'hello-world'); + genTheme(fmt, src, 'compact'); + genTheme(fmt, src, 'modern'); + genTheme(fmt, src, 'minimist'); + genTheme(fmt, src, 'awesome'); + genTheme(fmt, src, 'positive'); + genTheme(fmt, src, 'jsonresume-theme-boilerplate', 'node_modules/jsonresume-theme-boilerplate' ); + genTheme(fmt, src, 'jsonresume-theme-sceptile', 'node_modules/jsonresume-theme-sceptile' ); + genTheme(fmt, src, 'jsonresume-theme-modern', 'node_modules/jsonresume-theme-modern' ); + genTheme(fmt, src, 'jsonresume-theme-classy', 'node_modules/jsonresume-theme-classy' ); + + }); - src = ['test/resumes/jrs-0.0.0/richard-hendriks.json']; - genTheme('JRS', src, 'hello-world'); - genTheme('JRS', src, 'compact'); - genTheme('JRS', src, 'modern'); - genTheme('JRS', src, 'minimist'); - genTheme('JRS', src, 'awesome'); - genTheme('JRS', src, 'positive'); - genTheme('JRS', src, 'jsonresume-theme-boilerplate', 'node_modules/jsonresume-theme-boilerplate' ); - genTheme('JRS', src, 'jsonresume-theme-sceptile', 'node_modules/jsonresume-theme-sceptile' ); - genTheme('JRS', src, 'jsonresume-theme-modern', 'node_modules/jsonresume-theme-modern' ); - genTheme('JRS', src, 'jsonresume-theme-classy', 'node_modules/jsonresume-theme-classy' ); +} -}); +genThemes( 'jane-q-fullstacker', ['node_modules/jane-q-fullstacker/resume/jane-resume.json'], 'FRESH' ); +genThemes( 'johnny-trouble', ['node_modules/johnny-trouble-resume/src/johnny-trouble.fresh.json'], 'FRESH' ); +genThemes( 'richard-hendriks', ['test/resumes/jrs-0.0.0/richard-hendriks.json'], 'JRS' ); From 1f6d77fc280f2b797fd158f4cdce09daeca4831d Mon Sep 17 00:00:00 2001 From: hacksalot Date: Thu, 31 Dec 2015 03:18:02 -0500 Subject: [PATCH 047/488] Bump version to 1.3.0. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bef8d59f..2fa8669a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hackmyresume", - "version": "1.3.0-beta", + "version": "1.3.0", "description": "Generate polished résumés and CVs in HTML, Markdown, LaTeX, MS Word, PDF, plain text, JSON, XML, YAML, smoke signal, and carrier pigeon.", "repository": { "type": "git", From 069c02ddcc20bd47617f9de0e0d5ecf7fd3925e3 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Thu, 31 Dec 2015 03:34:41 -0500 Subject: [PATCH 048/488] Interim changes supporting v1.3.0. --- package.json | 3 +- src/core/convert.js | 36 ++++++++++-- src/core/jrs-resume.js | 22 +++++++- src/core/resume-factory.js | 104 ++++++++++++++++++++++++++++------- src/index.js | 2 +- src/utils/syntax-error-ex.js | 40 ++++++++++++++ src/verbs/convert.js | 25 ++++++--- src/verbs/create.js | 6 ++ src/verbs/generate.js | 16 +++--- src/verbs/validate.js | 66 ++++++++++------------ 10 files changed, 236 insertions(+), 84 deletions(-) create mode 100644 src/utils/syntax-error-ex.js diff --git a/package.json b/package.json index 2fa8669a..4b22adbc 100644 --- a/package.json +++ b/package.json @@ -47,12 +47,13 @@ "dependencies": { "colors": "^1.1.2", "copy": "^0.1.3", - "fresh-themes": "~0.9.3-beta", "fresca": "~0.2.2", + "fresh-themes": "~0.9.3-beta", "fs-extra": "^0.24.0", "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", diff --git a/src/core/convert.js b/src/core/convert.js index 29f1a47b..b579cf91 100644 --- a/src/core/convert.js +++ b/src/core/convert.js @@ -1,11 +1,13 @@ /** FRESH to JSON Resume conversion routiens. -@license MIT. Copyright (c) 2015 James Devlin / FluentDesk. +@license MIT. See LICENSE.md for details. @module convert.js */ (function(){ + var _ = require('underscore'); + /** Convert between FRESH and JRS resume/CV formats. @class FRESHConverter @@ -26,6 +28,8 @@ FRESH to JSON Resume conversion routiens. name: src.basics.name, + imp: src.basics.imp, + info: { label: src.basics.label, class: src.basics.class, // <--> round-trip @@ -92,7 +96,8 @@ FRESH to JSON Resume conversion routiens. countryCode: src.location.country, region: src.location.region }, - profiles: social( src.social, false ) + profiles: social( src.social, false ), + imp: src.imp }, work: employment( src.employment, false ), @@ -109,12 +114,30 @@ FRESH to JSON Resume conversion routiens. }; + }, + + toSTRING: function( src ) { + function replacerJRS( 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; + } + function replacerFRESH( 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( src, src.basics ? replacerJRS : replacerFRESH, 2 ); } }; function meta( direction, obj ) { - if( !obj ) return obj; // preserve null and undefined + //if( !obj ) return obj; // preserve null and undefined if( direction ) { obj = obj || { }; obj.format = obj.format || "FRESH@0.1.0"; @@ -151,7 +174,7 @@ FRESH to JSON Resume conversion routiens. start: job.startDate, end: job.endDate, url: job.website, - keywords: "", + keywords: [], highlights: job.highlights }; }) : undefined @@ -164,6 +187,7 @@ FRESH to JSON Resume conversion routiens. if( !obj ) return obj; if( direction ) { return obj && obj.length ? { + level: "", history: obj.map(function(edu){ return { institution: edu.institution, @@ -171,8 +195,8 @@ FRESH to JSON Resume conversion routiens. end: edu.endDate, grade: edu.gpa, curriculum: edu.courses, - url: edu.website || edu.url || null, - summary: null, + url: edu.website || edu.url || undefined, + summary: edu.summary || "", area: edu.area, studyType: edu.studyType }; diff --git a/src/core/jrs-resume.js b/src/core/jrs-resume.js index 49a8d86b..5609a75d 100644 --- a/src/core/jrs-resume.js +++ b/src/core/jrs-resume.js @@ -1,6 +1,6 @@ /** Definition of the JRSResume class. -@license MIT. Copyright (c) 2015 James Devlin / FluentDesk. +@license MIT. See LICENSE.md for details. @module jrs-resume.js */ @@ -12,6 +12,7 @@ Definition of the JRSResume class. , _ = require('underscore') , PATH = require('path') , MD = require('marked') + , CONVERTER = require('./convert') , moment = require('moment'); /** @@ -51,6 +52,24 @@ Definition of the JRSResume class. 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.fileName = filename || this.imp.fileName; + FS.writeFileSync( this.basics.imp.fileName, this.stringify(), 'utf8' ); + } + else { + var newRep = CONVERTER.toFRESH( this ); + var stringRep = CONVERTER.toSTRING( newRep ); + FS.writeFileSync( filename, stringRep, 'utf8' ); + } + return this; + + }; + /** Convert this object to a JSON string, sanitizing meta-properties along the way. Don't override .toString(). @@ -92,6 +111,7 @@ Definition of the JRSResume class. 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 ); diff --git a/src/core/resume-factory.js b/src/core/resume-factory.js index b72f8a90..b8d42656 100644 --- a/src/core/resume-factory.js +++ b/src/core/resume-factory.js @@ -1,43 +1,107 @@ /** -Core resume-loading logic for HackMyResume. +Definition of the ResumeFactory class. +@license MIT. See LICENSE.md for details. @module resume-factory.js */ + + (function(){ + + require('string.prototype.startswith'); var FS = require('fs'); var ResumeConverter = require('./convert'); + + /** A simple factory class for FRESH and JSON Resumes. @class ResumeFactory */ - module.exports = { + var ResumeFactory = module.exports = { + + /** - Load one or more resumes in a specific source format. + Load one or more resumes from disk. */ - load: function ( src, log, fn, toFormat ) { - - toFormat = toFormat && (toFormat.toLowerCase().trim()) || 'fresh'; - var ResumeClass = require('../core/' + toFormat + '-resume'); - - return src.map( function( res ) { - var rezJson = JSON.parse( FS.readFileSync( res ) ); - var orgFormat = ( rezJson.meta && rezJson.meta.format && - rezJson.meta.format.startsWith('FRESH@') ) ? - 'fresh' : 'jrs'; - if(orgFormat !== toFormat) { - rezJson = ResumeConverter[ 'to' + toFormat.toUpperCase() ]( rezJson ); - } - // TODO: Core should not log - log( 'Reading '.info + orgFormat.toUpperCase().infoBold + ' resume: '.info + res.cyan.bold ); - return (fn && fn(res)) || (new ResumeClass()).parseJSON( rezJson ); + load: function ( sources, log, toFormat, objectify ) { + // Loop over all inputs, parsing each to JSON and then to a FRESHResume + // or JRSResume object. + var that = this; + return sources.map( function( src ) { + return that.loadOne( src, log, toFormat, objectify ); }); - } + }, + + + + /** + Load a single resume from disk. + */ + loadOne: function( src, log, toFormat, 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, log, toFormat ); + if( info.error ) return info; + var json = info.json; + + // Determine the resume format: FRESH or JRS + 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 ); + } + + return { + file: src, + json: info.json, + rez: rez + }; + } }; + + + function _parse( fileName, log, toFormat ) { + var rawData; + try { + + // TODO: Core should not log + log( 'Reading '.info + /*orgFormat.toUpperCase().infoBold +*/ + 'resume: '.info + fileName.cyan.bold ); + + rawData = FS.readFileSync( fileName, 'utf8' ); + return { + json: JSON.parse( rawData ) + }; + + } + catch(ex) { + return { + error: ex, + raw: rawData + }; + } + } + + + }()); diff --git a/src/index.js b/src/index.js index 9352d49f..904d7900 100644 --- a/src/index.js +++ b/src/index.js @@ -73,7 +73,7 @@ function main() { // Massage inputs and outputs var src = a._.slice(1, splitAt === -1 ? undefined : splitAt ); var dst = splitAt === -1 ? [] : a._.slice( splitAt + 1 ); - ( splitAt === -1 ) && src.length > 1 && dst.push( src.pop() ); // Allow omitting TO keyword + ( splitAt === -1 ) && (src.length > 1) && (verb !== 'validate') && dst.push( src.pop() ); // Allow omitting TO keyword // Invoke the action (FCMD.verbs[verb] || FCMD.alias[verb]).apply(null, [src, dst, opts, logMsg]); diff --git a/src/utils/syntax-error-ex.js b/src/utils/syntax-error-ex.js new file mode 100644 index 00000000..9b2834e1 --- /dev/null +++ b/src/utils/syntax-error-ex.js @@ -0,0 +1,40 @@ +/** +Definition of the SyntaxErrorEx class. +@module syntax-error-ex.js +*/ + +(function() { + + + /** + Represents a SyntaxError exception with line and column info. + Collect syntax error information from the provided exception object. The + JavaScript `SyntaxError` exception isn't interpreted uniformly across environ- + ments, so we first check for a .lineNumber and .columnNumber and, if that's + not present, fall back to the JSONLint library, which provides that info. + See: http://stackoverflow.com/q/13323356 + @class SyntaxErrorEx + */ + + module.exports = function SyntaxErrorEx( ex, rawData ) { + + var lineNum = null, colNum = null; + if( ex.lineNumber !== undefined && ex.lineNumber !== null ) { + lineNum = ex.lineNumber; + } + if( ex.columnNumber !== undefined && ex.columnNumber !== null ) { + colNum = ex.columnNumber; + } + if( lineNum === null || colNum === null ) { + var JSONLint = require('json-lint'); + var lint = JSONLint( rawData, { comments: false } ); + if( lineNum === null ) lineNum = (lint.error ? lint.line : '???'); + if( colNum === null ) colNum = (lint.error ? lint.character : '???'); + } + this.line = lineNum; + this.col = colNum; + + }; + + +}()); diff --git a/src/verbs/convert.js b/src/verbs/convert.js index 7386072c..a2f80feb 100644 --- a/src/verbs/convert.js +++ b/src/verbs/convert.js @@ -1,3 +1,9 @@ +/** +Implementation of the 'convert' verb for HackMyResume. +@module convert.js +@license MIT. See LICENSE.md for details. +*/ + (function(){ var ResumeFactory = require('../core/resume-factory'); @@ -5,22 +11,23 @@ /** Convert between FRESH and JRS formats. */ - module.exports = function convert( src, dst, opts, logger ) { + module.exports = function convert( sources, dst, opts, logger ) { var _log = logger || console.log; - if( !src || !src.length ) { throw { fluenterror: 6 }; } + if( !sources || !sources.length ) { throw { fluenterror: 6 }; } if( !dst || !dst.length ) { - if( src.length === 1 ) { throw { fluenterror: 5 }; } - else if( src.length === 2 ) { dst = [ src[1] ]; src = [ src[0] ]; } + if( sources.length === 1 ) { throw { fluenterror: 5 }; } + else if( sources.length === 2 ) { dst = [ sources[1] ]; sources = [ sources[0] ]; } else { throw { fluenterror: 5 }; } } - if( src && dst && src.length && dst.length && src.length !== dst.length ) { + if( sources && dst && sources.length && dst.length && sources.length !== dst.length ) { throw { fluenterror: 7 }; } - var sheets = ResumeFactory.load( src, _log ); - sheets.forEach(function(sheet, idx){ - var sourceFormat = sheet.imp.orgFormat === 'JRS' ? 'JRS' : 'FRESH'; + var sourceResumes = ResumeFactory.load( sources, _log, null, true ); + sourceResumes.forEach(function( src, idx ) { + var sheet = src.rez; + var sourceFormat = ((sheet.basics && sheet.basics.imp) || sheet.imp).orgFormat === 'JRS' ? 'JRS' : 'FRESH'; var targetFormat = sourceFormat === 'JRS' ? 'FRESH' : 'JRS'; - _log( 'Converting '.useful + sheet.imp.fileName.useful.bold + (' (' + + _log( 'Converting '.useful + src.file.useful.bold + (' (' + sourceFormat + ') to ').useful + dst[0].useful.bold + (' (' + targetFormat + ').').useful ); sheet.saveAs( dst[idx], targetFormat ); diff --git a/src/verbs/create.js b/src/verbs/create.js index b5edf8ea..11833207 100644 --- a/src/verbs/create.js +++ b/src/verbs/create.js @@ -1,3 +1,9 @@ +/** +Implementation of the 'create' verb for HackMyResume. +@module create.js +@license MIT. See LICENSE.md for details. +*/ + (function(){ var FLUENT = require('../hackmyapi') diff --git a/src/verbs/generate.js b/src/verbs/generate.js index 542ef552..7dea1a59 100644 --- a/src/verbs/generate.js +++ b/src/verbs/generate.js @@ -54,28 +54,28 @@ Implementation of the 'generate' verb for HackMyResume. // Load the theme... var tFolder = verify_theme( _opts.theme ); - var theTheme = load_theme( tFolder ); + var theme = load_theme( tFolder ); // Load input resumes... if( !src || !src.length ) { throw { fluenterror: 3 }; } - var sheets = ResumeFactory.load( src, _log, null, - theTheme.render ? 'JRS' : 'FRESH' ); + var sheets = ResumeFactory.load(src, _log, theme.render ? 'JRS' : 'FRESH', true); // Merge input resumes... var msg = ''; - rez = _.reduceRight( sheets, function( a, b, idx ) { + var rezRep = _.reduceRight( sheets, function( a, b, idx ) { msg += ((idx == sheets.length - 2) ? - 'Merging '.gray+ a.imp.fileName : '') + ' onto '.gray + b.imp.fileName; - return extend( true, b, a ); + 'Merging '.gray + a.rez.imp.fileName : '') + ' onto '.gray + b.rez.fileName; + return extend( true, b.rez, a.rez ); }); + rez = rezRep.rez; msg && _log(msg); // Expand output resumes... - var targets = expand( dst, theTheme ); + var targets = expand( dst, theme ); // Run the transformation! targets.forEach( function(t) { - t.final = single( t, theTheme, targets ); + t.final = single( t, theme, targets ); }); // Don't send the client back empty-handed diff --git a/src/verbs/validate.js b/src/verbs/validate.js index 239576c9..a2c89f29 100644 --- a/src/verbs/validate.js +++ b/src/verbs/validate.js @@ -1,16 +1,23 @@ +/** +Implementation of the 'validate' verb for HackMyResume. +@module validate.js +@license MIT. See LICENSE.md for details. +*/ + (function() { var FS = require('fs'); var ResumeFactory = require('../core/resume-factory'); + var SyntaxErrorEx = require('../utils/syntax-error-ex'); module.exports = /** Validate 1 to N resumes in either FRESH or JSON Resume format. */ - function validate( src, unused, opts, logger ) { + function validate( sources, unused, opts, logger ) { var _log = logger || console.log; - if( !src || !src.length ) { throw { fluenterror: 6 }; } + if( !sources || !sources.length ) { throw { fluenterror: 6 }; } var isValid = true; var validator = require('is-my-json-valid'); @@ -20,67 +27,51 @@ }; // Load input resumes... - var sheets = ResumeFactory.load(src, _log, function( res ) { - try { - return { - file: res, - raw: FS.readFileSync( res, 'utf8' ) - }; - } - catch( ex ) { - throw ex; - } - }); - - sheets.forEach( function( rep ) { - - var rez; - try { - rez = JSON.parse( rep.raw ); - } - catch( ex ) { // Note [1] - _log('Validating '.info + rep.file.infoBold + - ' against FRESH/JRS schema: '.info + 'ERROR!'.error.bold); - - if (ex instanceof SyntaxError) { - // Invalid JSON - _log( '--> '.bold.red + rep.file.toUpperCase().red + - ' contains invalid JSON. Unable to validate.'.red ); - _log( (' INTERNAL: ' + ex).red ); + sources.forEach(function( src ) { + + var result = ResumeFactory.loadOne( src, function(){}, null, false ); + if( result.error ) { + _log( 'Validating '.info + src.infoBold + ' against '.info + 'AUTO'.infoBold + ' schema:'.info + ' BROKEN'.red.bold ); + + var ex = result.error; // alias + if ( ex instanceof SyntaxError) { + var info = new SyntaxErrorEx( ex, result.raw ); + _log( ('--> '.warn.bold + src.toUpperCase() + ' contains invalid JSON on line ' + + info.line + ' column ' + info.col + '.').warn + + ' Unable to validate.'.warn ); + _log( (' INTERNAL: ' + ex).warn ); } else { - - _log(('ERROR: ' + ex.toString()).red.bold); + _log(('ERROR: ' + ex.toString()).warn.bold); } return; } + var json = result.json; var isValid = false; var style = 'useful'; var errors = []; - var fmt = rez.meta && - (rez.meta.format === 'FRESH@0.1.0') ? 'fresh':'jars'; + var fmt = json.meta && (json.meta.format==='FRESH@0.1.0') ? 'fresh':'jars'; try { - var validate = validator( schemas[ fmt ], { // Note [1] formats: { date: /^\d{4}(?:-(?:0[0-9]{1}|1[0-2]{1})(?:-[0-9]{2})?)?$/ } }); - isValid = validate( rez ); + isValid = validate( json ); if( !isValid ) { style = 'warn'; errors = validate.errors; } } - catch(ex) { + catch(exc) { return; } - _log( 'Validating '.info + rep.file.infoBold + ' against '.info + + _log( 'Validating '.info + result.file.infoBold + ' against '.info + fmt.replace('jars','JSON Resume').toUpperCase().infoBold + ' schema: '.info + (isValid ? 'VALID!' : 'INVALID')[style].bold ); @@ -93,5 +84,4 @@ }); }; - }()); From b85d40b1b3323a223fc9db1d97380006bd47eb88 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Thu, 31 Dec 2015 06:38:30 -0500 Subject: [PATCH 049/488] Improve XML encoding for Word docs. Fix various encoding errors. --- src/core/fresh-resume.js | 79 +++++++++++++++++++++++++++++++++ src/eng/generic-helpers.js | 9 ++-- src/eng/handlebars-generator.js | 7 ++- src/utils/html-to-wpml.js | 2 + 4 files changed, 93 insertions(+), 4 deletions(-) diff --git a/src/core/fresh-resume.js b/src/core/fresh-resume.js index 58c97a51..54d08ace 100644 --- a/src/core/fresh-resume.js +++ b/src/core/fresh-resume.js @@ -13,6 +13,7 @@ Definition of the FRESHResume class. , __ = require('lodash') , PATH = require('path') , moment = require('moment') + , XML = require('xml-escape') , MD = require('marked') , CONVERTER = require('./convert') , JRSResume = require('./jrs-resume'); @@ -81,12 +82,90 @@ Definition of the FRESHResume class. return JSON.stringify( obj, replacer, 2 ); }; + /** + 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). + */ + FreshResume.prototype.transformStrings = function( filters, transformer ) { + + var that = this; + var ret = this.dupe(); + + // TODO: refactor recursion + function transformStringsInObject( obj ) { + + if( !obj ) return; + if( moment.isMoment( obj ) ) return; + + if( _.isArray( obj ) ) { + obj.forEach( function(elem, idx, ar) { + if( typeof elem === 'string' || elem instanceof String ) + ar[idx] = transformer( null, elem ); + else if (_.isObject(elem)) + transformStringsInObject( elem ); + }); + } + else if (_.isObject( obj )) { + Object.keys( obj ).forEach(function(k) { + var sub = obj[k]; + if( typeof sub === 'string' || sub instanceof String ) { + if( filters.length && _.contains(filters, k) ) + return; + obj[k] = transformer( k, sub ); + } + else if (_.isObject( sub )) + transformStringsInObject( sub ); + }); + } + + } + + Object.keys( ret ).forEach(function(member){ + transformStringsInObject( ret[ member ] ); + }); + + return ret; + }; + /** 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 ); + }; + + /** + Create a copy of this resume in which all fields have been interpreted as + Markdown. + */ + FreshResume.prototype.markdownify2 = function() { + var that = this; var ret = this.dupe(); diff --git a/src/eng/generic-helpers.js b/src/eng/generic-helpers.js index a9309337..6b10dc80 100644 --- a/src/eng/generic-helpers.js +++ b/src/eng/generic-helpers.js @@ -9,6 +9,7 @@ Generic template helper definitions for HackMyResume / FluentCV. var MD = require('marked') , H2W = require('../utils/html-to-wpml') + , XML = require('xml-escape') , moment = require('moment') , _ = require('underscore'); @@ -33,10 +34,12 @@ Generic template helper definitions for HackMyResume / FluentCV. wpml: function( txt, inline ) { if(!txt) return ''; inline = (inline && !inline.hash) || false; + txt = XML(txt.trim()); txt = inline ? - MD(txt.trim()).replace(/^\s*

|<\/p>\s*$/gi, '') : - MD(txt.trim()); - txt = H2W( txt.trim() ); + MD(txt).replace(/^\s*

|<\/p>\s*$/gi, '') : + MD(txt); + txt = H2W( txt ); + console.log(txt); return txt; }, diff --git a/src/eng/handlebars-generator.js b/src/eng/handlebars-generator.js index 27a8f7db..b9895eb4 100644 --- a/src/eng/handlebars-generator.js +++ b/src/eng/handlebars-generator.js @@ -35,8 +35,13 @@ Definition of the HandlebarsGenerator class. // Compile and run the Handlebars template. var template = HANDLEBARS.compile(jst); + + var encData = json; + ( format === 'html' || format === 'pdf' ) && (encData = json.markdownify()); + ( format === 'doc' ) && (encData = json.xmlify()); + return template({ - r: format === 'html' || format === 'pdf' || format === 'png' ? json.markdownify() : json, + r: encData, RAW: json, filt: opts.filters, cssInfo: cssInfo, diff --git a/src/utils/html-to-wpml.js b/src/utils/html-to-wpml.js index 89f1c51b..243a18df 100644 --- a/src/utils/html-to-wpml.js +++ b/src/utils/html-to-wpml.js @@ -47,6 +47,7 @@ Definition of the Markdown to WordProcessingML conversion routine. break; case 'Chars': + if( tok.chars.trim().length ) { var style = is_bold ? '' : ''; style += is_italic ? '': ''; style += is_link ? '' : ''; @@ -54,6 +55,7 @@ Definition of the Markdown to WordProcessingML conversion routine. (is_link ? ('') : '') + '' + style + '' + tok.chars + '' + (is_link ? '' : ''); + } break; } }); From dbda48c16dadc193392e2034f9a62787283a7fe4 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Thu, 31 Dec 2015 18:24:45 -0500 Subject: [PATCH 050/488] Add additional validate tests. --- test/test-cli.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/test/test-cli.js b/test/test-cli.js index 7c43fc9a..666eaf7c 100644 --- a/test/test-cli.js +++ b/test/test-cli.js @@ -47,11 +47,14 @@ describe('Testing CLI interface', function () { run( 'new', ['test/sandbox/new-jrs-resume.json'], [], opts2, ' (JRS format)' ); run( 'new', ['test/sandbox/new-1.json', 'test/sandbox/new-2.json', 'test/sandbox/new-3.json'], [], opts, ' (multiple FRESH resumes)' ); run( 'new', ['test/sandbox/new-jrs-1.json', 'test/sandbox/new-jrs-2.json', 'test/sandbox/new-jrs-3.json'], [], opts, ' (multiple JRS resumes)' ); - run( 'new', ['test/sandbox/new-jrs-resume.json'], [], opts2, ' (JRS format)' ); fail( 'new', [], [], opts, " (when a filename isn't specified)" ); - run( 'validate', ['node_modules/jane-q-fullstacker/resume/jane-resume.json'], [], opts, ' (FRESH format)' ); - run( 'validate', ['test/sandbox/new-fresh-resume.json'], [], opts, ' (FRESH format)' ); + run( 'validate', ['node_modules/jane-q-fullstacker/resume/jane-resume.json'], [], opts, ' (jane-q-fullstacker|FRESH)' ); + run( 'validate', ['node_modules/johnny-trouble-resume/src/johnny-trouble.fresh.json'], [], opts, ' (johnny-trouble|FRESH)' ); + run( 'validate', ['test/sandbox/new-fresh-resume.json'], [], opts, ' (new-fresh-resume|FRESH)' ); + run( 'validate', ['test/sandbox/resumes/jrs-0.0.0/ruchard-hendriks.json'], [], opts2, ' (richard-hendriks.json|JRS)' ); + run( 'validate', ['test/sandbox/resumes/jrs-0.0.0/jane-incomplete.json'], [], opts2, ' (jane-incomplete.json|JRS)' ); + run( 'validate', ['test/sandbox/new-1.json','test/sandbox/new-jrs-resume.json','test/sandbox/new-1.json', 'test/sandbox/new-2.json', 'test/sandbox/new-3.json'], [], opts, ' (5|BOTH)' ); function run( verb, src, dst, opts, msg ) { msg = msg || '.'; From ea8da6811a33d9d09ca7570710e021e0a4682dd1 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Thu, 31 Dec 2015 18:31:39 -0500 Subject: [PATCH 051/488] Include Node 0.10 in Travis tests. We've already done some work to support legacy Node 0.10 (ex https://github.com/hacksalot/HackMyResume/issues/31#issuecomment-167155845) no reason to drop this support by omitting tests. --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index e0bc653f..de82e916 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,6 @@ language: node_js node_js: + - "0.10" - "0.11" - "0.12" - "4.0" From 48cc315fc87aade00737776b86384c1346173621 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Thu, 31 Dec 2015 19:17:56 -0500 Subject: [PATCH 052/488] Update Travis shields. Add version and a badge for the /dev branch. --- README.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4e0aa3b1..dc2f6b86 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ HackMyResume ============ -[![Build status][travis-image]][travis-url] +[![Latest release][img-release]][latest-release] +[![Build status (MASTER)][img-master]][travis-url-master] +[![Build status (DEV)][img-dev]][travis-url-dev] *Create polished résumés and CVs in multiple formats from your command line or shell. Author in clean Markdown and JSON, export to Word, HTML, PDF, LaTeX, @@ -324,8 +326,12 @@ MIT. Go crazy. See [LICENSE.md][1] for details. [fresh]: https://github.com/fluentdesk/FRESH [fresca]: https://github.com/fluentdesk/FRESCA [dry]: https://en.wikipedia.org/wiki/Don%27t_repeat_yourself -[travis-image]: https://img.shields.io/travis/palomajs/paloma.svg?style=flat-square -[travis-url]: https://travis-ci.org/hacksalot/HackMyResume +[img-release]: https://img.shields.io/github/release/hacksalot/HackMyResume.svg?label=version +[img-master]: https://img.shields.io/travis/hacksalot/HackMyResume/master.svg +[img-dev]: https://img.shields.io/travis/hacksalot/HackMyResume/dev.svg?label=dev +[travis-url-master]: https://travis-ci.org/hacksalot/HackMyResume/master +[travis-url-dev]: https://travis-ci.org/hacksalot/HackMyResume/dev +[latest-release]: https://github.com/hacksalot/HackMyResume/releases/latest [contribute]: CONTRIBUTING.md [fresh-themes]: https://github.com/fluentdesk/fresh-themes [jrst]: https://www.npmjs.com/search?q=jsonresume-theme From 5c39c1c93d129375670321750e7bf4290c7ed02a Mon Sep 17 00:00:00 2001 From: hacksalot Date: Thu, 31 Dec 2015 19:47:55 -0500 Subject: [PATCH 053/488] Remove extraneous console.log. --- src/eng/generic-helpers.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/eng/generic-helpers.js b/src/eng/generic-helpers.js index 6b10dc80..3f6d92ad 100644 --- a/src/eng/generic-helpers.js +++ b/src/eng/generic-helpers.js @@ -39,7 +39,6 @@ Generic template helper definitions for HackMyResume / FluentCV. MD(txt).replace(/^\s*

|<\/p>\s*$/gi, '') : MD(txt); txt = H2W( txt ); - console.log(txt); return txt; }, From baccb75256911be2b32928aa3b4304baad34b506 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Thu, 31 Dec 2015 19:51:06 -0500 Subject: [PATCH 054/488] Tests: fix Travis error on Node 0.10. Node 0.10 doesn't have path.parse, so use require('parse-filepath') as a workaround. --- test/test-converter.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/test-converter.js b/test/test-converter.js index c3cfb233..75bb4163 100644 --- a/test/test-converter.js +++ b/test/test-converter.js @@ -3,6 +3,7 @@ var chai = require('chai') , expect = chai.expect , should = chai.should() , path = require('path') + , parsePath = require('parse-filepath') , _ = require('underscore') , FRESHResume = require('../src/core/fresh-resume') , CONVERTER = require('../src/core/convert') @@ -22,7 +23,7 @@ describe('FRESH/JRS converter', function () { var fileB = path.join( __dirname, 'sandbox/richard-hendriks.json' ); _sheet = new FRESHResume().open( fileA ); - MKDIRP.sync( path.parse(fileB).dir ); + MKDIRP.sync( parsePath( fileB ).dirname ); _sheet.saveAs( fileB, 'JRS' ); var rawA = FS.readFileSync( fileA, 'utf8' ); From fb32cb0d7805d2fba12453b9e7c1e93e4d344784 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Thu, 31 Dec 2015 20:00:39 -0500 Subject: [PATCH 055/488] Tests: Bump Johnny's expected duration to 4 years. Happy New Year, everybody. --- test/test-fresh-sheet.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test-fresh-sheet.js b/test/test-fresh-sheet.js index eb2ec7c2..3e55ed2c 100644 --- a/test/test-fresh-sheet.js +++ b/test/test-fresh-sheet.js @@ -59,4 +59,4 @@ function testResume(opts) { var sects = [ 'info', 'employment', 'service', 'skills', 'education', 'writing', 'recognition', 'references' ]; testResume({ title: 'jane-q-fullstacker', path: 'node_modules/jane-q-fullstacker/resume/jane-resume.json', duration: 7, sections: sects }); -testResume({ title: 'johnny-trouble-resume', path: 'node_modules/johnny-trouble-resume/src/johnny-trouble.fresh.json', duration: 3, sections: sects }); +testResume({ title: 'johnny-trouble-resume', path: 'node_modules/johnny-trouble-resume/src/johnny-trouble.fresh.json', duration: 4, sections: sects }); From 3453293c791e15a7e75c0ffcd9679c03c56b86ef Mon Sep 17 00:00:00 2001 From: hacksalot Date: Thu, 31 Dec 2015 20:41:54 -0500 Subject: [PATCH 056/488] Bump version to 1.3.1. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4b22adbc..c6f3eee7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hackmyresume", - "version": "1.3.0", + "version": "1.3.1", "description": "Generate polished résumés and CVs in HTML, Markdown, LaTeX, MS Word, PDF, plain text, JSON, XML, YAML, smoke signal, and carrier pigeon.", "repository": { "type": "git", From 6285c2db3bbfa7ebbce5bd25f54023820bf6299b Mon Sep 17 00:00:00 2001 From: hacksalot Date: Fri, 1 Jan 2016 03:39:48 -0500 Subject: [PATCH 057/488] Introduce "analyze" verb and framework. Introduce a new "analyze" command and start setting up the inspector / analyzer pipeline with a simple "gap analysis" inspector using a reference-counted gap detection approach. --- package.json | 4 +- src/hackmycmd.js | 1 + src/inspectors/gap-inspector.js | 86 +++++++++++++++++++++++++++++++++ src/verbs/analyze.js | 64 ++++++++++++++++++++++++ 4 files changed, 153 insertions(+), 2 deletions(-) create mode 100644 src/inspectors/gap-inspector.js create mode 100644 src/verbs/analyze.js diff --git a/package.json b/package.json index c6f3eee7..bab3436b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hackmyresume", - "version": "1.3.1", + "version": "1.4.0", "description": "Generate polished résumés and CVs in HTML, Markdown, LaTeX, MS Word, PDF, plain text, JSON, XML, YAML, smoke signal, and carrier pigeon.", "repository": { "type": "git", @@ -47,7 +47,7 @@ "dependencies": { "colors": "^1.1.2", "copy": "^0.1.3", - "fresca": "~0.2.2", + "fresca": "~0.2.4", "fresh-themes": "~0.9.3-beta", "fs-extra": "^0.24.0", "handlebars": "^4.0.5", diff --git a/src/hackmycmd.js b/src/hackmycmd.js index ad62e4e7..be9e69b4 100644 --- a/src/hackmycmd.js +++ b/src/hackmycmd.js @@ -25,6 +25,7 @@ Internal resume generation logic for HackMyResume. */ var v = { build: require('./verbs/generate'), + analyze: require('./verbs/analyze'), validate: require('./verbs/validate'), convert: require('./verbs/convert'), new: require('./verbs/create'), diff --git a/src/inspectors/gap-inspector.js b/src/inspectors/gap-inspector.js new file mode 100644 index 00000000..5f24b1cd --- /dev/null +++ b/src/inspectors/gap-inspector.js @@ -0,0 +1,86 @@ +/** +Employment gap analysis for HackMyResume. +@license MIT. See LICENSE.md for details. +@module gap-analyzer.js +*/ + + + +(function() { + + + + var _ = require('underscore'); + var FluentDate = require('../core/fluent-date'); + + + + /** + Identify gaps in the candidate's employment history. + @class gapInspector + */ + var gapInspector = module.exports = { + + + + moniker: 'gap-inspector', + + /** + Run the Gap Analyzer on a resume. + @method run + @return An array of object representing gaps in the candidate's employment + history. Each object provides the start, end, and duration of the gap: + { <-- gap + start: // A Moment.js date + end: // A Moment.js date + duration: // Gap length + } + */ + run: function( rez ) { + + // Convert the candidate's employment history to an array of dates, + // where each element in the array is a start date or an end date of a + // job -- it doesn't matter which. + var new_e = rez.employment.history.map( function( job ){ + var obj = _.pairs( _.pick( job, ['start', 'end'] ) ); + obj[0][1] = FluentDate.fmt( obj[0][1] ); + if( obj.length > 1 ) + obj[1][1] = FluentDate.fmt( obj[1][1] ); + return obj; + }); + + // Flatten the array. + new_e = _.flatten( new_e, true ); + + // Sort the array, mixing start dates and end dates together + new_e = _.sortBy( new_e, function( elem ) { return elem[1].unix(); }); + + // Iterative over elements in the array. Each time a start date is found, + // increment a reference count. Each time an end date is found, decrement + // the reference count. When the reference count reaches 0, we have a gap. + // When the reference count is > 0, the candidate is employed. + var num_gaps = 0, ref_count = 0, gap_start,gaps = []; + new_e.forEach( function(point) { + var inc = point[0] === 'start' ? 1 : -1; + ref_count += inc; + if( ref_count === 0 ) { + gaps.push( { start: point[1], end: null }); + } + else if( ref_count === 1 && inc === 1 ) { + var lastGap = _.last( gaps ); + if( lastGap ) { + lastGap.end = point[1]; + lastGap.duration = lastGap.end.diff( lastGap.start, 'days' ); + } + } + }); + + return gaps; + } + + + }; + + + +}()); diff --git a/src/verbs/analyze.js b/src/verbs/analyze.js new file mode 100644 index 00000000..3392adda --- /dev/null +++ b/src/verbs/analyze.js @@ -0,0 +1,64 @@ +/** +Implementation of the 'analyze' verb for HackMyResume. +@module create.js +@license MIT. See LICENSE.md for details. +*/ + + + +(function(){ + + + + var FLUENT = require('../hackmyapi') + , MKDIRP = require('mkdirp') + , PATH = require('path') + , _ = require('underscore') + , ResumeFactory = require('../core/resume-factory'); + + + + /** + Run the 'analyze' command. + */ + module.exports = function analyze( src, dst, opts, logger ) { + var _log = logger || console.log; + if( !src || !src.length ) throw { fluenterror: 8 }; + var sourceResumes = ResumeFactory.load( src, _log, null, true ); + var nlzrs = _loadInspectors(); + sourceResumes.forEach( function(r) { + _analyze( r, nlzrs, opts, _log ); + }); + }; + + + + /** + Analyze a single resume. + */ + function _analyze( resumeObject, nlzrs, opts, log ) { + var rez = resumeObject.rez; + var safeFormat = rez.meta.format.startsWith('FRESH') ? 'FRESH' : 'JRS'; + log('Analyzing '.useful + safeFormat.useful.bold + + ' resume: '.useful + resumeObject.file.useful.bold); + var info = _.mapObject( nlzrs, function(val, key) { + return val.run( resumeObject.rez ); + }); + + console.log('Gaps: ' + info.gaps.length ); + } + + + + /** + Load inspectors. + */ + function _loadInspectors() { + return { + gaps: require('../inspectors/gap-inspector') + }; + } + + + +}()); From d54b9a6d6c43add6898df19d4742ec9e572ff476 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Fri, 1 Jan 2016 03:45:14 -0500 Subject: [PATCH 058/488] Remove unused method. --- src/core/fresh-resume.js | 53 ---------------------------------------- 1 file changed, 53 deletions(-) diff --git a/src/core/fresh-resume.js b/src/core/fresh-resume.js index 54d08ace..280d6037 100644 --- a/src/core/fresh-resume.js +++ b/src/core/fresh-resume.js @@ -160,59 +160,6 @@ Definition of the FRESHResume class. return this.transformStrings( [], trx ); }; - /** - Create a copy of this resume in which all fields have been interpreted as - Markdown. - */ - FreshResume.prototype.markdownify2 = function() { - - var that = this; - var ret = this.dupe(); - - function MDIN(txt){ - return MD(txt || '' ).replace(/^\s*

|<\/p>\s*$/gi, ''); - } - - // TODO: refactor recursion - function markdownifyStringsInObject( 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 ? MDIN(elem) : MD( elem ); - else - markdownifyStringsInObject( 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','start','end','date'], key) ) - return; - if( key === 'summary' ) - obj[key] = MD( obj[key] ); - else - obj[key] = inline ? MDIN( obj[key] ) : MD( obj[key] ); - } - else - markdownifyStringsInObject( sub ); - }); - } - - } - - Object.keys( ret ).forEach(function(member){ - markdownifyStringsInObject( ret[ member ] ); - }); - - return ret; - }; - /** Convert this object to a JSON string, sanitizing meta-properties along the way. Don't override .toString(). From cb14452df31e28db6db94d276364a329d0d25531 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Fri, 1 Jan 2016 04:44:14 -0500 Subject: [PATCH 059/488] Replace colors with chalk. Chalk has a few more options and doesn't mess around with String.prototype. --- package.json | 2 +- src/core/error-handler.js | 44 +++++++++++++++++++------------------- src/core/resume-factory.js | 4 ++-- src/hackmycmd.js | 7 +++--- src/index.js | 26 +++++++++++----------- src/verbs/analyze.js | 4 ++-- src/verbs/convert.js | 6 +++--- src/verbs/create.js | 4 ++-- src/verbs/generate.js | 14 ++++++------ src/verbs/validate.js | 29 +++++++++++++------------ 10 files changed, 72 insertions(+), 68 deletions(-) diff --git a/package.json b/package.json index bab3436b..ef9583ad 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ }, "homepage": "https://github.com/hacksalot/HackMyResume", "dependencies": { - "colors": "^1.1.2", + "chalk": "^1.1.1", "copy": "^0.1.3", "fresca": "~0.2.4", "fresh-themes": "~0.9.3-beta", diff --git a/src/core/error-handler.js b/src/core/error-handler.js index a2ade774..62d4af76 100644 --- a/src/core/error-handler.js +++ b/src/core/error-handler.js @@ -9,7 +9,8 @@ , FS = require('fs') , FCMD = require('../hackmycmd') , PATH = require('path') - , title = ('\n*** HackMyResume v' + PKG.version + ' ***').bold.white; + , chalk = require('chalk') + , title = chalk.white.bold('\n*** HackMyResume v' + PKG.version + ' ***'); var ErrorHandler = module.exports = { @@ -29,46 +30,45 @@ break; case HACKMYSTATUS.resumeNotFound: - msg = 'Please '.guide + 'feed me a resume'.guide.bold + - ' in FRESH or JSON Resume format.'.guide; + msg = chalk.yellow('Please ') + chalk.yellow.bold('feed me a resume') + + chalk.yellow(' in FRESH or JSON Resume format.'); break; case HACKMYSTATUS.missingCommand: - msg = title + "\nPlease ".guide + "give me a command".guide.bold + - " (".guide; + msg = title + chalk.yellow("\nPlease ") + chalk.yellow.bold("give me a command") + + chalk.yellow(" ("); msg += Object.keys( FCMD.verbs ).map( function(v, idx, ar) { - return (idx === ar.length - 1 ? 'or '.guide : '') + - v.toUpperCase().guide; - }).join(', '.guide) + ").\n\n".guide; + return (idx === ar.length - 1 ? chalk.yellow('or ') : '') + + chalk.yellow.bold(v.toUpperCase()); + }).join( chalk.yellow(', ')) + chalk.yellow(").\n\n"); - msg += FS.readFileSync( - PATH.resolve(__dirname, '../use.txt'), 'utf8' ).info.bold; + msg += chalk.gray(FS.readFileSync( PATH.resolve(__dirname, '../use.txt'), 'utf8' )); break; case HACKMYSTATUS.invalidCommand: - msg = 'Please '.guide + 'specify the output resume file'.guide.bold + - ' that should be created.'.guide; + msg = chalk.yellow('Please ') + chalk.yellow.bold('specify the output resume file') + + chalk.yellow(' that should be created.'); break; case HACKMYSTATUS.resumeNotFoundAlt: - msg = 'Please '.guide + 'feed me a resume'.guide.bold + - ' in either FRESH or JSON Resume format.'.guide; + msg = chalk.yellow('Please ') + chalk.yellow.bold('feed me a resume') + + chalk.yellow(' in either FRESH or JSON Resume format.'); break; case HACKMYSTATUS.inputOutputParity: - msg = 'Please '.guide + 'specify an output file name'.guide.bold + - ' for every input file you wish to convert.'.guide; + msg = chalk.yellow('Please ') + chalk.yellow.bold('specify an output file name') + + chalk.yellow(' for every input file you wish to convert.'); break; case HACKMYSTATUS.createNameMissing: - msg = 'Please '.guide + 'specify the filename of the resume'.guide.bold + - ' to create.'.guide; + msg = chalk.yellow('Please ') + chalk.yellow.bold('specify the filename of the resume') + + chalk.yellow(' to create.'); break; case HACKMYSTATUS.wkhtmltopdf: - msg = 'ERROR: PDF generation failed. '.red.bold + ('Make sure wkhtmltopdf is ' + - 'installed and accessible from your path.').red; + msg = chalk.red.bold('ERROR: PDF generation failed. ') + chalk.red('Make sure wkhtmltopdf is ' + + 'installed and accessible from your path.'); break; } @@ -83,8 +83,8 @@ var idx = msg.indexOf('Error: '); var trimmed = idx === -1 ? msg : msg.substring( idx + 7 ); if( !ex.fluenterror || ex.fluenterror < 3 ) { // TODO: magic #s - console.log( ('ERROR: ' + trimmed.toString()).red.bold ); - console.log( ex.stack.gray); + console.log( chalk.red.bold('ERROR: ' + trimmed.toString()) ); + console.log( chalk.gray(ex.stack) ); } else { console.log( trimmed.toString() ); diff --git a/src/core/resume-factory.js b/src/core/resume-factory.js index b8d42656..08becdca 100644 --- a/src/core/resume-factory.js +++ b/src/core/resume-factory.js @@ -13,6 +13,7 @@ Definition of the ResumeFactory class. require('string.prototype.startswith'); var FS = require('fs'); var ResumeConverter = require('./convert'); + var chalk = require('chalk'); @@ -85,8 +86,7 @@ Definition of the ResumeFactory class. try { // TODO: Core should not log - log( 'Reading '.info + /*orgFormat.toUpperCase().infoBold +*/ - 'resume: '.info + fileName.cyan.bold ); + log( chalk.gray('Reading resume: ') + chalk.cyan.bold(fileName) ); rawData = FS.readFileSync( fileName, 'utf8' ); return { diff --git a/src/hackmycmd.js b/src/hackmycmd.js index be9e69b4..75f69f3c 100644 --- a/src/hackmycmd.js +++ b/src/hackmycmd.js @@ -9,7 +9,8 @@ Internal resume generation logic for HackMyResume. var unused = require('./utils/string') , PATH = require('path') - , FS = require('fs'); + , FS = require('fs') + , chalk = require('chalk'); /** @@ -17,7 +18,7 @@ Internal resume generation logic for HackMyResume. */ function help() { var manPage = FS.readFileSync( PATH.join(__dirname, 'use.txt'), 'utf8' ); - console.log( manPage.useful.bold ); + console.log( chalk.green.bold(manPage) ); } /** @@ -25,7 +26,7 @@ Internal resume generation logic for HackMyResume. */ var v = { build: require('./verbs/generate'), - analyze: require('./verbs/analyze'), + analyze: require('./verbs/analyze'), validate: require('./verbs/validate'), convert: require('./verbs/convert'), new: require('./verbs/create'), diff --git a/src/index.js b/src/index.js index 904d7900..81fa89f8 100644 --- a/src/index.js +++ b/src/index.js @@ -12,12 +12,12 @@ var SPAWNW = require('./core/spawn-watch') , ARGS = require( 'minimist' ) , FCMD = require( './hackmycmd') , PKG = require('../package.json') - , COLORS = require('colors') , FS = require('fs') + , chalk = require('chalk') , PATH = require('path') , HACKMYSTATUS = require('./core/status-codes') , opts = { } - , title = ('\n*** HackMyResume v' + PKG.version + ' ***').bold.white + , title = chalk.white('\n*** HackMyResume v' + PKG.version + ' ***') , _ = require('underscore'); @@ -35,16 +35,16 @@ catch( ex ) { function main() { // Colorize - COLORS.setTheme({ - title: ['white','bold'], - info: process.platform === 'win32' ? 'gray' : ['white','dim'], - infoBold: ['white','dim'], - warn: 'yellow', - error: 'red', - guide: 'yellow', - status: 'gray',//['white','dim'], - useful: 'green', - }); + // COLORS.setTheme({ + // title: ['white','bold'], + // info: process.platform === 'win32' ? 'gray' : ['white','dim'], + // infoBold: ['white','dim'], + // warn: 'yellow', + // error: 'red', + // guide: 'yellow', + // status: 'gray',//['white','dim'], + // useful: 'green', + // }); // Setup if( process.argv.length <= 2 ) { throw { fluenterror: 4 }; } @@ -56,7 +56,7 @@ function main() { var params = a._.map( function(p){ return p.toLowerCase().trim(); }); var verb = params[0]; if( !FCMD.verbs[ verb ] && !FCMD.alias[ verb ] ) { - logMsg('Invalid command: "'.warn + verb.warn.bold + '"'.warn); + logMsg(chalk.yellow('Invalid command: "') + chalk.yellow.bold(verb) + chalk.yellow('"')); return; } diff --git a/src/verbs/analyze.js b/src/verbs/analyze.js index 3392adda..cf5b395a 100644 --- a/src/verbs/analyze.js +++ b/src/verbs/analyze.js @@ -39,8 +39,8 @@ Implementation of the 'analyze' verb for HackMyResume. function _analyze( resumeObject, nlzrs, opts, log ) { var rez = resumeObject.rez; var safeFormat = rez.meta.format.startsWith('FRESH') ? 'FRESH' : 'JRS'; - log('Analyzing '.useful + safeFormat.useful.bold + - ' resume: '.useful + resumeObject.file.useful.bold); + log(chalk.cyan('Analyzing ') + chalk.cyan.bold(safeFormat) + + chalk.cyan(' resume: ') + chalk.cyan.bold(resumeObject.file)); var info = _.mapObject( nlzrs, function(val, key) { return val.run( resumeObject.rez ); }); diff --git a/src/verbs/convert.js b/src/verbs/convert.js index a2f80feb..50b1a62b 100644 --- a/src/verbs/convert.js +++ b/src/verbs/convert.js @@ -27,9 +27,9 @@ Implementation of the 'convert' verb for HackMyResume. var sheet = src.rez; var sourceFormat = ((sheet.basics && sheet.basics.imp) || sheet.imp).orgFormat === 'JRS' ? 'JRS' : 'FRESH'; var targetFormat = sourceFormat === 'JRS' ? 'FRESH' : 'JRS'; - _log( 'Converting '.useful + src.file.useful.bold + (' (' + - sourceFormat + ') to ').useful + dst[0].useful.bold + - (' (' + targetFormat + ').').useful ); + _log( chalk.green('Converting ') + chalk.green.bold(src.file) + chalk.green(' (' + + sourceFormat + ') to ') + chalk.green.bold(dst[0]) + + chalk.green(' (' + targetFormat + ').') ); sheet.saveAs( dst[idx], targetFormat ); }); }; diff --git a/src/verbs/create.js b/src/verbs/create.js index 11833207..7b9fd6a2 100644 --- a/src/verbs/create.js +++ b/src/verbs/create.js @@ -18,8 +18,8 @@ Implementation of the 'create' verb for HackMyResume. if( !src || !src.length ) throw { fluenterror: 8 }; src.forEach( function( t ) { var safeFormat = opts.format.toUpperCase(); - _log('Creating new '.useful +safeFormat.useful.bold + - ' resume: '.useful + t.useful.bold); + _log(chalk.green('Creating new ') + chalk.green.bold(safeFormat) + + chalk.green(' resume: ') + chalk.green.bold(t)); MKDIRP.sync( PATH.dirname( t ) ); // Ensure dest folder exists; FLUENT[ safeFormat + 'Resume' ].default().save( t ); }); diff --git a/src/verbs/generate.js b/src/verbs/generate.js index 7dea1a59..5b7de95e 100644 --- a/src/verbs/generate.js +++ b/src/verbs/generate.js @@ -21,6 +21,7 @@ Implementation of the 'generate' verb for HackMyResume. , _ = require('underscore') , _fmts = require('../core/default-formats') , extend = require('../utils/extend') + , chalk = require('chalk') , _err, _log, rez; @@ -64,9 +65,10 @@ Implementation of the 'generate' verb for HackMyResume. var msg = ''; var rezRep = _.reduceRight( sheets, function( a, b, idx ) { msg += ((idx == sheets.length - 2) ? - 'Merging '.gray + a.rez.imp.fileName : '') + ' onto '.gray + b.rez.fileName; + chalk.gray('Merging ') + a.rez.imp.fileName : '') + chalk.gray(' onto ') + b.rez.fileName; return extend( true, b.rez, a.rez ); }); + rez = rezRep.rez; msg && _log(msg); @@ -102,9 +104,9 @@ Implementation of the 'generate' verb for HackMyResume. , fName = PATH.basename(f, '.' + fType) , theFormat; - _log( 'Generating '.useful + - targInfo.fmt.outFormat.toUpperCase().useful.bold + - ' resume: '.useful + PATH.relative(process.cwd(), f ).useful.bold ); + _log( chalk.green('Generating ') + + chalk.green.bold(targInfo.fmt.outFormat.toUpperCase()) + + chalk.green(' resume: ') + chalk.green.bold( PATH.relative(process.cwd(), f )) ); // If targInfo.fmt.files exists, this format is backed by a document. // Fluent/FRESH themes are handled here. @@ -273,8 +275,8 @@ Implementation of the 'generate' verb for HackMyResume. // Output a message TODO: core should not log var numFormats = Object.keys(theTheme.formats).length; - _log( 'Applying '.info + theTheme.name.toUpperCase().infoBold + - (' theme (' + numFormats + ' formats)').info); + _log( chalk.gray('Applying ') + chalk.gray.bold(theTheme.name.toUpperCase()) + + chalk.gray(' theme (' + numFormats + ' formats)')); return theTheme; } diff --git a/src/verbs/validate.js b/src/verbs/validate.js index a2c89f29..0d12ce44 100644 --- a/src/verbs/validate.js +++ b/src/verbs/validate.js @@ -9,6 +9,7 @@ Implementation of the 'validate' verb for HackMyResume. var FS = require('fs'); var ResumeFactory = require('../core/resume-factory'); var SyntaxErrorEx = require('../utils/syntax-error-ex'); + var chalk = require('chalk'); module.exports = @@ -31,25 +32,25 @@ Implementation of the 'validate' verb for HackMyResume. var result = ResumeFactory.loadOne( src, function(){}, null, false ); if( result.error ) { - _log( 'Validating '.info + src.infoBold + ' against '.info + 'AUTO'.infoBold + ' schema:'.info + ' BROKEN'.red.bold ); + _log( chalk.white('Validating ') + chalk.gray.bold(src) + chalk.white(' against ') + chalk.gray.bold('AUTO') + chalk.white(' schema:') + chalk.red.bold(' BROKEN') ); var ex = result.error; // alias if ( ex instanceof SyntaxError) { var info = new SyntaxErrorEx( ex, result.raw ); - _log( ('--> '.warn.bold + src.toUpperCase() + ' contains invalid JSON on line ' + - info.line + ' column ' + info.col + '.').warn + - ' Unable to validate.'.warn ); - _log( (' INTERNAL: ' + ex).warn ); + _log( chalk.red.bold('--> ') + chalk.red(src.toUpperCase() + ' contains invalid JSON on line ' + + info.line + ' column ' + info.col + '.') + + chalk.red(' Unable to validate.') ); + _log( chalk.red(' INTERNAL: ' + ex) ); } else { - _log(('ERROR: ' + ex.toString()).warn.bold); + _log(chalk.red.bold('ERROR: ' + ex.toString())); } return; } var json = result.json; var isValid = false; - var style = 'useful'; + var style = 'green'; var errors = []; var fmt = json.meta && (json.meta.format==='FRESH@0.1.0') ? 'fresh':'jars'; @@ -62,7 +63,7 @@ Implementation of the 'validate' verb for HackMyResume. isValid = validate( json ); if( !isValid ) { - style = 'warn'; + style = 'yellow'; errors = validate.errors; } @@ -71,14 +72,14 @@ Implementation of the 'validate' verb for HackMyResume. return; } - _log( 'Validating '.info + result.file.infoBold + ' against '.info + - fmt.replace('jars','JSON Resume').toUpperCase().infoBold + - ' schema: '.info + (isValid ? 'VALID!' : 'INVALID')[style].bold ); + _log( chalk.white('Validating ') + chalk.white.bold(result.file) + chalk.white(' against ') + + chalk.white.bold(fmt.replace('jars','JSON Resume').toUpperCase()) + + chalk.white(' schema: ') + chalk[style].bold(isValid ? 'VALID!' : 'INVALID') ); errors.forEach(function(err,idx) { - _log( '--> '.bold.yellow + - (err.field.replace('data.','resume.').toUpperCase() + ' ' + - err.message).yellow ); + _log( chalk.yellow.bold('--> ') + + chalk.yellow(err.field.replace('data.','resume.').toUpperCase() + ' ' + + err.message) ); }); }); From b96526da3173c3f4b7b19e346aab7a9ffabc1d94 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Fri, 1 Jan 2016 04:48:20 -0500 Subject: [PATCH 060/488] Replace chalk with colors in tests. --- test/test-cli.js | 14 +------------- test/test-themes.js | 14 +------------- 2 files changed, 2 insertions(+), 26 deletions(-) diff --git a/test/test-cli.js b/test/test-cli.js index 666eaf7c..86fb8799 100644 --- a/test/test-cli.js +++ b/test/test-cli.js @@ -6,8 +6,7 @@ var chai = require('chai') , _ = require('underscore') , FRESHResume = require('../src/core/fresh-resume') , FCMD = require( '../src/hackmycmd') - , validator = require('is-my-json-valid') - , COLORS = require('colors'); + , validator = require('is-my-json-valid'); chai.config.includeStack = false; @@ -19,17 +18,6 @@ describe('Testing CLI interface', function () { } - COLORS.setTheme({ - title: ['white','bold'], - info: process.platform === 'win32' ? 'gray' : ['white','dim'], - infoBold: ['white','dim'], - warn: 'yellow', - error: 'red', - guide: 'yellow', - status: 'gray',//['white','dim'], - useful: 'green', - }); - var opts = { //theme: 'compact', format: 'FRESH', diff --git a/test/test-themes.js b/test/test-themes.js index ba9496a5..c26c7f89 100644 --- a/test/test-themes.js +++ b/test/test-themes.js @@ -7,8 +7,7 @@ var SPAWNWATCHER = require('../src/core/spawn-watch') , _ = require('underscore') , FRESHResume = require('../src/core/fresh-resume') , FCMD = require( '../src/hackmycmd') - , validator = require('is-my-json-valid') - , COLORS = require('colors'); + , validator = require('is-my-json-valid'); chai.config.includeStack = false; @@ -18,17 +17,6 @@ function genThemes( title, src, fmt ) { var _sheet; - COLORS.setTheme({ - title: ['white','bold'], - info: process.platform === 'win32' ? 'gray' : ['white','dim'], - infoBold: ['white','dim'], - warn: 'yellow', - error: 'red', - guide: 'yellow', - status: 'gray',//['white','dim'], - useful: 'green', - }); - function genTheme( fmt, src, themeName, themeLoc, testTitle ) { themeLoc = themeLoc || themeName; testTitle = themeName.toUpperCase() + ' theme (' + fmt + ') should generate without throwing an exception'; From 84ad6cf35614d96ee25679087330b91fd6a7b99a Mon Sep 17 00:00:00 2001 From: hacksalot Date: Fri, 1 Jan 2016 04:57:50 -0500 Subject: [PATCH 061/488] Add missing chalk references. --- src/verbs/analyze.js | 3 ++- src/verbs/convert.js | 1 + src/verbs/create.js | 3 ++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/verbs/analyze.js b/src/verbs/analyze.js index cf5b395a..7bdb557d 100644 --- a/src/verbs/analyze.js +++ b/src/verbs/analyze.js @@ -14,7 +14,8 @@ Implementation of the 'analyze' verb for HackMyResume. , MKDIRP = require('mkdirp') , PATH = require('path') , _ = require('underscore') - , ResumeFactory = require('../core/resume-factory'); + , ResumeFactory = require('../core/resume-factory') + , chalk = require('chalk'); diff --git a/src/verbs/convert.js b/src/verbs/convert.js index 50b1a62b..1d4a1391 100644 --- a/src/verbs/convert.js +++ b/src/verbs/convert.js @@ -7,6 +7,7 @@ Implementation of the 'convert' verb for HackMyResume. (function(){ var ResumeFactory = require('../core/resume-factory'); + var chalk = require('chalk'); /** Convert between FRESH and JRS formats. diff --git a/src/verbs/create.js b/src/verbs/create.js index 7b9fd6a2..c2d1c8f9 100644 --- a/src/verbs/create.js +++ b/src/verbs/create.js @@ -8,7 +8,8 @@ Implementation of the 'create' verb for HackMyResume. var FLUENT = require('../hackmyapi') , MKDIRP = require('mkdirp') - , PATH = require('path'); + , PATH = require('path') + , chalk = require('chalk'); /** Create a new empty resume in either FRESH or JRS format. From 49e56cc226da3db9b756955d043e5c74635144e9 Mon Sep 17 00:00:00 2001 From: TJ Lavelle Date: Fri, 1 Jan 2016 11:27:05 -0500 Subject: [PATCH 062/488] Fix relative theme directory The theme directory assumes it was a child of the HackMyResume module, but NPM3 will actually flatten this out. Following the same logic that the template-generator uses, find the path to the themes using NPMs require method. --- src/verbs/generate.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/verbs/generate.js b/src/verbs/generate.js index 7dea1a59..9caa0410 100644 --- a/src/verbs/generate.js +++ b/src/verbs/generate.js @@ -242,9 +242,8 @@ Implementation of the 'generate' verb for HackMyResume. Verify the specified theme name/path. */ function verify_theme( themeNameOrPath ) { - var tFolder = PATH.resolve( - __dirname, - '../../node_modules/fresh-themes/themes', + var tFolder = PATH.join( + parsePath ( require.resolve('fresh-themes') ).dirname, themeNameOrPath ); var exists = require('path-exists').sync; From bb28e5aa8e4f517bef4f864cbdf69e09c589c33c Mon Sep 17 00:00:00 2001 From: hacksalot Date: Fri, 1 Jan 2016 14:38:00 -0500 Subject: [PATCH 063/488] Support --help option. Support standard syntax for the HELP command. --- src/index.js | 25 +++++++------------------ 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/src/index.js b/src/index.js index 81fa89f8..b54a1a57 100644 --- a/src/index.js +++ b/src/index.js @@ -34,18 +34,6 @@ catch( ex ) { function main() { - // Colorize - // COLORS.setTheme({ - // title: ['white','bold'], - // info: process.platform === 'win32' ? 'gray' : ['white','dim'], - // infoBold: ['white','dim'], - // warn: 'yellow', - // error: 'red', - // guide: 'yellow', - // status: 'gray',//['white','dim'], - // useful: 'green', - // }); - // Setup if( process.argv.length <= 2 ) { throw { fluenterror: 4 }; } var a = ARGS( process.argv.slice(2) ); @@ -54,7 +42,7 @@ function main() { // Get the action to be performed var params = a._.map( function(p){ return p.toLowerCase().trim(); }); - var verb = params[0]; + var verb = opts.help ? 'help' : params[0]; if( !FCMD.verbs[ verb ] && !FCMD.alias[ verb ] ) { logMsg(chalk.yellow('Invalid command: "') + chalk.yellow.bold(verb) + chalk.yellow('"')); return; @@ -62,11 +50,11 @@ function main() { // Find the TO keyword, if any var splitAt = _.indexOf( params, 'to' ); - if( splitAt === a._.length - 1 ) { + if( splitAt === a._.length - 1 && splitAt !== -1 ) { // 'TO' cannot be the last argument - logMsg('Please '.warn + 'specify an output file'.warn.bold + - ' for this operation or '.warn + 'omit the TO keyword'.warn.bold + - '.'.warn ); + logMsg(chalk.yellow('Please ') + chalk.yellow.bold('specify an output file') + + chalk.yellow(' for this operation or ') + chalk.yellow.bold('omit the TO keyword') + + chalk.yellow('.') ); return; } @@ -92,6 +80,7 @@ function getOpts( args ) { format: args.f || 'FRESH', prettify: !noPretty, silent: args.s || args.silent, - css: args.css || 'embed' + css: args.css || 'embed', + help: args.help || undefined }; } From 43873efcab86804bb2f009a516256b718408e1a7 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Fri, 1 Jan 2016 14:38:52 -0500 Subject: [PATCH 064/488] Tweak analyze command error. --- src/verbs/analyze.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/verbs/analyze.js b/src/verbs/analyze.js index 7bdb557d..d8d177bb 100644 --- a/src/verbs/analyze.js +++ b/src/verbs/analyze.js @@ -24,7 +24,7 @@ Implementation of the 'analyze' verb for HackMyResume. */ module.exports = function analyze( src, dst, opts, logger ) { var _log = logger || console.log; - if( !src || !src.length ) throw { fluenterror: 8 }; + if( !src || !src.length ) throw { fluenterror: 3 }; var sourceResumes = ResumeFactory.load( src, _log, null, true ); var nlzrs = _loadInspectors(); sourceResumes.forEach( function(r) { From 37a7c318d5db20a1343598a81dba93f81e83934f Mon Sep 17 00:00:00 2001 From: hacksalot Date: Fri, 1 Jan 2016 14:58:56 -0500 Subject: [PATCH 065/488] Remove stack trace for ENOENT. --- src/core/error-handler.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/core/error-handler.js b/src/core/error-handler.js index 62d4af76..cecfa3de 100644 --- a/src/core/error-handler.js +++ b/src/core/error-handler.js @@ -84,7 +84,8 @@ var trimmed = idx === -1 ? msg : msg.substring( idx + 7 ); if( !ex.fluenterror || ex.fluenterror < 3 ) { // TODO: magic #s console.log( chalk.red.bold('ERROR: ' + trimmed.toString()) ); - console.log( chalk.gray(ex.stack) ); + if( ex.code !== 'ENOENT' ) // Don't emit stack for common stuff + console.log( chalk.gray(ex.stack) ); } else { console.log( trimmed.toString() ); From 5a2d892b850d7bfbdc112b11e9fb78c480df2f10 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Fri, 1 Jan 2016 14:59:21 -0500 Subject: [PATCH 066/488] Scrub error-handler.js. --- src/core/error-handler.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/core/error-handler.js b/src/core/error-handler.js index cecfa3de..b1a6b191 100644 --- a/src/core/error-handler.js +++ b/src/core/error-handler.js @@ -1,9 +1,15 @@ /** +Error-handling routines for HackMyResume. @module error-handler.js +@license MIT. See LICENSE.md for details. */ + + (function() { + + var HACKMYSTATUS = require('./status-codes') , PKG = require('../../package.json') , FS = require('fs') @@ -12,9 +18,17 @@ , chalk = require('chalk') , title = chalk.white.bold('\n*** HackMyResume v' + PKG.version + ' ***'); + + + /** + An amorphous blob of error handling code for HackMyResume. Have an error? + Stick it here. We don't mind. + @class ErrorHandler + */ var ErrorHandler = module.exports = { + err: function( ex, shouldExit ) { var msg = '', exitCode; @@ -80,8 +94,12 @@ exitCode = 4; } + // Deal with pesky 'Error:' prefix. var idx = msg.indexOf('Error: '); var trimmed = idx === -1 ? msg : msg.substring( idx + 7 ); + + // If this is an unhandled error, or a specific class of handled error, + // output the error message and stack. if( !ex.fluenterror || ex.fluenterror < 3 ) { // TODO: magic #s console.log( chalk.red.bold('ERROR: ' + trimmed.toString()) ); if( ex.code !== 'ENOENT' ) // Don't emit stack for common stuff @@ -91,6 +109,7 @@ console.log( trimmed.toString() ); } + // Let the error code be the process's return code. if( shouldExit ) process.exit( exitCode ); From 2b669cf35c0f7aabac4cf6c16d93f7704775091f Mon Sep 17 00:00:00 2001 From: hacksalot Date: Fri, 1 Jan 2016 15:06:16 -0500 Subject: [PATCH 067/488] Tweak error handling for cmd params. --- src/core/resume-factory.js | 12 +++++++++++- src/index.js | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/core/resume-factory.js b/src/core/resume-factory.js index 08becdca..8bf0f83e 100644 --- a/src/core/resume-factory.js +++ b/src/core/resume-factory.js @@ -88,17 +88,27 @@ Definition of the ResumeFactory class. // TODO: Core should not log log( chalk.gray('Reading resume: ') + chalk.cyan.bold(fileName) ); + // Read the file rawData = FS.readFileSync( fileName, 'utf8' ); + + // Parse it to JSON return { json: JSON.parse( rawData ) }; } - catch(ex) { + catch( ex ) { + + // If FS.readFileSync threw, pass the exception along. + if (!rawData) + throw ex; + + // Otherwise if JSON.parse failed: probably a SyntaxError. return { error: ex, raw: rawData }; + } } diff --git a/src/index.js b/src/index.js index b54a1a57..dad71a75 100644 --- a/src/index.js +++ b/src/index.js @@ -35,8 +35,8 @@ catch( ex ) { function main() { // Setup - if( process.argv.length <= 2 ) { throw { fluenterror: 4 }; } var a = ARGS( process.argv.slice(2) ); + if( a._.length === 0 ) { throw { fluenterror: 4 }; } opts = getOpts( a ); logMsg( title ); From 8c8dbfed72f8a73362df558305f993a2d8d590c9 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Fri, 1 Jan 2016 15:06:36 -0500 Subject: [PATCH 068/488] Adjust test paths. --- test/test-cli.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test-cli.js b/test/test-cli.js index 86fb8799..2215c5e6 100644 --- a/test/test-cli.js +++ b/test/test-cli.js @@ -40,8 +40,8 @@ describe('Testing CLI interface', function () { run( 'validate', ['node_modules/jane-q-fullstacker/resume/jane-resume.json'], [], opts, ' (jane-q-fullstacker|FRESH)' ); run( 'validate', ['node_modules/johnny-trouble-resume/src/johnny-trouble.fresh.json'], [], opts, ' (johnny-trouble|FRESH)' ); run( 'validate', ['test/sandbox/new-fresh-resume.json'], [], opts, ' (new-fresh-resume|FRESH)' ); - run( 'validate', ['test/sandbox/resumes/jrs-0.0.0/ruchard-hendriks.json'], [], opts2, ' (richard-hendriks.json|JRS)' ); - run( 'validate', ['test/sandbox/resumes/jrs-0.0.0/jane-incomplete.json'], [], opts2, ' (jane-incomplete.json|JRS)' ); + run( 'validate', ['test/resumes/jrs-0.0.0/richard-hendriks.json'], [], opts2, ' (richard-hendriks.json|JRS)' ); + run( 'validate', ['test/resumes/jrs-0.0.0/jane-incomplete.json'], [], opts2, ' (jane-incomplete.json|JRS)' ); run( 'validate', ['test/sandbox/new-1.json','test/sandbox/new-jrs-resume.json','test/sandbox/new-1.json', 'test/sandbox/new-2.json', 'test/sandbox/new-3.json'], [], opts, ' (5|BOTH)' ); function run( verb, src, dst, opts, msg ) { From 13fc903b2b50791444e668ab5a42b92dbc0bf175 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Fri, 1 Jan 2016 17:20:42 -0500 Subject: [PATCH 069/488] Catch JSON syntax errors for all commands. ...and emit line/column info. --- src/core/error-handler.js | 6 ++++++ src/core/resume-factory.js | 36 ++++++++++++++++++++++++------------ src/verbs/convert.js | 2 +- src/verbs/generate.js | 4 +++- src/verbs/validate.js | 21 +++++++++++++++------ 5 files changed, 49 insertions(+), 20 deletions(-) diff --git a/src/core/error-handler.js b/src/core/error-handler.js index b1a6b191..358e67e1 100644 --- a/src/core/error-handler.js +++ b/src/core/error-handler.js @@ -32,6 +32,12 @@ Error-handling routines for HackMyResume. err: function( ex, shouldExit ) { var msg = '', exitCode; + if( ex.handled ) { + if( shouldExit ) + process.exit( exitCode ); + return; + } + if( ex.fluenterror ){ switch( ex.fluenterror ) { // TODO: Remove magic numbers diff --git a/src/core/resume-factory.js b/src/core/resume-factory.js index 8bf0f83e..dda26ccf 100644 --- a/src/core/resume-factory.js +++ b/src/core/resume-factory.js @@ -14,6 +14,7 @@ Definition of the ResumeFactory class. var FS = require('fs'); var ResumeConverter = require('./convert'); var chalk = require('chalk'); + var SyntaxErrorEx = require('../utils/syntax-error-ex'); @@ -28,12 +29,13 @@ Definition of the ResumeFactory class. /** Load one or more resumes from disk. */ - load: function ( sources, log, toFormat, objectify ) { + load: function ( sources, opts ) { + // Loop over all inputs, parsing each to JSON and then to a FRESHResume // or JRSResume object. var that = this; return sources.map( function( src ) { - return that.loadOne( src, log, toFormat, objectify ); + return that.loadOne( src, opts ); }); }, @@ -43,17 +45,21 @@ Definition of the ResumeFactory class. /** Load a single resume from disk. */ - loadOne: function( src, log, toFormat, objectify ) { + loadOne: function( src, opts ) { + + var log = opts.log; + var toFormat = opts.format; + 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, log, toFormat ); + var info = _parse( src, opts ); if( info.error ) return info; - var json = info.json; // 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'; @@ -81,12 +87,12 @@ Definition of the ResumeFactory class. - function _parse( fileName, log, toFormat ) { + function _parse( fileName, opts ) { var rawData; try { // TODO: Core should not log - log( chalk.gray('Reading resume: ') + chalk.cyan.bold(fileName) ); + opts.log( chalk.gray('Reading resume: ') + chalk.cyan.bold(fileName) ); // Read the file rawData = FS.readFileSync( fileName, 'utf8' ); @@ -99,12 +105,18 @@ Definition of the ResumeFactory class. } catch( ex ) { - // If FS.readFileSync threw, pass the exception along. - if (!rawData) - throw ex; + // JSON.parse failed due to invalid JSON + if ( ex instanceof SyntaxError) { + var info = new SyntaxErrorEx( ex, rawData ); + opts.log( chalk.red.bold(fileName.toUpperCase() + ' contains invalid JSON on line ' + + info.line + ' column ' + info.col + '.' + + chalk.red(' Unable to validate.'))); + opts.log( chalk.red.bold('INTERNAL: ' + ex) ); + ex.handled = true; + } - // Otherwise if JSON.parse failed: probably a SyntaxError. - return { + if( opts.throw ) throw ex; + else return { error: ex, raw: rawData }; diff --git a/src/verbs/convert.js b/src/verbs/convert.js index 1d4a1391..c70196d3 100644 --- a/src/verbs/convert.js +++ b/src/verbs/convert.js @@ -23,7 +23,7 @@ Implementation of the 'convert' verb for HackMyResume. if( sources && dst && sources.length && dst.length && sources.length !== dst.length ) { throw { fluenterror: 7 }; } - var sourceResumes = ResumeFactory.load( sources, _log, null, true ); + var sourceResumes = ResumeFactory.load( sources, { log: _log, format: null, objectify: true, throw: true } ); sourceResumes.forEach(function( src, idx ) { var sheet = src.rez; var sourceFormat = ((sheet.basics && sheet.basics.imp) || sheet.imp).orgFormat === 'JRS' ? 'JRS' : 'FRESH'; diff --git a/src/verbs/generate.js b/src/verbs/generate.js index 5b7de95e..50db299a 100644 --- a/src/verbs/generate.js +++ b/src/verbs/generate.js @@ -59,7 +59,9 @@ Implementation of the 'generate' verb for HackMyResume. // Load input resumes... if( !src || !src.length ) { throw { fluenterror: 3 }; } - var sheets = ResumeFactory.load(src, _log, theme.render ? 'JRS' : 'FRESH', true); + var sheets = ResumeFactory.load(src, { + log: _log, format: theme.render ? 'JRS' : 'FRESH', objectify: true, throw: true + }); // Merge input resumes... var msg = ''; diff --git a/src/verbs/validate.js b/src/verbs/validate.js index 0d12ce44..2d7f744a 100644 --- a/src/verbs/validate.js +++ b/src/verbs/validate.js @@ -30,17 +30,26 @@ Implementation of the 'validate' verb for HackMyResume. // Load input resumes... sources.forEach(function( src ) { - var result = ResumeFactory.loadOne( src, function(){}, null, false ); + var result = ResumeFactory.loadOne( src, { + log: function(){}, + format: null, + objectify: false, + throw: false + }); + if( result.error ) { - _log( chalk.white('Validating ') + chalk.gray.bold(src) + chalk.white(' against ') + chalk.gray.bold('AUTO') + chalk.white(' schema:') + chalk.red.bold(' BROKEN') ); + // TODO: Core should not log + _log( chalk.white('Validating ') + chalk.gray.bold(src) + + chalk.white(' against ') + chalk.gray.bold('AUTO') + + chalk.white(' schema:') + chalk.red.bold(' BROKEN') ); var ex = result.error; // alias if ( ex instanceof SyntaxError) { var info = new SyntaxErrorEx( ex, result.raw ); - _log( chalk.red.bold('--> ') + chalk.red(src.toUpperCase() + ' contains invalid JSON on line ' + - info.line + ' column ' + info.col + '.') + - chalk.red(' Unable to validate.') ); - _log( chalk.red(' INTERNAL: ' + ex) ); + _log( chalk.red.bold('--> ' + src.toUpperCase() + ' contains invalid JSON on line ' + + info.line + ' column ' + info.col + '.' + + chalk.red(' Unable to validate.') ) ); + _log( chalk.red.bold(' INTERNAL: ' + ex) ); } else { _log(chalk.red.bold('ERROR: ' + ex.toString())); From 889bd4bfc51eb74b4e6d91e1978a72e12102a6a7 Mon Sep 17 00:00:00 2001 From: Josh Janusch Date: Fri, 1 Jan 2016 17:27:49 -0500 Subject: [PATCH 070/488] formatDate helper now will only use moment if date is valid. If it's not, will use the user inputted value or a fallback parameter, if it is provided --- src/eng/generic-helpers.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/eng/generic-helpers.js b/src/eng/generic-helpers.js index 3f6d92ad..86a75e9a 100644 --- a/src/eng/generic-helpers.js +++ b/src/eng/generic-helpers.js @@ -21,10 +21,20 @@ Generic template helper definitions for HackMyResume / FluentCV. /** Convert the input date to a specified format through Moment.js. + If date is invalid, will return the time provided by the user, + or default to the fallback param or 'Present' if that is set to true @method formatDate */ - formatDate: function(datetime, format) { - return moment ? moment( datetime ).format( format ) : datetime; + formatDate: function(datetime, format, fallback) { + if (moment) { + var momentDate = moment( datetime ); + + if (momentDate.isValid()) { + return moment.format(format); + } + } + + return datetime || (typeof fallback == 'string' ? fallback : (fallback === true ? 'Present' : null)); }, /** From d8b9d86896531f8276e23098d2210d2bf96d8aa0 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Fri, 1 Jan 2016 17:30:57 -0500 Subject: [PATCH 071/488] Scrub. --- src/verbs/convert.js | 51 ++++++++++++++++++++++++++++++++------------ 1 file changed, 37 insertions(+), 14 deletions(-) diff --git a/src/verbs/convert.js b/src/verbs/convert.js index c70196d3..2539c963 100644 --- a/src/verbs/convert.js +++ b/src/verbs/convert.js @@ -4,35 +4,58 @@ Implementation of the 'convert' verb for HackMyResume. @license MIT. See LICENSE.md for details. */ + + (function(){ - var ResumeFactory = require('../core/resume-factory'); - var chalk = require('chalk'); + + + var ResumeFactory = require('../core/resume-factory') + , chalk = require('chalk'); + + /** Convert between FRESH and JRS formats. */ module.exports = function convert( sources, dst, opts, logger ) { + + // Housekeeping var _log = logger || console.log; if( !sources || !sources.length ) { throw { fluenterror: 6 }; } if( !dst || !dst.length ) { if( sources.length === 1 ) { throw { fluenterror: 5 }; } - else if( sources.length === 2 ) { dst = [ sources[1] ]; sources = [ sources[0] ]; } + else if( sources.length === 2 ) { + dst = [ sources[1] ]; sources = [ sources[0] ]; + } else { throw { fluenterror: 5 }; } } - if( sources && dst && sources.length && dst.length && sources.length !== dst.length ) { - throw { fluenterror: 7 }; - } - var sourceResumes = ResumeFactory.load( sources, { log: _log, format: null, objectify: true, throw: true } ); + if( sources && dst && sources.length && dst.length && + sources.length !== dst.length ) { throw { fluenterror: 7 }; } + + // Load source resumes + var sourceResumes = ResumeFactory.load( sources, { + log: _log, format: null, objectify: true, throw: true + }); + + // Apply the conversion to each sourceResumes.forEach(function( src, idx ) { - var sheet = src.rez; - var sourceFormat = ((sheet.basics && sheet.basics.imp) || sheet.imp).orgFormat === 'JRS' ? 'JRS' : 'FRESH'; - var targetFormat = sourceFormat === 'JRS' ? 'FRESH' : 'JRS'; - _log( chalk.green('Converting ') + chalk.green.bold(src.file) + chalk.green(' (' + - sourceFormat + ') to ') + chalk.green.bold(dst[0]) + - chalk.green(' (' + targetFormat + ').') ); - sheet.saveAs( dst[idx], targetFormat ); + + var s = src.rez + , srcFmt = ((s.basics && s.basics.imp) || s.imp).orgFormat === 'JRS' ? + 'JRS' : 'FRESH'; + + var targetFormat = srcFmt === 'JRS' ? 'FRESH' : 'JRS'; + + // TODO: Core should not log + _log( chalk.green('Converting ') + chalk.green.bold(src.file) + + chalk.green(' (' + sourceFormat + ') to ') + chalk.green.bold(dst[0]) + + chalk.green(' (' + targetFormat + ').')); + + s.saveAs( dst[idx], targetFormat ); }); }; + + }()); From dd2148bb9254375ce91d27c40bd4cea3ef1454c6 Mon Sep 17 00:00:00 2001 From: Josh Janusch Date: Fri, 1 Jan 2016 18:05:33 -0500 Subject: [PATCH 072/488] Fix: formatDate helper references the moment method, not the momentDate object --- src/eng/generic-helpers.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/eng/generic-helpers.js b/src/eng/generic-helpers.js index 86a75e9a..fffb094f 100644 --- a/src/eng/generic-helpers.js +++ b/src/eng/generic-helpers.js @@ -28,10 +28,7 @@ Generic template helper definitions for HackMyResume / FluentCV. formatDate: function(datetime, format, fallback) { if (moment) { var momentDate = moment( datetime ); - - if (momentDate.isValid()) { - return moment.format(format); - } + if (momentDate.isValid()) return momentDate.format(format); } return datetime || (typeof fallback == 'string' ? fallback : (fallback === true ? 'Present' : null)); From e4a549ed30c476e0ee25f47b284f622863e9ba25 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Fri, 1 Jan 2016 20:26:47 -0500 Subject: [PATCH 073/488] Tests: Add ICE detection test. ICE is the internal boilerplate we use to freeze/unfreeze themes when trying to force-feed them Markdown or other formatted data. --- src/utils/file-contains.js | 12 ++++++++++++ test/test-themes.js | 22 +++++++++++++++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 src/utils/file-contains.js diff --git a/src/utils/file-contains.js b/src/utils/file-contains.js new file mode 100644 index 00000000..164f3112 --- /dev/null +++ b/src/utils/file-contains.js @@ -0,0 +1,12 @@ +/** +Definition of the SyntaxErrorEx class. +@module file-contains.js +*/ + +(function(){ + + module.exports = function( file, needle ) { + return require('fs').readFileSync(file,'utf-8').indexOf( needle ) > -1; + }; + +}()); diff --git a/test/test-themes.js b/test/test-themes.js index c26c7f89..fa108481 100644 --- a/test/test-themes.js +++ b/test/test-themes.js @@ -7,7 +7,10 @@ var SPAWNWATCHER = require('../src/core/spawn-watch') , _ = require('underscore') , FRESHResume = require('../src/core/fresh-resume') , FCMD = require( '../src/hackmycmd') - , validator = require('is-my-json-valid'); + , validator = require('is-my-json-valid') + , READFILES = require('recursive-readdir-sync') + , fileContains = require('../src/utils/file-contains') + , FS = require('fs'); chai.config.includeStack = false; @@ -51,6 +54,23 @@ function genThemes( title, src, fmt ) { } +function folderContains( needle, haystack ) { + return _.some( READFILES( path.join(__dirname, haystack) ), function( absPath ) { + if( FS.lstatSync( absPath ).isFile() ) { + if( fileContains( absPath, needle ) ) { + console.log('Found invalid metadata in ' + absPath); + return true; + } + } + }); +} + genThemes( 'jane-q-fullstacker', ['node_modules/jane-q-fullstacker/resume/jane-resume.json'], 'FRESH' ); genThemes( 'johnny-trouble', ['node_modules/johnny-trouble-resume/src/johnny-trouble.fresh.json'], 'FRESH' ); genThemes( 'richard-hendriks', ['test/resumes/jrs-0.0.0/richard-hendriks.json'], 'JRS' ); + +describe('Verifying generated theme files...', function() { + it('Generated files should not contain ICE.', function() { + expect( folderContains('@@@@', 'sandbox') ).to.be.false; + }); +}); From 47553b6defc2b7032bcd13b623a5e956c4925c77 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Fri, 1 Jan 2016 20:27:46 -0500 Subject: [PATCH 074/488] Fix ICE encoding issues. Fix issue where @@@@ is appearing in generated resumes. --- src/verbs/generate.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/verbs/generate.js b/src/verbs/generate.js index 50db299a..33519624 100644 --- a/src/verbs/generate.js +++ b/src/verbs/generate.js @@ -155,7 +155,7 @@ Implementation of the 'generate' verb for HackMyResume. console.log = consoleLog; // Unharden - rezHtml = rezHtml.replace( /@@@@~.+?~@@@@/g, function(val){ + rezHtml = rezHtml.replace( /@@@@~.*?~@@@@/gm, function(val){ return MDIN( val.replace( /~@@@@/gm,'' ).replace( /@@@@~/gm,'' ) ); }); From a95b52acd0aef65ea9ae4c8a3e64bf9164ba4aba Mon Sep 17 00:00:00 2001 From: hacksalot Date: Sat, 2 Jan 2016 00:15:46 -0500 Subject: [PATCH 075/488] Refactor command processing. --- src/core/resume-factory.js | 7 ++++--- src/gen/template-generator.js | 2 +- src/index.js | 3 +-- src/verbs/analyze.js | 19 ++++++++++++------ src/verbs/convert.js | 38 +++++++++++++++++------------------ src/verbs/generate.js | 28 ++++++++++++++++---------- src/verbs/validate.js | 31 ++++++++++++++-------------- test/test-themes.js | 6 +++--- 8 files changed, 74 insertions(+), 60 deletions(-) diff --git a/src/core/resume-factory.js b/src/core/resume-factory.js index dda26ccf..025ca1f9 100644 --- a/src/core/resume-factory.js +++ b/src/core/resume-factory.js @@ -92,7 +92,7 @@ Definition of the ResumeFactory class. try { // TODO: Core should not log - opts.log( chalk.gray('Reading resume: ') + chalk.cyan.bold(fileName) ); + opts.log( chalk.cyan('Reading resume: ') + chalk.cyan.bold(fileName) ); // Read the file rawData = FS.readFileSync( fileName, 'utf8' ); @@ -106,7 +106,7 @@ Definition of the ResumeFactory class. catch( ex ) { // JSON.parse failed due to invalid JSON - if ( ex instanceof SyntaxError) { + if ( !opts.muffle && ex instanceof SyntaxError) { var info = new SyntaxErrorEx( ex, rawData ); opts.log( chalk.red.bold(fileName.toUpperCase() + ' contains invalid JSON on line ' + info.line + ' column ' + info.col + '.' + @@ -118,7 +118,8 @@ Definition of the ResumeFactory class. if( opts.throw ) throw ex; else return { error: ex, - raw: rawData + raw: rawData, + file: fileName }; } diff --git a/src/gen/template-generator.js b/src/gen/template-generator.js index 257f4772..2eadd55c 100644 --- a/src/gen/template-generator.js +++ b/src/gen/template-generator.js @@ -239,7 +239,6 @@ Definition of the TemplateGenerator class. var t; if( this.opts.theme.startsWith('jsonresume-theme-') ) { - console.log('LOADING JSON RESUME'); t = new JRSTheme().open( tFolder ); } else { @@ -274,6 +273,7 @@ Definition of the TemplateGenerator class. } catch(ex) { console.log(ex); + throw ex; } } diff --git a/src/index.js b/src/index.js index dad71a75..9d8ac127 100644 --- a/src/index.js +++ b/src/index.js @@ -17,7 +17,7 @@ var SPAWNW = require('./core/spawn-watch') , PATH = require('path') , HACKMYSTATUS = require('./core/status-codes') , opts = { } - , title = chalk.white('\n*** HackMyResume v' + PKG.version + ' ***') + , title = chalk.white.bold('\n*** HackMyResume v' + PKG.version + ' ***') , _ = require('underscore'); @@ -61,7 +61,6 @@ function main() { // Massage inputs and outputs var src = a._.slice(1, splitAt === -1 ? undefined : splitAt ); var dst = splitAt === -1 ? [] : a._.slice( splitAt + 1 ); - ( splitAt === -1 ) && (src.length > 1) && (verb !== 'validate') && dst.push( src.pop() ); // Allow omitting TO keyword // Invoke the action (FCMD.verbs[verb] || FCMD.alias[verb]).apply(null, [src, dst, opts, logMsg]); diff --git a/src/verbs/analyze.js b/src/verbs/analyze.js index d8d177bb..48fbbc48 100644 --- a/src/verbs/analyze.js +++ b/src/verbs/analyze.js @@ -22,14 +22,19 @@ Implementation of the 'analyze' verb for HackMyResume. /** Run the 'analyze' command. */ - module.exports = function analyze( src, dst, opts, logger ) { + module.exports = function analyze( sources, dst, opts, logger ) { var _log = logger || console.log; - if( !src || !src.length ) throw { fluenterror: 3 }; - var sourceResumes = ResumeFactory.load( src, _log, null, true ); + if( !sources || !sources.length ) throw { fluenterror: 3 }; + var nlzrs = _loadInspectors(); - sourceResumes.forEach( function(r) { - _analyze( r, nlzrs, opts, _log ); + + sources.forEach( function(src) { + var result = ResumeFactory.loadOne( src, { + log: _log, format: 'FRESH', objectify: true, throw: false + }); + result.error || _analyze( result, nlzrs, opts, _log ); }); + }; @@ -39,7 +44,9 @@ Implementation of the 'analyze' verb for HackMyResume. */ function _analyze( resumeObject, nlzrs, opts, log ) { var rez = resumeObject.rez; - var safeFormat = rez.meta.format.startsWith('FRESH') ? 'FRESH' : 'JRS'; + var safeFormat = + (rez.meta && rez.meta.format && rez.meta.format.startsWith('FRESH')) ? + 'FRESH' : 'JRS'; log(chalk.cyan('Analyzing ') + chalk.cyan.bold(safeFormat) + chalk.cyan(' resume: ') + chalk.cyan.bold(resumeObject.file)); var info = _.mapObject( nlzrs, function(val, key) { diff --git a/src/verbs/convert.js b/src/verbs/convert.js index 2539c963..e5e483a1 100644 --- a/src/verbs/convert.js +++ b/src/verbs/convert.js @@ -18,42 +18,42 @@ Implementation of the 'convert' verb for HackMyResume. /** Convert between FRESH and JRS formats. */ - module.exports = function convert( sources, dst, opts, logger ) { + module.exports = function convert( srcs, dst, opts, logger ) { // Housekeeping var _log = logger || console.log; - if( !sources || !sources.length ) { throw { fluenterror: 6 }; } + if( !srcs || !srcs.length ) { throw { fluenterror: 6 }; } if( !dst || !dst.length ) { - if( sources.length === 1 ) { throw { fluenterror: 5 }; } - else if( sources.length === 2 ) { - dst = [ sources[1] ]; sources = [ sources[0] ]; - } + if( srcs.length === 1 ) { throw { fluenterror: 5 }; } + else if( srcs.length === 2 ) { dst = dst || []; dst.push( srcs.pop() ); } else { throw { fluenterror: 5 }; } } - if( sources && dst && sources.length && dst.length && - sources.length !== dst.length ) { throw { fluenterror: 7 }; } + if( srcs && dst && srcs.length && dst.length && + srcs.length !== dst.length ) { throw { fluenterror: 7 }; } // Load source resumes - var sourceResumes = ResumeFactory.load( sources, { - log: _log, format: null, objectify: true, throw: true - }); + srcs.forEach( function( src, idx ) { - // Apply the conversion to each - sourceResumes.forEach(function( src, idx ) { + // Load the resume + var rinfo = ResumeFactory.loadOne( src, { + log: _log, format: null, objectify: true, throw: true + }); - var s = src.rez + var s = rinfo.rez , srcFmt = ((s.basics && s.basics.imp) || s.imp).orgFormat === 'JRS' ? - 'JRS' : 'FRESH'; - - var targetFormat = srcFmt === 'JRS' ? 'FRESH' : 'JRS'; + 'JRS' : 'FRESH' + , targetFormat = srcFmt === 'JRS' ? 'FRESH' : 'JRS'; // TODO: Core should not log - _log( chalk.green('Converting ') + chalk.green.bold(src.file) + - chalk.green(' (' + sourceFormat + ') to ') + chalk.green.bold(dst[0]) + + _log( chalk.green('Converting ') + chalk.green.bold(rinfo.file) + + chalk.green(' (' + srcFmt + ') to ') + chalk.green.bold(dst[0]) + chalk.green(' (' + targetFormat + ').')); + // Save it to the destination format s.saveAs( dst[idx], targetFormat ); + }); + }; diff --git a/src/verbs/generate.js b/src/verbs/generate.js index 33519624..ca912d13 100644 --- a/src/verbs/generate.js +++ b/src/verbs/generate.js @@ -45,14 +45,20 @@ Implementation of the 'generate' verb for HackMyResume. */ function build( src, dst, opts, logger, errHandler ) { - // Housekeeping... + // Housekeeping + //_opts = extend( true, _opts, opts ); _log = logger || console.log; _err = errHandler || error; - //_opts = extend( true, _opts, opts ); _opts.theme = (opts.theme && opts.theme.toLowerCase().trim())|| 'modern'; _opts.prettify = opts.prettify === true ? _opts.prettify : false; _opts.css = opts.css; + // If two or more files are passed to the GENERATE command and the TO + // keyword is omitted, the last file specifies the output file. + if( src.length > 1 && ( !dst || !dst.length ) ) { + dst.push( src.pop() ); + } + // Load the theme... var tFolder = verify_theme( _opts.theme ); var theme = load_theme( tFolder ); @@ -60,18 +66,18 @@ Implementation of the 'generate' verb for HackMyResume. // Load input resumes... if( !src || !src.length ) { throw { fluenterror: 3 }; } var sheets = ResumeFactory.load(src, { - log: _log, format: theme.render ? 'JRS' : 'FRESH', objectify: true, throw: true - }); + log: _log, format: theme.render ? 'JRS' : 'FRESH', + objectify: true, throw: true + }).map(function(sh){ return sh.rez; }); // Merge input resumes... var msg = ''; - var rezRep = _.reduceRight( sheets, function( a, b, idx ) { + rez = _.reduceRight( sheets, function( a, b, idx ) { msg += ((idx == sheets.length - 2) ? - chalk.gray('Merging ') + a.rez.imp.fileName : '') + chalk.gray(' onto ') + b.rez.fileName; - return extend( true, b.rez, a.rez ); + chalk.cyan('Merging ') + chalk.cyan.bold(a.file) : '') + + chalk.cyan(' onto ') + chalk.cyan.bold(b.file); + return extend( true, b, a ); }); - - rez = rezRep.rez; msg && _log(msg); // Expand output resumes... @@ -277,8 +283,8 @@ Implementation of the 'generate' verb for HackMyResume. // Output a message TODO: core should not log var numFormats = Object.keys(theTheme.formats).length; - _log( chalk.gray('Applying ') + chalk.gray.bold(theTheme.name.toUpperCase()) + - chalk.gray(' theme (' + numFormats + ' formats)')); + _log( chalk.yellow('Applying ') + chalk.yellow.bold(theTheme.name.toUpperCase()) + + chalk.yellow(' theme (' + numFormats + ' formats)')); return theTheme; } diff --git a/src/verbs/validate.js b/src/verbs/validate.js index 2d7f744a..7d4eefba 100644 --- a/src/verbs/validate.js +++ b/src/verbs/validate.js @@ -27,26 +27,27 @@ Implementation of the 'validate' verb for HackMyResume. jars: require('../core/resume.json') }; - // Load input resumes... - sources.forEach(function( src ) { + var resumes = ResumeFactory.load( sources, { + log: _log, + format: null, + objectify: false, + throw: false, + muffle: true + }); - var result = ResumeFactory.loadOne( src, { - log: function(){}, - format: null, - objectify: false, - throw: false - }); + // Load input resumes... + resumes.forEach(function( src ) { - if( result.error ) { + if( src.error ) { // TODO: Core should not log - _log( chalk.white('Validating ') + chalk.gray.bold(src) + + _log( chalk.white('Validating ') + chalk.gray.bold(src.file) + chalk.white(' against ') + chalk.gray.bold('AUTO') + chalk.white(' schema:') + chalk.red.bold(' BROKEN') ); - var ex = result.error; // alias + var ex = src.error; // alias if ( ex instanceof SyntaxError) { - var info = new SyntaxErrorEx( ex, result.raw ); - _log( chalk.red.bold('--> ' + src.toUpperCase() + ' contains invalid JSON on line ' + + var info = new SyntaxErrorEx( ex, src.raw ); + _log( chalk.red.bold('--> ' + src.file.toUpperCase() + ' contains invalid JSON on line ' + info.line + ' column ' + info.col + '.' + chalk.red(' Unable to validate.') ) ); _log( chalk.red.bold(' INTERNAL: ' + ex) ); @@ -57,7 +58,7 @@ Implementation of the 'validate' verb for HackMyResume. return; } - var json = result.json; + var json = src.json; var isValid = false; var style = 'green'; var errors = []; @@ -81,7 +82,7 @@ Implementation of the 'validate' verb for HackMyResume. return; } - _log( chalk.white('Validating ') + chalk.white.bold(result.file) + chalk.white(' against ') + + _log( chalk.white('Validating ') + chalk.white.bold(src.file) + chalk.white(' against ') + chalk.white.bold(fmt.replace('jars','JSON Resume').toUpperCase()) + chalk.white(' schema: ') + chalk[style].bold(isValid ? 'VALID!' : 'INVALID') ); diff --git a/test/test-themes.js b/test/test-themes.js index fa108481..b7ff672e 100644 --- a/test/test-themes.js +++ b/test/test-themes.js @@ -12,7 +12,7 @@ var SPAWNWATCHER = require('../src/core/spawn-watch') , fileContains = require('../src/utils/file-contains') , FS = require('fs'); -chai.config.includeStack = false; +chai.config.includeStack = true; function genThemes( title, src, fmt ) { @@ -31,9 +31,9 @@ function genThemes( title, src, fmt ) { theme: themeLoc, format: fmt, prettify: true, - silent: true + silent: false }; - FCMD.verbs.build( src, dst, opts, function() {} ); + FCMD.verbs.build( src, dst, opts, function(msg) { console.log(msg); } ); } tryOpen.should.not.Throw(); }); From 6b3396e01baa42a6fa7c5aa69b47e6bebb953cc9 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Sun, 3 Jan 2016 02:22:26 -0500 Subject: [PATCH 076/488] Use Commander.js for invocations. --- package.json | 1 + src/core/error-handler.js | 5 +- src/index.js | 178 +++++++++++++++++++++++++++++++++----- src/verbs/convert.js | 22 +++-- src/verbs/create.js | 1 + 5 files changed, 173 insertions(+), 34 deletions(-) diff --git a/package.json b/package.json index ef9583ad..c234aeeb 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "homepage": "https://github.com/hacksalot/HackMyResume", "dependencies": { "chalk": "^1.1.1", + "commander": "^2.9.0", "copy": "^0.1.3", "fresca": "~0.2.4", "fresh-themes": "~0.9.3-beta", diff --git a/src/core/error-handler.js b/src/core/error-handler.js index 358e67e1..839a4af8 100644 --- a/src/core/error-handler.js +++ b/src/core/error-handler.js @@ -67,8 +67,7 @@ Error-handling routines for HackMyResume. break; case HACKMYSTATUS.invalidCommand: - msg = chalk.yellow('Please ') + chalk.yellow.bold('specify the output resume file') + - chalk.yellow(' that should be created.'); + msg = chalk.yellow('Invalid command: "') + chalk.yellow.bold(ex.attempted) + chalk.yellow('"'); break; case HACKMYSTATUS.resumeNotFoundAlt: @@ -116,7 +115,7 @@ Error-handling routines for HackMyResume. } // Let the error code be the process's return code. - if( shouldExit ) + if( shouldExit || ex.shouldExit ) process.exit( exitCode ); } diff --git a/src/index.js b/src/index.js index 9d8ac127..9cdbc088 100644 --- a/src/index.js +++ b/src/index.js @@ -18,8 +18,9 @@ var SPAWNW = require('./core/spawn-watch') , HACKMYSTATUS = require('./core/status-codes') , opts = { } , title = chalk.white.bold('\n*** HackMyResume v' + PKG.version + ' ***') - , _ = require('underscore'); - + , StringUtils = require('./utils/string.js') + , _ = require('underscore') + , Command = require('commander').Command; @@ -31,46 +32,175 @@ catch( ex ) { } - +/** +Kick off the HackMyResume application. +*/ function main() { - // Setup - var a = ARGS( process.argv.slice(2) ); - if( a._.length === 0 ) { throw { fluenterror: 4 }; } - opts = getOpts( a ); - logMsg( title ); + var args = initialize(); - // Get the action to be performed - var params = a._.map( function(p){ return p.toLowerCase().trim(); }); - var verb = opts.help ? 'help' : params[0]; - if( !FCMD.verbs[ verb ] && !FCMD.alias[ verb ] ) { - logMsg(chalk.yellow('Invalid command: "') + chalk.yellow.bold(verb) + chalk.yellow('"')); - return; + function execCommand() { + var argsArray = Array.prototype.slice.call(arguments); + //console.log(argsArray); + return FCMD.verbs[ this.name() ].apply( null, argsArray ); } + // Create the top-level (application) command... + var program = new Command('hackmyresume') + .version(PKG.version) + .description(chalk.yellow.bold('*** HackMyResume ***')) + .option('-s, --silent', 'Run in silent mode.') + .usage('COMMAND [TO ]'); + + // Create the NEW command + program + .command('new') + .arguments('') + .option('-f --format ', 'FRESH or JRS format', 'FRESH') + .alias('create') + .description('Create resume(s) in FRESH or JSON RESUME format.') + .action(function( sources ) { + execCommand.call( this, sources, [], this.opts(), logMsg ); + }); + + // Create the VALIDATE command + program + .command('validate') + .arguments('') + .description('Validate a resume in FRESH or JSON RESUME format.') + .action(function(sources) { + execCommand.call(this, sources, [], this.opts(), logMsg); + }); + + // Create the CONVERT command + program + .command('convert') + //.arguments('') + .description('Convert a resume to/from FRESH or JSON RESUME format.') + .action(function() { + var x = splitSrcDest.call( this ); + execCommand.call( this, x.src, x.dst, this.opts(), logMsg ); + }); + + // Create the ANALYZE command + program + .command('analyze') + .arguments('') + .description('Analyze one or more resumes.') + .action(function() { + execCommand.call(this, sources, [], this.opts(), logMsg); + }); + + // Create the BUILD command + program + .command('build') + .alias('generate') + //.arguments(' TO [targets]') + //.usage('...') + .option('-t --theme ', 'Theme name or path') + .option('-p --prettify', 'Preffity HTML output.') + .description('Generate resume to multiple formats') + .action(function( sources, targets, options ) { + var x = splitSrcDest.call( this ); + execCommand.call( this, x.src, x.dst, opts, logMsg ); + }); + + // program.on('--help', function(){ + // console.log(' Examples:'); + // console.log(''); + // console.log(' $ custom-help --help'); + // console.log(' $ custom-help -h'); + // console.log(''); + // }); + + program.parse( args ); + + if (!program.args.length) { throw { fluenterror: 4 }; } + +} + + + +/** +Split multiple command-line filenames by the 'TO' keyword +*/ +function splitSrcDest() { + + var params = this.parent.args.filter(function(j) { return String.is(j); }); + if( params.length === 0 ) + throw { fluenterror: HACKMYSTATUS.resumeNotFound }; + // Find the TO keyword, if any - var splitAt = _.indexOf( params, 'to' ); - if( splitAt === a._.length - 1 && splitAt !== -1 ) { - // 'TO' cannot be the last argument - logMsg(chalk.yellow('Please ') + chalk.yellow.bold('specify an output file') + - chalk.yellow(' for this operation or ') + chalk.yellow.bold('omit the TO keyword') + + var splitAt = _.findIndex( params, function(p) { + return p.toLowerCase() === 'to'; + }); + + // TO can't be the last keyword + if( splitAt === params.length - 1 && splitAt !== -1 ) { + logMsg(chalk.yellow('Please ') + + chalk.yellow.bold('specify an output file') + + chalk.yellow(' for this operation or ') + + chalk.yellow.bold('omit the TO keyword') + chalk.yellow('.') ); return; } - // Massage inputs and outputs - var src = a._.slice(1, splitAt === -1 ? undefined : splitAt ); - var dst = splitAt === -1 ? [] : a._.slice( splitAt + 1 ); + return { + src: params.slice(0, splitAt === -1 ? undefined : splitAt ), + dst: splitAt === -1 ? [] : params.slice( splitAt + 1 ) + }; +} - // Invoke the action - (FCMD.verbs[verb] || FCMD.alias[verb]).apply(null, [src, dst, opts, logMsg]); +/** +Massage command-line args and setup Commander.js. +*/ +function initialize() { + + // Support case-insensitive sub-commands (build, generate, validate, etc.).. + var oVerb, verb = '', args = process.argv.slice(), cleanArgs = args.slice(2); + if( cleanArgs.length ) { + var verbIdx = _.findIndex( cleanArgs, function(v){ return v[0] !== '-'; }); + if( verbIdx !== -1 ) { + oVerb = cleanArgs[ verbIdx ]; + verb = args[ verbIdx + 2 ] = oVerb.trim().toLowerCase(); + } + } + + // Handle invalid verbs here (a bit easier here than in commander.js)... + if( !FCMD.verbs[ verb ] && !FCMD.alias[ verb ] ) { + throw { fluenterror: HACKMYSTATUS.invalidCommand, shouldExit: true, + attempted: oVerb }; + } + + // Override the .missingArgument behavior + Command.prototype.missingArgument = function(name) { + throw { fluenterror: HACKMYSTATUS.resumeNotFound }; + }; + + // Override the .helpInformation behavior + Command.prototype.helpInformation = function() { + var manPage = FS.readFileSync( PATH.join(__dirname, 'use.txt'), 'utf8' ); + return chalk.green.bold(manPage); + }; + + return args; } + + +/** +Simple logging placeholder. +*/ function logMsg( msg ) { opts.silent || console.log( msg ); } + + +/** +Fetch options from command line arguments. +*/ function getOpts( args ) { var noPretty = args.nopretty || args.n; noPretty = noPretty && (noPretty === true || noPretty === 'true'); diff --git a/src/verbs/convert.js b/src/verbs/convert.js index e5e483a1..d050ec0a 100644 --- a/src/verbs/convert.js +++ b/src/verbs/convert.js @@ -11,7 +11,8 @@ Implementation of the 'convert' verb for HackMyResume. var ResumeFactory = require('../core/resume-factory') - , chalk = require('chalk'); + , chalk = require('chalk') + , HACKMYSTATUS = require('../core/status-codes'); @@ -24,12 +25,19 @@ Implementation of the 'convert' verb for HackMyResume. var _log = logger || console.log; if( !srcs || !srcs.length ) { throw { fluenterror: 6 }; } if( !dst || !dst.length ) { - if( srcs.length === 1 ) { throw { fluenterror: 5 }; } - else if( srcs.length === 2 ) { dst = dst || []; dst.push( srcs.pop() ); } - else { throw { fluenterror: 5 }; } + if( srcs.length === 1 ) { + throw { fluenterror: HACKMYSTATUS.inputOutputParity }; + } + else if( srcs.length === 2 ) { + dst = dst || []; dst.push( srcs.pop() ); + } + else { + throw { fluenterror: HACKMYSTATUS.inputOutputParity }; + } + } + if(srcs && dst && srcs.length && dst.length && srcs.length !== dst.length){ + throw { fluenterror: HACKMYSTATUS.inputOutputParity }; } - if( srcs && dst && srcs.length && dst.length && - srcs.length !== dst.length ) { throw { fluenterror: 7 }; } // Load source resumes srcs.forEach( function( src, idx ) { @@ -46,7 +54,7 @@ Implementation of the 'convert' verb for HackMyResume. // TODO: Core should not log _log( chalk.green('Converting ') + chalk.green.bold(rinfo.file) + - chalk.green(' (' + srcFmt + ') to ') + chalk.green.bold(dst[0]) + + chalk.green(' (' + srcFmt + ') to ') + chalk.green.bold(dst[idx]) + chalk.green(' (' + targetFormat + ').')); // Save it to the destination format diff --git a/src/verbs/create.js b/src/verbs/create.js index c2d1c8f9..1df90045 100644 --- a/src/verbs/create.js +++ b/src/verbs/create.js @@ -15,6 +15,7 @@ Implementation of the 'create' verb for HackMyResume. Create a new empty resume in either FRESH or JRS format. */ module.exports = function create( src, dst, opts, logger ) { + //console.log(src); var _log = logger || console.log; if( !src || !src.length ) throw { fluenterror: 8 }; src.forEach( function( t ) { From 69e8adc1ccd67ae69276c732f0715e866e9476e7 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Sun, 3 Jan 2016 02:25:39 -0500 Subject: [PATCH 077/488] Remove 'minimist' dependency. --- package.json | 1 - src/index.js | 1 - 2 files changed, 2 deletions(-) diff --git a/package.json b/package.json index c234aeeb..c031a5b0 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,6 @@ "jst": "0.0.13", "lodash": "^3.10.1", "marked": "^0.3.5", - "minimist": "^1.2.0", "mkdirp": "^0.5.1", "moment": "^2.10.6", "parse-filepath": "^0.6.3", diff --git a/src/index.js b/src/index.js index 9cdbc088..258f2cca 100644 --- a/src/index.js +++ b/src/index.js @@ -9,7 +9,6 @@ Command-line interface (CLI) for HackMyResume. var SPAWNW = require('./core/spawn-watch') - , ARGS = require( 'minimist' ) , FCMD = require( './hackmycmd') , PKG = require('../package.json') , FS = require('fs') From 8fc0fa99d3960a72681e80600627bfdb938767cb Mon Sep 17 00:00:00 2001 From: hacksalot Date: Sun, 3 Jan 2016 02:39:43 -0500 Subject: [PATCH 078/488] Remove unnecessary indirection. --- src/index.js | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/src/index.js b/src/index.js index 258f2cca..90d87180 100644 --- a/src/index.js +++ b/src/index.js @@ -38,12 +38,6 @@ function main() { var args = initialize(); - function execCommand() { - var argsArray = Array.prototype.slice.call(arguments); - //console.log(argsArray); - return FCMD.verbs[ this.name() ].apply( null, argsArray ); - } - // Create the top-level (application) command... var program = new Command('hackmyresume') .version(PKG.version) @@ -59,7 +53,7 @@ function main() { .alias('create') .description('Create resume(s) in FRESH or JSON RESUME format.') .action(function( sources ) { - execCommand.call( this, sources, [], this.opts(), logMsg ); + FCMD.verbs[ this.name() ].call( null, sources, [], this.opts(), logMsg); }); // Create the VALIDATE command @@ -68,7 +62,7 @@ function main() { .arguments('') .description('Validate a resume in FRESH or JSON RESUME format.') .action(function(sources) { - execCommand.call(this, sources, [], this.opts(), logMsg); + FCMD.verbs[ this.name() ].call( null, sources, [], this.opts(), logMsg); }); // Create the CONVERT command @@ -78,7 +72,7 @@ function main() { .description('Convert a resume to/from FRESH or JSON RESUME format.') .action(function() { var x = splitSrcDest.call( this ); - execCommand.call( this, x.src, x.dst, this.opts(), logMsg ); + FCMD.verbs[ this.name() ].call( null, x.src, x.dst, this.opts(), logMsg); }); // Create the ANALYZE command @@ -86,8 +80,8 @@ function main() { .command('analyze') .arguments('') .description('Analyze one or more resumes.') - .action(function() { - execCommand.call(this, sources, [], this.opts(), logMsg); + .action(function( sources ) { + FCMD.verbs[ this.name() ].call( null, sources, [], this.opts(), logMsg); }); // Create the BUILD command @@ -101,7 +95,7 @@ function main() { .description('Generate resume to multiple formats') .action(function( sources, targets, options ) { var x = splitSrcDest.call( this ); - execCommand.call( this, x.src, x.dst, opts, logMsg ); + FCMD.verbs[ this.name() ].call( null, x.src, x.dst, this.opts(), logMsg); }); // program.on('--help', function(){ @@ -167,7 +161,7 @@ function initialize() { } // Handle invalid verbs here (a bit easier here than in commander.js)... - if( !FCMD.verbs[ verb ] && !FCMD.alias[ verb ] ) { + if( verb && !FCMD.verbs[ verb ] && !FCMD.alias[ verb ] ) { throw { fluenterror: HACKMYSTATUS.invalidCommand, shouldExit: true, attempted: oVerb }; } From 655ecebaa55cab920ca2d6fca07a457770d143f5 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Sun, 3 Jan 2016 02:40:04 -0500 Subject: [PATCH 079/488] Clean up comments. --- src/gen/html-pdf-generator.js | 2 +- src/inspectors/gap-inspector.js | 2 +- src/verbs/create.js | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/gen/html-pdf-generator.js b/src/gen/html-pdf-generator.js index 705c4b34..8d433ed3 100644 --- a/src/gen/html-pdf-generator.js +++ b/src/gen/html-pdf-generator.js @@ -10,7 +10,7 @@ Definition of the HtmlPdfGenerator class. , HTML = require( 'html' ); /** - An HTML-based PDF resume generator for HackMyResume. + An HTML-driven PDF resume generator for HackMyResume. */ var HtmlPdfGenerator = module.exports = TemplateGenerator.extend({ diff --git a/src/inspectors/gap-inspector.js b/src/inspectors/gap-inspector.js index 5f24b1cd..a996680b 100644 --- a/src/inspectors/gap-inspector.js +++ b/src/inspectors/gap-inspector.js @@ -1,7 +1,7 @@ /** Employment gap analysis for HackMyResume. @license MIT. See LICENSE.md for details. -@module gap-analyzer.js +@module gap-inspector.js */ diff --git a/src/verbs/create.js b/src/verbs/create.js index 1df90045..c2d1c8f9 100644 --- a/src/verbs/create.js +++ b/src/verbs/create.js @@ -15,7 +15,6 @@ Implementation of the 'create' verb for HackMyResume. Create a new empty resume in either FRESH or JRS format. */ module.exports = function create( src, dst, opts, logger ) { - //console.log(src); var _log = logger || console.log; if( !src || !src.length ) throw { fluenterror: 8 }; src.forEach( function( t ) { From 8d7cf329886ad5bd529b2558edc9784198c83ed0 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Sun, 3 Jan 2016 03:18:56 -0500 Subject: [PATCH 080/488] Finish Commander.js integration. --- src/core/fresh-resume.js | 8 ++++ src/core/jrs-resume.js | 10 +++++ src/core/resume-factory.js | 1 + src/index.js | 82 ++++++++++++++++---------------------- src/verbs/generate.js | 4 +- 5 files changed, 56 insertions(+), 49 deletions(-) diff --git a/src/core/fresh-resume.js b/src/core/fresh-resume.js index 280d6037..1ab8833a 100644 --- a/src/core/fresh-resume.js +++ b/src/core/fresh-resume.js @@ -208,6 +208,14 @@ Definition of the FRESHResume class. return this.parseJSON( JSON.parse( stringData ), opts ); }; + /** + Return internal metadata. Create if it doesn't exist. + */ + FreshResume.prototype.imp = function() { + this.imp = (this.imp || { }); + return this.imp; + }; + /** Return a unique list of all keywords across all skills. */ diff --git a/src/core/jrs-resume.js b/src/core/jrs-resume.js index 5609a75d..0c5a8626 100644 --- a/src/core/jrs-resume.js +++ b/src/core/jrs-resume.js @@ -136,6 +136,16 @@ Definition of the JRSResume class. 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.imp = function() { + this.basics = this.basics || { imp: { } }; + return this.basics; + }; + /** Reset the sheet to an empty state. */ diff --git a/src/core/resume-factory.js b/src/core/resume-factory.js index 025ca1f9..e9351b3a 100644 --- a/src/core/resume-factory.js +++ b/src/core/resume-factory.js @@ -75,6 +75,7 @@ Definition of the ResumeFactory class. if( objectify ) { var ResumeClass = require('../core/' + (toFormat || orgFormat) + '-resume'); rez = new ResumeClass().parseJSON( json ); + rez.imp().file = src; } return { diff --git a/src/index.js b/src/index.js index 90d87180..b8244c0d 100644 --- a/src/index.js +++ b/src/index.js @@ -1,5 +1,7 @@ #! /usr/bin/env node + + /** Command-line interface (CLI) for HackMyResume. @license MIT. Copyright (c) 2015 hacksalot (https://github.com/hacksalot) @@ -90,12 +92,13 @@ function main() { .alias('generate') //.arguments(' TO [targets]') //.usage('...') - .option('-t --theme ', 'Theme name or path') - .option('-p --prettify', 'Preffity HTML output.') + .option('-t --theme ', 'Theme name or path', 'modern') + .option('-p --prettify', 'Preffity HTML output', true) + .option('-c --css

|<\/p>\s*$/gi, ''); - } - try { if( !targInfo.fmt ) { return; @@ -218,37 +205,7 @@ Implementation of the 'generate' verb for HackMyResume. // JSON Resume themes have a 'render' method that needs to be called if( theme.render ) { - var COPY = require('copy'); - var globs = [ '*.css', '*.js', '*.png', '*.jpg', '*.gif', '*.bmp' ]; - COPY.sync( globs , outFolder, { - cwd: theme.folder, nodir: true, - ignore: ['node_modules/','node_modules/**'] - // rewrite: function(p1, p2) { - // return PATH.join(p2, p1); - // } - }); - - // Prevent JSON Resume theme .js from chattering (TODO: redirect IO) - var consoleLog = console.log; - console.log = function() { }; - - // Call the theme's render method - var rezDupe = rez.harden(); - var rezHtml = theme.render( rezDupe ); - - // Turn logging back on - console.log = consoleLog; - - // Unharden - rezHtml = rezHtml.replace( /@@@@~.*?~@@@@/gm, function(val){ - return MDIN( val.replace( /~@@@@/gm,'' ).replace( /@@@@~/gm,'' ) ); - }); - - // Save the file - FS.writeFileSync( f, rezHtml ); - - // Return markup to the client - return rezHtml; + return renderJRSTheme( f, outFolder, theme ); } else { return theFormat.gen.generate( rez, f, _opts ); @@ -262,6 +219,48 @@ Implementation of the 'generate' verb for HackMyResume. + /** + Render a JSON Resume theme. JSON Resume themes have an index.js that needs + to be called to perform the render. Additionally, we need to flow Markdown + styles to the JSON Resume (to the extent possible). + */ + function renderJRSTheme( f, outFolder, theme ) { + + var COPY = require('copy'); + var globs = [ '*.css', '*.js', '*.png', '*.jpg', '*.gif', '*.bmp' ]; + COPY.sync( globs , outFolder, { + cwd: theme.folder, nodir: true, + ignore: ['node_modules/','node_modules/**'] + // rewrite: function(p1, p2) { + // return PATH.join(p2, p1); + // } + }); + + // Disable JRS theme chatter (console.log, console.error, etc.) + var off = ['log', 'error', 'dir'], org = off.map(function(c){ + var ret = console[c]; console[c] = function(){}; return ret; + }); + + // Freeze and render + var rezHtml = theme.render( rez.harden() ); + + // Turn logging back on + off.forEach(function(c, idx){ console[c] = org[idx]; }); + + // Unfreeze and apply Markdown + rezHtml = rezHtml.replace( /@@@@~.*?~@@@@/gm, function(val){ + return MDIN( val.replace( /~@@@@/gm,'' ).replace( /@@@@~/gm,'' ) ); + }); + + // Save the file + FS.writeFileSync( f, rezHtml ); + + // Return markup to the client + return rezHtml; + } + + + /** Ensure that user-specified outputs/targets are valid. */ @@ -275,7 +274,8 @@ Implementation of the 'generate' verb for HackMyResume. }; }), function(t) { - return t.format === 'all' || theme.hasFormat( parsePath( t.format ).extname.substr(1)); + return t.format === 'all' || + theme.hasFormat( parsePath( t.format ).extname.substr(1)); } ); @@ -342,6 +342,7 @@ Implementation of the 'generate' verb for HackMyResume. } + /** Verify the specified theme name/path. */ @@ -364,7 +365,8 @@ Implementation of the 'generate' verb for HackMyResume. /** - Load the specified theme. + Load the specified theme, which could be either a FRESH theme or a JSON Resume + theme. */ function load_theme( tFolder ) { @@ -380,6 +382,21 @@ Implementation of the 'generate' verb for HackMyResume. + /** + Handle an exception. Placeholder. + */ + function error( ex ) { + throw ex; + } + + + + function MDIN(txt) { // TODO: Move this + return MD(txt || '' ).replace(/^\s*

|<\/p>\s*$/gi, ''); + } + + + module.exports = build; From ae51930c9c00d34bef8276858df2a2f0f2ac7425 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Fri, 8 Jan 2016 07:06:26 -0500 Subject: [PATCH 160/488] Tweak indentation. --- src/verbs/build.js | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/verbs/build.js b/src/verbs/build.js index 2f191dad..4ab40e8e 100644 --- a/src/verbs/build.js +++ b/src/verbs/build.js @@ -124,7 +124,7 @@ Implementation of the 'generate' verb for HackMyResume. _log = logger || console.log; _err = errHandler || error; - //_opts = extend( true, _opts, opts ); + // Cherry-pick options //_opts = extend( true, _opts, opts ); _opts.theme = (opts.theme && opts.theme.toLowerCase().trim()) || 'modern'; _opts.prettify = opts.prettify === true; _opts.css = opts.css || 'embed'; @@ -159,28 +159,28 @@ Implementation of the 'generate' verb for HackMyResume. , fName = PATH.basename(f, '.' + fType) , theFormat; - var suffix = ''; - if( targInfo.fmt.outFormat === 'pdf' ) { - if( _opts.pdf ) { - if( _opts.pdf !== 'none' ) { - suffix = chalk.green(' (with ' + _opts.pdf + ')'); - } - else { - _log( chalk.gray('Skipping ') + - chalk.white.bold( - pad(targInfo.fmt.outFormat.toUpperCase(),4,null,pad.RIGHT)) + - chalk.gray(' resume') + suffix + chalk.green(': ') + - chalk.white( PATH.relative(process.cwd(), f )) ); - return; - } + var suffix = ''; + if( targInfo.fmt.outFormat === 'pdf' ) { + if( _opts.pdf ) { + if( _opts.pdf !== 'none' ) { + suffix = chalk.green(' (with ' + _opts.pdf + ')'); + } + else { + _log( chalk.gray('Skipping ') + + chalk.white.bold( + pad(targInfo.fmt.outFormat.toUpperCase(),4,null,pad.RIGHT)) + + chalk.gray(' resume') + suffix + chalk.green(': ') + + chalk.white( PATH.relative(process.cwd(), f )) ); + return; } } + } - _log( chalk.green('Generating ') + - chalk.green.bold( - pad(targInfo.fmt.outFormat.toUpperCase(),4,null,pad.RIGHT)) + - chalk.green(' resume') + suffix + chalk.green(': ') + - chalk.green.bold( PATH.relative(process.cwd(), f )) ); + _log( chalk.green('Generating ') + + chalk.green.bold( + pad(targInfo.fmt.outFormat.toUpperCase(),4,null,pad.RIGHT)) + + chalk.green(' resume') + suffix + chalk.green(': ') + + chalk.green.bold( PATH.relative(process.cwd(), f )) ); // If targInfo.fmt.files exists, this format is backed by a document. // Fluent/FRESH themes are handled here. From 4a2a47f551b2bbdd98731ef9bec9d6db9c5c09f0 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Fri, 8 Jan 2016 07:08:12 -0500 Subject: [PATCH 161/488] Tweak casing. --- src/verbs/build.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/verbs/build.js b/src/verbs/build.js index 4ab40e8e..2dcc6e62 100644 --- a/src/verbs/build.js +++ b/src/verbs/build.js @@ -44,11 +44,11 @@ Implementation of the 'generate' verb for HackMyResume. // Load the theme...we do this first because the theme choice (FRESH or // JSON Resume) determines what format we'll convert the resume to. - var tFolder = verify_theme( _opts.theme ); - var theme = load_theme( tFolder ); + var tFolder = verifyTheme( _opts.theme ); + var theme = loadTheme( tFolder ); // Check for invalid outputs - var inv = verify_outputs( dst, theme ); + var inv = verifyOutputs( dst, theme ); if( inv && inv.length ) { throw { fluenterror: HACKMYSTATUS.invalidTarget, data: inv, theme: theme }; } @@ -223,6 +223,7 @@ Implementation of the 'generate' verb for HackMyResume. Render a JSON Resume theme. JSON Resume themes have an index.js that needs to be called to perform the render. Additionally, we need to flow Markdown styles to the JSON Resume (to the extent possible). + TODO: Refactor */ function renderJRSTheme( f, outFolder, theme ) { @@ -264,7 +265,7 @@ Implementation of the 'generate' verb for HackMyResume. /** Ensure that user-specified outputs/targets are valid. */ - function verify_outputs( targets, theme ) { + function verifyOutputs( targets, theme ) { return _.reject( targets.map( function( t ) { @@ -346,7 +347,7 @@ Implementation of the 'generate' verb for HackMyResume. /** Verify the specified theme name/path. */ - function verify_theme( themeNameOrPath ) { + function verifyTheme( themeNameOrPath ) { var tFolder = PATH.join( parsePath ( require.resolve('fresh-themes') ).dirname, '/themes/', @@ -368,7 +369,7 @@ Implementation of the 'generate' verb for HackMyResume. Load the specified theme, which could be either a FRESH theme or a JSON Resume theme. */ - function load_theme( tFolder ) { + function loadTheme( tFolder ) { // Create a FRESH or JRS theme object var theTheme = _opts.theme.indexOf('jsonresume-theme-') > -1 ? From d6280e6d894536a0bbfd63032e12fff5cfc83356 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Fri, 8 Jan 2016 08:40:19 -0500 Subject: [PATCH 162/488] Start integrating JRS and FRESH rendering paths. --- src/core/error-handler.js | 2 +- src/core/jrs-theme.js | 24 ++++++++-- src/eng/jrs-generator.js | 76 +++++++++++++++++++++++++++++ src/gen/html-pdf-cli-generator.js | 22 +++++++-- src/gen/template-generator.js | 13 +++-- src/verbs/build.js | 80 ++++++++----------------------- 6 files changed, 143 insertions(+), 74 deletions(-) create mode 100644 src/eng/jrs-generator.js diff --git a/src/core/error-handler.js b/src/core/error-handler.js index ac4efe42..2c84d745 100644 --- a/src/core/error-handler.js +++ b/src/core/error-handler.js @@ -29,7 +29,7 @@ Error-handling routines for HackMyResume. err: function( ex, shouldExit ) { - var msg = '', exitCode, log = console.log, showStack = false; + var msg = '', exitCode, log = console.log, showStack = ex.showStack; // If the exception has been handled elsewhere and shouldExit is true, // let's get out of here, otherwise silently return. diff --git a/src/core/jrs-theme.js b/src/core/jrs-theme.js index 4f2942a9..6f4993e2 100644 --- a/src/core/jrs-theme.js +++ b/src/core/jrs-theme.js @@ -16,8 +16,7 @@ Definition of the JRSTheme class. /** - The JRSTheme class is a representation of a JSON Resume - theme asset. See also: FRESHTheme. + The JRSTheme class is a representation of a JSON Resume theme asset. @class JRSTheme */ function JRSTheme() { @@ -41,12 +40,31 @@ Definition of the JRSTheme class. // 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) this.formats = { - html: { title:'html', outFormat:'html', ext:'html' } + html: { outFormat: 'html', files: [ + { + action: 'transform', + render: this.render, + //path: absPath, + major: true, + //orgPath: PATH.relative(thFolder, absPath), + ext: pathInfo.extname.slice(1), + //title: friendlyName( outFmt ), + //pre: outFmt, + // outFormat: outFmt || pathInfo.name, + //data: FS.readFileSync( absPath, 'utf8' ), + css: null + } + ]} }; } else { diff --git a/src/eng/jrs-generator.js b/src/eng/jrs-generator.js new file mode 100644 index 00000000..17d26fe2 --- /dev/null +++ b/src/eng/jrs-generator.js @@ -0,0 +1,76 @@ +/** +Definition of the JRSGenerator class. +@license MIT. See LICENSE.md for details. +@module jrs-generator.js +*/ + + + +(function() { + + + + var _ = require('underscore') + , HANDLEBARS = require('handlebars') + , FS = require('fs') + , registerHelpers = require('./handlebars-helpers') + , PATH = require('path') + , parsePath = require('parse-filepath') + , READFILES = require('recursive-readdir-sync') + , SLASH = require('slash') + , MD = require('marked'); + + + + /** + Perform template-based resume generation for JSON Resume themes. + @class JRSGenerator + */ + var JRSGenerator = module.exports = { + + + + + generate: function( json, jst, format, cssInfo, opts, theme ) { + + // JSON Resume themes don't have a specific structure, so the safest thing + // to do is copy all files from source to dest. + // var COPY = require('copy'); + // var globs = [ '*.css', '*.js', '*.png', '*.jpg', '*.gif', '*.bmp' ]; + // COPY.sync( globs , outFolder, { + // cwd: theme.folder, nodir: true, + // ignore: ['node_modules/','node_modules/**'] + // // rewrite: function(p1, p2) { + // // return PATH.join(p2, p1); + // // } + // }); + + // Disable JRS theme chatter (console.log, console.error, etc.) + var off = ['log', 'error', 'dir'], org = off.map(function(c){ + var ret = console[c]; console[c] = function(){}; return ret; + }); + + // Freeze and render + var rezHtml = theme.render( json.harden() ); + + // Turn logging back on + off.forEach(function(c, idx){ console[c] = org[idx]; }); + + // Unfreeze and apply Markdown + rezHtml = rezHtml.replace( /@@@@~.*?~@@@@/gm, function(val){ + return MDIN( val.replace( /~@@@@/gm,'' ).replace( /@@@@~/gm,'' ) ); + }); + + return rezHtml; + } + + + }; + + + function MDIN(txt) { // TODO: Move this + return MD(txt || '' ).replace(/^\s*

|<\/p>\s*$/gi, ''); + } + + +}()); diff --git a/src/gen/html-pdf-cli-generator.js b/src/gen/html-pdf-cli-generator.js index ad360e13..c5921a51 100644 --- a/src/gen/html-pdf-cli-generator.js +++ b/src/gen/html-pdf-cli-generator.js @@ -20,7 +20,8 @@ Definition of the HtmlPdfCLIGenerator class. /** An HTML-driven PDF resume generator for HackMyResume. Talks to Phantom, - wkhtmltopdf, and other PDF libraries over a CLI. + 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({ @@ -36,6 +37,7 @@ Definition of the HtmlPdfCLIGenerator class. Generate the binary PDF. */ onBeforeSave: function( info ) { + console.log('Called'); try { var safe_eng = info.opts.pdf || 'wkhtmltopdf'; engines[ safe_eng ].call( this, info.mk, info.outputFile ); @@ -50,16 +52,23 @@ Definition of the HtmlPdfCLIGenerator class. } } + + }); + // TODO: Move each engine to a separate module var engines = { /** - Generate a PDF from HTML using wkhtmltopdf. + 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) { @@ -89,8 +98,11 @@ Definition of the HtmlPdfCLIGenerator class. /** - Generate a PDF from HTML using Phantom. - See: https://github.com/ariya/phantomjs/blob/master/examples/rasterize.js + Generate a PDF from HTML using Phantom's CLI interface. + Spawns a child process with `phantomjs