diff --git a/package.json b/package.json index 85697a8..99a8d50 100644 --- a/package.json +++ b/package.json @@ -27,22 +27,30 @@ "license": "MIT", "dependencies": { "buffer": "^5.2.1", + "err-code": "^2.0.0", + "fs-extra": "^8.1.0", "is-buffer": "^2.0.3", "is-electron": "^2.2.0", "is-pull-stream": "0.0.0", "is-stream": "^2.0.0", + "it-glob": "0.0.4", "kind-of": "^6.0.2", "readable-stream": "^3.4.0" }, "devDependencies": { "aegir": "^20.0.0", "chai": "^4.2.0", + "chai-as-promised": "^7.1.1", "dirty-chai": "^2.0.1", "electron": "^5.0.7", "electron-mocha": "^8.0.3", + "is-node": "^1.0.2", "pull-stream": "^3.6.13" }, "contributors": [ "Hugo Dias " - ] + ], + "browser": { + "fs-extra": false + } } diff --git a/src/files/glob-source.js b/src/files/glob-source.js new file mode 100644 index 0000000..56a67de --- /dev/null +++ b/src/files/glob-source.js @@ -0,0 +1,89 @@ +'use strict' + +const fs = require('fs-extra') +const glob = require('it-glob') +const Path = require('path') +const errCode = require('err-code') + +/** +* Create an async iterator that yields paths that match requested file paths. +* +* @param {String} ...paths File system path(s) to glob from +* @param {Object} [options] Optional options +* @param {Boolean} [options.recursive] Recursively glob all paths in directories +* @param {Boolean} [options.hidden] Include .dot files in matched paths +* @param {Array} [options.ignore] Glob paths to ignore +* @param {Boolean} [options.followSymlinks] follow symlinks +* @yields {Object} File objects in the form `{ path: String, content: AsyncIterator }` +*/ +module.exports = async function * globSource (...args) { + const options = typeof args[args.length - 1] === 'string' ? {} : args.pop() + const paths = args + + const globSourceOptions = { + recursive: options.recursive, + glob: { + dot: Boolean(options.hidden), + ignore: Array.isArray(options.ignore) ? options.ignore : [], + follow: options.followSymlinks != null ? options.followSymlinks : true + } + } + + // Check the input paths comply with options.recursive and convert to glob sources + for (const path of paths) { + if (typeof path !== 'string') { + throw errCode( + new Error(`Path must be a string`), + 'ERR_INVALID_PATH', + { path } + ) + } + + const absolutePath = Path.resolve(process.cwd(), path) + const stat = await fs.stat(absolutePath) + const prefix = Path.dirname(absolutePath) + + for await (const entry of toGlobSource({ path, type: stat.isDirectory() ? 'dir' : 'file', prefix }, globSourceOptions)) { + yield entry + } + } +} + +async function * toGlobSource ({ path, type, prefix }, options) { + options = options || {} + + const baseName = Path.basename(path) + + if (type === 'file') { + yield { + path: baseName.replace(prefix, ''), + content: fs.createReadStream(Path.isAbsolute(path) ? path : Path.join(process.cwd(), path)) + } + + return + } + + if (type === 'dir' && !options.recursive) { + throw errCode( + new Error(`'${path}' is a directory and recursive option not set`), + 'ERR_DIR_NON_RECURSIVE', + { path } + ) + } + + const globOptions = Object.assign({}, options.glob, { + cwd: path, + nodir: true, + realpath: false, + absolute: true + }) + + for await (const p of glob(path, '**/*', globOptions)) { + yield { + path: toPosix(p.replace(prefix, '')), + content: fs.createReadStream(p) + } + } +} + +const toPosix = path => path.replace(/\\/g, '/') diff --git a/test/files/glob-source.spec.js b/test/files/glob-source.spec.js new file mode 100644 index 0000000..3f7efa9 --- /dev/null +++ b/test/files/glob-source.spec.js @@ -0,0 +1,108 @@ +'use strict' + +/* eslint-env mocha */ +const chai = require('chai') +const dirtyChai = require('dirty-chai') +const chaiAsPromised = require('chai-as-promised') +const globSource = require('../../src/files/glob-source') +const all = require('async-iterator-all') +const path = require('path') +const isNode = require('is-node') + +chai.use(dirtyChai) +chai.use(chaiAsPromised) +const expect = chai.expect + +describe('glob-source', () => { + it('single file, relative path', async function () { + if (!isNode) { + return this.skip() + } + + const result = await all(globSource(path.relative(process.cwd(), path.join(__dirname, '..', 'fixtures', 'file-0.html')))) + + expect(result.length).to.equal(1) + expect(result[0].path).to.equal('file-0.html') + }) + + it('directory, relative path', async function () { + if (!isNode) { + return this.skip() + } + + const result = await all(globSource(path.relative(process.cwd(), path.join(__dirname, '..', 'fixtures', 'dir')), { + recursive: true + })) + + expect(result.length).to.equal(3) + expect(result[0].path).to.equal('/dir/file-1.txt') + expect(result[1].path).to.equal('/dir/file-2.js') + expect(result[2].path).to.equal('/dir/file-3.css') + }) + + it('single file, absolute path', async function () { + if (!isNode) { + return this.skip() + } + + const result = await all(globSource(path.resolve(process.cwd(), path.join(__dirname, '..', 'fixtures', 'file-0.html')))) + + expect(result.length).to.equal(1) + expect(result[0].path).to.equal('file-0.html') + }) + + it('directory, relative path', async function () { + if (!isNode) { + return this.skip() + } + + const result = await all(globSource(path.resolve(process.cwd(), path.join(__dirname, '..', 'fixtures', 'dir')), { + recursive: true + })) + + expect(result.length).to.equal(3) + expect(result[0].path).to.equal('/dir/file-1.txt') + expect(result[1].path).to.equal('/dir/file-2.js') + expect(result[2].path).to.equal('/dir/file-3.css') + }) + + it('directory, hidden files', async function () { + if (!isNode) { + return this.skip() + } + + const result = await all(globSource(path.resolve(process.cwd(), path.join(__dirname, '..', 'fixtures', 'dir')), { + recursive: true, + hidden: true + })) + + expect(result.length).to.equal(4) + expect(result[0].path).to.equal('/dir/.hidden.txt') + expect(result[1].path).to.equal('/dir/file-1.txt') + expect(result[2].path).to.equal('/dir/file-2.js') + expect(result[3].path).to.equal('/dir/file-3.css') + }) + + it('directory, ignore files', async function () { + if (!isNode) { + return this.skip() + } + + const result = await all(globSource(path.resolve(process.cwd(), path.join(__dirname, '..', 'fixtures', 'dir')), { + recursive: true, + ignore: ['**/file-1.txt'] + })) + + expect(result.length).to.equal(2) + expect(result[0].path).to.equal('/dir/file-2.js') + expect(result[1].path).to.equal('/dir/file-3.css') + }) + + it('require recusive flag for directory', async function () { + if (!isNode) { + return this.skip() + } + + await expect(all(globSource(path.resolve(process.cwd(), path.join(__dirname, '..', 'fixtures', 'dir'))))).to.be.rejectedWith(/recursive option not set/) + }) +}) diff --git a/test/fixtures/dir/.hidden.txt b/test/fixtures/dir/.hidden.txt new file mode 100644 index 0000000..e69de29 diff --git a/test/fixtures/dir/file-1.txt b/test/fixtures/dir/file-1.txt new file mode 100644 index 0000000..e69de29 diff --git a/test/fixtures/dir/file-2.js b/test/fixtures/dir/file-2.js new file mode 100644 index 0000000..e69de29 diff --git a/test/fixtures/dir/file-3.css b/test/fixtures/dir/file-3.css new file mode 100644 index 0000000..e69de29 diff --git a/test/fixtures/file-0.html b/test/fixtures/file-0.html new file mode 100644 index 0000000..e69de29