diff --git a/README.md b/README.md index 26e4e373..56ea67a6 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ Utilities for [Hexo]. - [hash](#hashstr) - [highlight](#highlightstr-options) - [htmlTag](#htmltagtag-attrs-text-escape) +- [joinPath](#joinpathpaths-option) - [isExternalLink](#isexternallinkurl-sitehost-exclude) - [Pattern](#patternrule) - [Permalink](#permalinkrule-options) @@ -324,6 +325,51 @@ htmlTag('script', {src: '/foo.js', async: true}, '') // ``` +### joinPath([...paths][, option]) + +Join and format paths. + +Option | Description | Default +--- | --- | --- +`sep` | Path separator | [`path.sep`](https://nodejs.org/api/path.html#path_path_sep) + +``` js +joinPath('foo/bar\\baz') +// Windows: foo\bar\baz +// Unix: foo/bar/baz + +joinPath('foo/bar/baz', 'lorem\\ipsum\\dolor') +// Windows: foo\bar\baz\lorem\ipsum\dolor +// Unix: foo/bar/baz/lorem/ipsum/dolor + +joinPath('foo/bar/baz', 'lorem\\ipsum\\dolor', { sep: '/' }) +// foo/bar/baz/lorem/ipsum/dolor + +joinPath('foo/', '/bar', '/baz') +// Windows: foo\bar\baz +// Unix: foo/bar/baz + +/* If the joined path string is a zero-length string, then '.' will be returned, +representing the current working directory. */ +joinPath() +// . + +joinPath('') +// . +``` + +Platform-specific separator also can be used: + +``` js +const { sep } = require('path').win32 +joinPath('foo/', '/bar', '/baz', { sep }) +// foo\bar\baz + +const { sep } = require('path').posix +joinPath('foo/', '/bar', '/baz', { sep }) +// foo/bar/baz +``` + ### isExternalLink(url, sitehost, [exclude]) Option | Description | Default diff --git a/lib/index.js b/lib/index.js index 66634015..6df90277 100644 --- a/lib/index.js +++ b/lib/index.js @@ -20,6 +20,7 @@ exports.HashStream = hash.HashStream; exports.highlight = require('./highlight'); exports.htmlTag = require('./html_tag'); exports.isExternalLink = require('./is_external_link'); +exports.joinPath = require('./join_path'); exports.Pattern = require('./pattern'); exports.Permalink = require('./permalink'); exports.prettyUrls = require('./pretty_urls'); diff --git a/lib/join_path.js b/lib/join_path.js new file mode 100644 index 00000000..c136499b --- /dev/null +++ b/lib/join_path.js @@ -0,0 +1,26 @@ +'use strict'; + +const { sep: sepPlatform } = require('path'); + +const formatPath = (str, sep) => { + const rDedup = new RegExp('\\' + sep + '{2,}', 'g'); + return str.replace(/\/|\\/g, sep).replace(rDedup, sep); +}; + +const joinPath = (...args) => { + const argsSize = args.length; + + // defaults to platform-specific path separator + let sep = sepPlatform; + if (typeof args[argsSize - 1] === 'object') sep = args.pop().sep; + + const result = args.join(sep); + + // Similar behaviour as path.join() + // https://nodejs.org/api/path.html#path_path_join_paths + if (result.length === 0) return '.'; + + return formatPath(result, sep); +}; + +module.exports = joinPath; diff --git a/test/join_path.spec.js b/test/join_path.spec.js new file mode 100644 index 00000000..52e6c1e5 --- /dev/null +++ b/test/join_path.spec.js @@ -0,0 +1,64 @@ +'use strict'; + +require('chai').should(); +const { sep } = require('path'); +const join = require('../lib/join_path'); + +describe('joinPath', () => { + it('one path', () => { + const content = 'foo/bar'; + join(content).should.eql('foo' + sep + 'bar'); + }); + + it('no argument', () => { + join().should.eql('.'); + }); + + it('zero-length string', () => { + join('').should.eql('.'); + }); + + it('two paths', () => { + join('foo', 'bar').should.eql('foo' + sep + 'bar'); + }); + + it('one path - custom separator', () => { + const custom = '\\'; + join('foo/bar', { sep: custom }).should.eql('foo' + custom + 'bar'); + }); + + it('two paths - custom separator', () => { + const custom = '\\'; + join('foo', 'bar', { sep: custom }).should.eql('foo' + custom + 'bar'); + }); + + it('deduplicate separators', () => { + join('foo/', '/bar').should.eql('foo' + sep + 'bar'); + }); + + it('starting and ending slashes', () => { + join('/foo/', '/bar/').should.eql(sep + 'foo' + sep + 'bar' + sep); + }); + + it('mixed slashes', () => { + join('foo/', '\\bar').should.eql('foo' + sep + 'bar'); + }); + + it('mixed and >2 slashes', () => { + join('foo////', '\\\\bar').should.eql('foo' + sep + 'bar'); + }); + + it('mixed and >2 slashes - custom separator', () => { + const custom = '\\'; + join('foo////', '\\\\bar', { sep: custom }).should.eql('foo' + custom + 'bar'); + }); + + it('three paths', () => { + join('foo', 'bar', 'baz').should.eql('foo' + sep + 'bar' + sep + 'baz'); + }); + + it('three paths - custom separator', () => { + const custom = '\\'; + join('foo', 'bar', 'baz', { sep: custom }).should.eql('foo' + custom + 'bar' + custom + 'baz'); + }); +});