Skip to content

Commit 0a95ef8

Browse files
achingbrainhugomrdias
authored andcommitted
feat: add glob-source from js-ipfs to be shared (#9)
* 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. * chore: use env file instead of dep * test: add test for multiple paths * feat: make paths (async)iterable or string
1 parent b22b8de commit 0a95ef8

File tree

8 files changed

+225
-1
lines changed

8 files changed

+225
-1
lines changed

package.json

+7-1
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,12 @@
2828
"dependencies": {
2929
"buffer": "^5.2.1",
3030
"err-code": "^2.0.0",
31+
"fs-extra": "^8.1.0",
3132
"is-buffer": "^2.0.3",
3233
"is-electron": "^2.2.0",
3334
"is-pull-stream": "0.0.0",
3435
"is-stream": "^2.0.0",
36+
"it-glob": "0.0.4",
3537
"kind-of": "^6.0.2",
3638
"pull-stream-to-async-iterator": "^1.0.2",
3739
"readable-stream": "^3.4.0"
@@ -40,12 +42,16 @@
4042
"aegir": "^20.0.0",
4143
"async-iterator-all": "^1.0.0",
4244
"chai": "^4.2.0",
45+
"chai-as-promised": "^7.1.1",
4346
"dirty-chai": "^2.0.1",
4447
"electron": "^6.0.6",
4548
"electron-mocha": "^8.0.3",
4649
"pull-stream": "^3.6.13"
4750
},
4851
"contributors": [
4952
"Hugo Dias <[email protected]>"
50-
]
53+
],
54+
"browser": {
55+
"fs-extra": false
56+
}
5157
}

src/files/glob-source.js

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

test/files/glob-source.spec.js

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

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)