Skip to content

Commit 4e4fcf0

Browse files
committed
feat: add glob-source from js-ipfs
The business of turning a path and pattern into an iterator of files is duplicated between js-ipfs and js-ipfs-http-client so moving it here to aid deduplication.
1 parent b30d7a3 commit 4e4fcf0

File tree

8 files changed

+206
-1
lines changed

8 files changed

+206
-1
lines changed

package.json

+9-1
Original file line numberDiff line numberDiff line change
@@ -27,22 +27,30 @@
2727
"license": "MIT",
2828
"dependencies": {
2929
"buffer": "^5.2.1",
30+
"err-code": "^2.0.0",
31+
"fs-extra": "^8.1.0",
3032
"is-buffer": "^2.0.3",
3133
"is-electron": "^2.2.0",
3234
"is-pull-stream": "0.0.0",
3335
"is-stream": "^2.0.0",
36+
"it-glob": "0.0.4",
3437
"kind-of": "^6.0.2",
3538
"readable-stream": "^3.4.0"
3639
},
3740
"devDependencies": {
3841
"aegir": "^20.0.0",
3942
"chai": "^4.2.0",
43+
"chai-as-promised": "^7.1.1",
4044
"dirty-chai": "^2.0.1",
4145
"electron": "^5.0.7",
4246
"electron-mocha": "^8.0.3",
47+
"is-node": "^1.0.2",
4348
"pull-stream": "^3.6.13"
4449
},
4550
"contributors": [
4651
"Hugo Dias <[email protected]>"
47-
]
52+
],
53+
"browser": {
54+
"fs-extra": false
55+
}
4856
}

src/files/glob-source.js

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
'use strict'
2+
3+
const fs = require('fs-extra')
4+
const glob = require('it-glob')
5+
const Path = require('path')
6+
const errCode = require('err-code')
7+
8+
/**
9+
* Create an async iterator that yields paths that match requested file paths.
10+
*
11+
* @param {String} ...paths File system path(s) to glob from
12+
* @param {Object} [options] Optional options
13+
* @param {Boolean} [options.recursive] Recursively glob all paths in directories
14+
* @param {Boolean} [options.hidden] Include .dot files in matched paths
15+
* @param {Array<String>} [options.ignore] Glob paths to ignore
16+
* @param {Boolean} [options.followSymlinks] follow symlinks
17+
* @yields {Object} File objects in the form `{ path: String, content: AsyncIterator<Buffer> }`
18+
*/
19+
module.exports = async function * globSource (...args) {
20+
const options = typeof args[args.length - 1] === 'string' ? {} : args.pop()
21+
const paths = args
22+
23+
const globSourceOptions = {
24+
recursive: options.recursive,
25+
glob: {
26+
dot: Boolean(options.hidden),
27+
ignore: Array.isArray(options.ignore) ? options.ignore : [],
28+
follow: options.followSymlinks != null ? options.followSymlinks : true
29+
}
30+
}
31+
32+
// Check the input paths comply with options.recursive and convert to glob sources
33+
for (const path of paths) {
34+
if (typeof path !== 'string') {
35+
throw errCode(
36+
new Error(`Path must be a string`),
37+
'ERR_INVALID_PATH',
38+
{ path }
39+
)
40+
}
41+
42+
const absolutePath = Path.resolve(process.cwd(), path)
43+
const stat = await fs.stat(absolutePath)
44+
const prefix = Path.dirname(absolutePath)
45+
46+
for await (const entry of toGlobSource({ path, type: stat.isDirectory() ? 'dir' : 'file', prefix }, globSourceOptions)) {
47+
yield entry
48+
}
49+
}
50+
}
51+
52+
async function * toGlobSource ({ path, type, prefix }, options) {
53+
options = options || {}
54+
55+
const baseName = Path.basename(path)
56+
57+
if (type === 'file') {
58+
yield {
59+
path: baseName.replace(prefix, ''),
60+
content: fs.createReadStream(Path.isAbsolute(path) ? path : Path.join(process.cwd(), path))
61+
}
62+
63+
return
64+
}
65+
66+
if (type === 'dir' && !options.recursive) {
67+
throw errCode(
68+
new Error(`'${path}' is a directory and recursive option not set`),
69+
'ERR_DIR_NON_RECURSIVE',
70+
{ path }
71+
)
72+
}
73+
74+
const globOptions = Object.assign({}, options.glob, {
75+
cwd: path,
76+
nodir: true,
77+
realpath: false,
78+
absolute: true
79+
})
80+
81+
for await (const p of glob(path, '**/*', globOptions)) {
82+
yield {
83+
path: toPosix(p.replace(prefix, '')),
84+
content: fs.createReadStream(p)
85+
}
86+
}
87+
}
88+
89+
const toPosix = path => path.replace(/\\/g, '/')

test/files/glob-source.spec.js

+108
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
'use strict'
2+
3+
/* eslint-env mocha */
4+
const chai = require('chai')
5+
const dirtyChai = require('dirty-chai')
6+
const chaiAsPromised = require('chai-as-promised')
7+
const globSource = require('../../src/files/glob-source')
8+
const all = require('async-iterator-all')
9+
const path = require('path')
10+
const isNode = require('is-node')
11+
12+
chai.use(dirtyChai)
13+
chai.use(chaiAsPromised)
14+
const expect = chai.expect
15+
16+
describe('glob-source', () => {
17+
it('single file, relative path', async function () {
18+
if (!isNode) {
19+
return this.skip()
20+
}
21+
22+
const result = await all(globSource(path.relative(process.cwd(), path.join(__dirname, '..', 'fixtures', 'file-0.html'))))
23+
24+
expect(result.length).to.equal(1)
25+
expect(result[0].path).to.equal('file-0.html')
26+
})
27+
28+
it('directory, relative path', async function () {
29+
if (!isNode) {
30+
return this.skip()
31+
}
32+
33+
const result = await all(globSource(path.relative(process.cwd(), path.join(__dirname, '..', 'fixtures', 'dir')), {
34+
recursive: true
35+
}))
36+
37+
expect(result.length).to.equal(3)
38+
expect(result[0].path).to.equal('/dir/file-1.txt')
39+
expect(result[1].path).to.equal('/dir/file-2.js')
40+
expect(result[2].path).to.equal('/dir/file-3.css')
41+
})
42+
43+
it('single file, absolute path', async function () {
44+
if (!isNode) {
45+
return this.skip()
46+
}
47+
48+
const result = await all(globSource(path.resolve(process.cwd(), path.join(__dirname, '..', 'fixtures', 'file-0.html'))))
49+
50+
expect(result.length).to.equal(1)
51+
expect(result[0].path).to.equal('file-0.html')
52+
})
53+
54+
it('directory, relative path', async function () {
55+
if (!isNode) {
56+
return this.skip()
57+
}
58+
59+
const result = await all(globSource(path.resolve(process.cwd(), path.join(__dirname, '..', 'fixtures', 'dir')), {
60+
recursive: true
61+
}))
62+
63+
expect(result.length).to.equal(3)
64+
expect(result[0].path).to.equal('/dir/file-1.txt')
65+
expect(result[1].path).to.equal('/dir/file-2.js')
66+
expect(result[2].path).to.equal('/dir/file-3.css')
67+
})
68+
69+
it('directory, hidden files', async function () {
70+
if (!isNode) {
71+
return this.skip()
72+
}
73+
74+
const result = await all(globSource(path.resolve(process.cwd(), path.join(__dirname, '..', 'fixtures', 'dir')), {
75+
recursive: true,
76+
hidden: true
77+
}))
78+
79+
expect(result.length).to.equal(4)
80+
expect(result[0].path).to.equal('/dir/.hidden.txt')
81+
expect(result[1].path).to.equal('/dir/file-1.txt')
82+
expect(result[2].path).to.equal('/dir/file-2.js')
83+
expect(result[3].path).to.equal('/dir/file-3.css')
84+
})
85+
86+
it('directory, ignore files', async function () {
87+
if (!isNode) {
88+
return this.skip()
89+
}
90+
91+
const result = await all(globSource(path.resolve(process.cwd(), path.join(__dirname, '..', 'fixtures', 'dir')), {
92+
recursive: true,
93+
ignore: ['**/file-1.txt']
94+
}))
95+
96+
expect(result.length).to.equal(2)
97+
expect(result[0].path).to.equal('/dir/file-2.js')
98+
expect(result[1].path).to.equal('/dir/file-3.css')
99+
})
100+
101+
it('require recusive flag for directory', async function () {
102+
if (!isNode) {
103+
return this.skip()
104+
}
105+
106+
await expect(all(globSource(path.resolve(process.cwd(), path.join(__dirname, '..', 'fixtures', 'dir'))))).to.be.rejectedWith(/recursive option not set/)
107+
})
108+
})

test/fixtures/dir/.hidden.txt

Whitespace-only changes.

test/fixtures/dir/file-1.txt

Whitespace-only changes.

test/fixtures/dir/file-2.js

Whitespace-only changes.

test/fixtures/dir/file-3.css

Whitespace-only changes.

test/fixtures/file-0.html

Whitespace-only changes.

0 commit comments

Comments
 (0)