diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..765ba8b --- /dev/null +++ b/.babelrc @@ -0,0 +1,23 @@ +{ + "env": { + "test": { + "presets": [ ["env", { "targets": { "node": "current" } }] ], + "plugins": [] + }, + + "v4": { + "presets": [ ["env", { "targets": { "node": 4.0 } }] ], + "plugins": ["babel-plugin-add-module-exports"] + }, + + "v6": { + "presets": [ ["env", { "targets": { "node": 6.0 } }] ], + "plugins": ["babel-plugin-add-module-exports"] + }, + + "v7": { + "presets": [ ["env", { "targets": { "node": 7.0 } }] ], + "plugins": ["babel-plugin-add-module-exports"] + } + } +} diff --git a/.codeclimate.yml b/.codeclimate.yml new file mode 100644 index 0000000..faf3e94 --- /dev/null +++ b/.codeclimate.yml @@ -0,0 +1,17 @@ +engines: + fixme: + enabled: true + + duplication: + enabled: true + config: + languages: + - javascript + + eslint: + enabled: true + +ratings: + paths: + - src/** + - test/** diff --git a/.editorconfig b/.editorconfig index e717f5e..435e3c7 100644 --- a/.editorconfig +++ b/.editorconfig @@ -2,12 +2,12 @@ root = true [*] -indent_style = space -indent_size = 2 -end_of_line = lf charset = utf-8 -trim_trailing_whitespace = true +end_of_line = lf +indent_size = 2 +indent_style = space insert_final_newline = true +trim_trailing_whitespace = true [*.md] trim_trailing_whitespace = false diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index 176a458..0000000 --- a/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -* text=auto diff --git a/.gitignore b/.gitignore index dddca89..1bfd37a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ *.log -node_modules -coverage* +/lib +/node_modules +/.nyc_output diff --git a/.jshintrc b/.jshintrc deleted file mode 100644 index 086f6ba..0000000 --- a/.jshintrc +++ /dev/null @@ -1,4 +0,0 @@ -{ - "asi": true, - "node": true -} diff --git a/.travis.yml b/.travis.yml index 4c6ee55..cc70a14 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,32 @@ -sudo: false - language: node_js node_js: - 4 - 6 + - 7 + +env: + - BABEL_ENV=test + +matrix: + fast_finish: true -after_script: +cache: + directories: + - node_modules + +before_script: + - npm prune + +after_success: - npm run coverage - npm run codeclimate + - BABEL_ENV=v4 npm run compile -- -d lib + - BABEL_ENV=v6 npm run compile -- -d lib/node6 + - BABEL_ENV=v7 npm run compile -- -d lib/node7 + - npm run semantic-release + +branches: + except: + - /^v\d+\.\d+\.\d+$/ + - /^greenkeeper\/.+/ diff --git a/LICENSE b/LICENSE index d527871..ca55c91 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,13 @@ -The MIT License (MIT) - -Copyright (c) 2015 Ahmad Nassri (https://www.ahmadnassri.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +Copyright (c) 2015, Ahmad Nassri + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/README.md b/README.md index 550f788..2918111 100644 --- a/README.md +++ b/README.md @@ -6,26 +6,65 @@ A Metalsmith plugin that adds file path values (`base`, `dir`, `ext`, `name`, `h [![Downloads][npm-downloads]][npm-url] [![Code Climate][codeclimate-quality]][codeclimate-url] [![Coverage Status][codeclimate-coverage]][codeclimate-url] +[![Dependency Status][dependencyci-image]][dependencyci-url] [![Dependencies][david-image]][david-url] ## Install -```sh -npm install --save metalsmith-paths +```bash +npm install --production --save metalsmith-paths ``` -## API +## Usage + +I recommend using an optimized build matching your Node.js environment version, otherwise, the standard `require` would work just fine with any version of Node `>= v4.0` . ```js -var Metalsmith = require('metalsmith') +/* + * Node 7 + */ +const paths = require('metalsmith-paths/lib/node7') + +/* + * Node 6 + */ +const paths = require('metalsmith-paths/lib/node6') + +/* + * Node 4 (Default) + * Note: additional ES2015 polyfills may be required + */ var paths = require('metalsmith-paths') +``` + +## API -var metalsmith = new Metalsmith(__dirname) +```js +const metalsmith = new Metalsmith(__dirname) .use(paths({ property: "paths" })) ``` +given the following directory structure: + +``` +src/ +├── blog +    └── post.html +``` + +The following metadata will be generated: + +| metadata | value | +| ------------- | ----------------- | +| `path.base` | `post.html` | +| `path.dir` | `blog` | +| `path.ext` | `.html` | +| `path.name` | `post` | +| `path.href` | `/blog/post.html` | +| `path.dhref` | `/blog/` | + ## CLI You can also use the plugin with the Metalsmith CLI by adding a key to your `metalsmith.json` file: @@ -72,20 +111,14 @@ Would produce the following filenames: | /portfolio/project1.html | /portfolio/project1.html | | /portfolio/project2.html | /portfolio/project2.html | -## Support - -Donations are welcome to help support the continuous development of this project. - -[![Gratipay][gratipay-image]][gratipay-url] -[![PayPal][paypal-image]][paypal-url] -[![Flattr][flattr-image]][flattr-url] -[![Bitcoin][bitcoin-image]][bitcoin-url] -## License +---- +> :copyright: [ahmadnassri.com](https://www.ahmadnassri.com/)  ·  +> License: [ISC][license-url]  ·  +> Github: [@ahmadnassri](https://github.com/ahmadnassri)  ·  +> Twitter: [@ahmadnassri](https://twitter.com/ahmadnassri) -[MIT](LICENSE) © [Ahmad Nassri](https://www.ahmadnassri.com) - -[license-url]: https://github.com/ahmadnassri/metalsmith-paths/blob/master/LICENSE +[license-url]: http://choosealicense.com/licenses/isc/ [travis-url]: https://travis-ci.org/ahmadnassri/metalsmith-paths [travis-image]: https://img.shields.io/travis/ahmadnassri/metalsmith-paths.svg?style=flat-square @@ -102,14 +135,5 @@ Donations are welcome to help support the continuous development of this project [david-url]: https://david-dm.org/ahmadnassri/metalsmith-paths [david-image]: https://img.shields.io/david/ahmadnassri/metalsmith-paths.svg?style=flat-square -[gratipay-url]: https://www.gratipay.com/ahmadnassri/ -[gratipay-image]: https://img.shields.io/gratipay/ahmadnassri.svg?style=flat-square - -[paypal-url]: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=UJ2B2BTK9VLRS&on0=project&os0=metalsmith-paths -[paypal-image]: http://img.shields.io/badge/paypal-donate-green.svg?style=flat-square - -[flattr-url]: https://flattr.com/submit/auto?user_id=ahmadnassri&url=https://github.com/ahmadnassri/metalsmith-paths&title=metalsmith-paths&language=&tags=github&category=software -[flattr-image]: http://img.shields.io/badge/flattr-donate-green.svg?style=flat-square - -[bitcoin-image]: http://img.shields.io/badge/bitcoin-1Nb46sZRVG3or7pNaDjthcGJpWhvoPpCxy-green.svg?style=flat-square -[bitcoin-url]: https://www.coinbase.com/checkouts/ae383ae6bb931a2fa5ad11cec115191e?name=metalsmith-paths +[dependencyci-url]: https://dependencyci.com/github/ahmadnassri/metalsmith-paths +[dependencyci-image]: https://dependencyci.com/github/ahmadnassri/metalsmith-paths/badge?style=flat-square diff --git a/lib/index.js b/lib/index.js deleted file mode 100644 index 977fb8e..0000000 --- a/lib/index.js +++ /dev/null @@ -1,58 +0,0 @@ -'use strict' - -var debug = require('debug-log')('metalsmith-paths') -var path = require('path') - -/** - * @param {Object} options - * @return {Function} - */ - -module.exports = function plugin (options) { - var opts = options || {} - var prop = opts.property || 'path' - var directoryIndex = opts.directoryIndex || false - - return function (files, metalsmith, done) { - setImmediate(done) - - Object.keys(files).forEach(function (file) { - debug('process file: %s', file) - - if (path.parse) { - debug('[node >= 0.11.15] using path.parse') - - files[file][prop] = path.parse(file) - } else { - // add file path info - var extname = path.extname(file) - - files[file][prop] = { - base: path.basename(file), - dir: path.dirname(file), - ext: extname, - name: path.basename(file, extname) - } - } - - // In some versions of node/path, 'dir' at root may be either '.' or empty - // Normalize this property to be empty - if (files[file][prop].dir === '.') { - files[file][prop].dir = '' - } - - // generate href - var href = '/' - - if (files[file][prop].dir && files[file][prop].dir !== '.') { - href += files[file][prop].dir + '/' - } - - if (!directoryIndex || path.basename(file) !== directoryIndex) { - href += path.basename(file) - } - - files[file][prop].href = href - }) - } -} diff --git a/package.json b/package.json index 0f458e4..41b8495 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,14 @@ { - "version": "2.1.2", + "version": "0.0.0-development", "name": "metalsmith-paths", "description": "A metalsmith plugin that adds file path values to metadata.", "author": "Ahmad Nassri (https://www.ahmadnassri.com/)", "homepage": "https://github.com/ahmadnassri/metalsmith-paths", - "repository": "ahmadnassri/metalsmith-paths", - "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/ahmadnassri/metalsmith-paths.git" + }, + "license": "ISC", "main": "lib/index.js", "keywords": [ "basename", @@ -16,35 +19,49 @@ "plugin" ], "engines": { - "node": ">= 4" + "node": ">=4" }, "files": [ - "lib" + "lib", + "src" ], "bugs": { "url": "https://github.com/ahmadnassri/metalsmith-paths/issues" }, "scripts": { - "pretest": "standard && echint", - "test": "mocha", - "posttest": "npm run coverage", - "coverage": "istanbul cover --dir coverage _mocha -- -R dot", - "codeclimate": "codeclimate-test-reporter < coverage/lcov.info" + "compile": "babel -q src", + "test": "BABEL_ENV=test tap test/*.js --node-arg=--require --node-arg=babel-register | tap-mocha-reporter spec", + "pretest": "snazzy && echint", + "coverage": "BABEL_ENV=test tap test/*.js --coverage --nyc-arg=--require --nyc-arg=babel-register", + "codeclimate": "nyc report --reporter=text-lcov | codeclimate-test-reporter", + "semantic-release": "semantic-release pre && npm publish && semantic-release post" + }, + "standard": { + "ignore": [ + "lib/**" + ] }, "echint": { "ignore": [ - "coverage/**" + "lib/**" ] }, + "config": { + "commitizen": { + "path": "./node_modules/cz-conventional-changelog" + } + }, "devDependencies": { - "codeclimate-test-reporter": "0.4.0", + "babel-cli": "^6.18.0", + "babel-plugin-add-module-exports": "^0.2.1", + "babel-preset-env": "0.0.8", + "babel-register": "^6.18.0", + "codeclimate-test-reporter": "^0.4.0", + "cz-conventional-changelog": "^1.2.0", "echint": "^1.5.3", - "istanbul": "^0.4.2", - "mocha": "^3.0.1", - "should": "^11.0.0", - "standard": "^8.0.0" - }, - "dependencies": { - "debug-log": "^1.0.0" + "semantic-release": "^6.3.2", + "snazzy": "^5.0.0", + "tap": "^8.0.1", + "tap-mocha-reporter": "^3.0.0" } } diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..c13cd45 --- /dev/null +++ b/src/index.js @@ -0,0 +1,63 @@ +import path from 'path' +import { debuglog } from 'util' + +const debug = debuglog('metalsmith-paths') + +/** + * @param {Object} options + * @return {Function} + */ + +module.exports = function plugin (options) { + options = Object.assign({}, { + property: 'path', + directoryIndex: false, + hrefIndex: false + }, options) + + return function (files, metalsmith, done) { + setImmediate(done) + + Object.keys(files).forEach((file) => { + debug('process file: %s', file) + + if (path.parse) { + debug('[node >= 0.11.15] using path.parse') + + files[file][options.property] = path.parse(file) + } else { + // add file path info + let extname = path.extname(file) + + files[file][options.property] = { + base: path.basename(file), + dir: path.dirname(file), + ext: extname, + name: path.basename(file, extname) + } + } + + // In some versions of node/path, 'dir' at root may be either '.' or empty + // Normalize this property to be empty + if (files[file][options.property].dir === '.') { + files[file][options.property].dir = '' + } + + // generate href + let href = '/' + + if (files[file][options.property].dir && files[file][options.property].dir !== '.') { + href += files[file][options.property].dir + '/' + } + + const dhref = href + + if (!options.directoryIndex || path.basename(file) !== options.directoryIndex) { + href += path.basename(file) + } + + files[file][options.property].href = href + files[file][options.property].dhref = dhref + }) + } +} diff --git a/test/index.js b/test/index.js index 3ebba63..bd7ecc3 100644 --- a/test/index.js +++ b/test/index.js @@ -1,117 +1,106 @@ -/* global describe, it */ +import plugin from '../src' +import { test } from 'tap' -'use strict' +test('should be a plugin', (assert) => { + assert.plan(8) -var plugin = require('..') + assert.type(plugin, 'function') -require('should') + let files = { + 'path/to/file.ext': {} + } -describe('Metalsmith Paths', function () { - it('should be a plugin', function (done) { - plugin.should.be.a.Function + plugin()(files, null, () => { + assert.type(files['path/to/file.ext'].path, Object) - var files = { - 'path/to/file.ext': {} - } + assert.equal(files['path/to/file.ext'].path.base, 'file.ext') + assert.equal(files['path/to/file.ext'].path.dir, 'path/to') + assert.equal(files['path/to/file.ext'].path.ext, '.ext') + assert.equal(files['path/to/file.ext'].path.name, 'file') + assert.equal(files['path/to/file.ext'].path.href, '/path/to/file.ext') + assert.equal(files['path/to/file.ext'].path.dhref, '/path/to/') + }) +}) + +test('should use custom property', (assert) => { + assert.plan(7) - plugin()(files, null, function () { - files['path/to/file.ext'].should.have.property('path').and.be.an.Object + let files = { + 'path/to/file.ext': {} + } - files['path/to/file.ext'].path.should.have.property('base').and.equal('file.ext') - files['path/to/file.ext'].path.should.have.property('dir').and.equal('path/to') - files['path/to/file.ext'].path.should.have.property('ext').and.equal('.ext') - files['path/to/file.ext'].path.should.have.property('name').and.equal('file') - files['path/to/file.ext'].path.should.have.property('href').and.equal('/path/to/file.ext') + plugin({ property: 'foo' })(files, null, () => { + assert.type(files['path/to/file.ext'].foo, Object) - done() - }) + assert.equal(files['path/to/file.ext'].foo.base, 'file.ext') + assert.equal(files['path/to/file.ext'].foo.dir, 'path/to') + assert.equal(files['path/to/file.ext'].foo.ext, '.ext') + assert.equal(files['path/to/file.ext'].foo.name, 'file') + assert.equal(files['path/to/file.ext'].foo.href, '/path/to/file.ext') + assert.equal(files['path/to/file.ext'].foo.dhref, '/path/to/') }) +}) - it('should use custom property', function (done) { - plugin.should.be.a.Function +test('should respect directory indexes', (assert) => { + assert.plan(13) - var files = { - 'path/to/file.ext': {} - } + let files = { + 'path/to/index.html': {}, + 'path/to/file.ext': {} + } - plugin({ - property: 'foo' - })(files, null, function () { - files['path/to/file.ext'].should.have.property('foo').and.be.an.Object + plugin({ directoryIndex: 'index.html' })(files, null, () => { + assert.type(files['path/to/index.html'].path, Object) - files['path/to/file.ext'].foo.should.have.property('base').and.equal('file.ext') - files['path/to/file.ext'].foo.should.have.property('dir').and.equal('path/to') - files['path/to/file.ext'].foo.should.have.property('ext').and.equal('.ext') - files['path/to/file.ext'].foo.should.have.property('name').and.equal('file') - files['path/to/file.ext'].foo.should.have.property('href').and.equal('/path/to/file.ext') + assert.equal(files['path/to/index.html'].path.base, 'index.html') + assert.equal(files['path/to/index.html'].path.dir, 'path/to') + assert.equal(files['path/to/index.html'].path.ext, '.html') + assert.equal(files['path/to/index.html'].path.name, 'index') + assert.equal(files['path/to/index.html'].path.href, '/path/to/') - done() - }) - }) + assert.type(files['path/to/file.ext'].path, Object) - it('should respect directory indexes', function (done) { - plugin.should.be.a.Function - - var files = { - 'path/to/index.html': {}, - 'path/to/file.ext': {} - } - - plugin({ - directoryIndex: 'index.html' - })(files, null, function () { - files['path/to/index.html'].should.have.property('path').and.be.an.Object - files['path/to/index.html'].path.should.have.property('base').and.equal('index.html') - files['path/to/index.html'].path.should.have.property('dir').and.equal('path/to') - files['path/to/index.html'].path.should.have.property('ext').and.equal('.html') - files['path/to/index.html'].path.should.have.property('name').and.equal('index') - files['path/to/index.html'].path.should.have.property('href').and.equal('/path/to/') - - files['path/to/file.ext'].should.have.property('path').and.be.an.Object - files['path/to/file.ext'].path.should.have.property('base').and.equal('file.ext') - files['path/to/file.ext'].path.should.have.property('dir').and.equal('path/to') - files['path/to/file.ext'].path.should.have.property('ext').and.equal('.ext') - files['path/to/file.ext'].path.should.have.property('name').and.equal('file') - files['path/to/file.ext'].path.should.have.property('href').and.equal('/path/to/file.ext') - - done() - }) + assert.equal(files['path/to/file.ext'].path.base, 'file.ext') + assert.equal(files['path/to/file.ext'].path.dir, 'path/to') + assert.equal(files['path/to/file.ext'].path.ext, '.ext') + assert.equal(files['path/to/file.ext'].path.name, 'file') + assert.equal(files['path/to/file.ext'].path.href, '/path/to/file.ext') + assert.equal(files['path/to/file.ext'].path.dhref, '/path/to/') }) +}) - it('should return slash on directoryIndex root', function (done) { - plugin.should.be.a.Function - - var files = { - 'index.html': {}, - 'directory/index.html': {}, - 'directory/file.html': {} - } - - plugin({ - directoryIndex: 'index.html' - })(files, null, function () { - files['index.html'].should.have.property('path').and.be.an.Object - files['index.html'].path.should.have.property('base').and.equal('index.html') - files['index.html'].path.should.have.property('dir').and.equal('') - files['index.html'].path.should.have.property('ext').and.equal('.html') - files['index.html'].path.should.have.property('name').and.equal('index') - files['index.html'].path.should.have.property('href').and.equal('/') - - files['directory/index.html'].should.have.property('path').and.be.an.Object - files['directory/index.html'].path.should.have.property('base').and.equal('index.html') - files['directory/index.html'].path.should.have.property('dir').and.equal('directory') - files['directory/index.html'].path.should.have.property('ext').and.equal('.html') - files['directory/index.html'].path.should.have.property('name').and.equal('index') - files['directory/index.html'].path.should.have.property('href').and.equal('/directory/') - - files['directory/file.html'].should.have.property('path').and.be.an.Object - files['directory/file.html'].path.should.have.property('base').and.equal('file.html') - files['directory/file.html'].path.should.have.property('dir').and.equal('directory') - files['directory/file.html'].path.should.have.property('ext').and.equal('.html') - files['directory/file.html'].path.should.have.property('name').and.equal('file') - files['directory/file.html'].path.should.have.property('href').and.equal('/directory/file.html') - - done() - }) +test('should return slash on directoryIndex root', (assert) => { + assert.plan(21) + + let files = { + 'index.html': {}, + 'directory/index.html': {}, + 'directory/file.html': {} + } + + plugin({ directoryIndex: 'index.html' })(files, null, () => { + assert.type(files['index.html'].path, Object) + assert.equal(files['index.html'].path.base, 'index.html') + assert.equal(files['index.html'].path.dir, '') + assert.equal(files['index.html'].path.ext, '.html') + assert.equal(files['index.html'].path.name, 'index') + assert.equal(files['index.html'].path.href, '/') + assert.equal(files['index.html'].path.dhref, '/') + + assert.type(files['directory/index.html'].path, Object) + assert.equal(files['directory/index.html'].path.base, 'index.html') + assert.equal(files['directory/index.html'].path.dir, 'directory') + assert.equal(files['directory/index.html'].path.ext, '.html') + assert.equal(files['directory/index.html'].path.name, 'index') + assert.equal(files['directory/index.html'].path.href, '/directory/') + assert.equal(files['directory/index.html'].path.dhref, '/directory/') + + assert.type(files['directory/file.html'].path, Object) + assert.equal(files['directory/file.html'].path.base, 'file.html') + assert.equal(files['directory/file.html'].path.dir, 'directory') + assert.equal(files['directory/file.html'].path.ext, '.html') + assert.equal(files['directory/file.html'].path.name, 'file') + assert.equal(files['directory/file.html'].path.href, '/directory/file.html') + assert.equal(files['directory/file.html'].path.dhref, '/directory/') }) })