Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow custom fs adapter to be passed as option (#280) #285

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,8 @@ be immediately available on the `g.found` member.
* `realpathCache` An optional object which is passed to `fs.realpath`
to minimize unnecessary syscalls. It is stored on the instantiated
Glob object, and may be re-used.
* `fs` By default `require('fs')` is used, but a custom filesystem
adapter may be provided as an option.

### Events

Expand Down Expand Up @@ -272,10 +274,14 @@ the filesystem.
* `realpath` Set to true to call `fs.realpath` on all of the results.
In the case of a symlink that cannot be resolved, the full absolute
path to the matched entry is returned (though it will usually be a
broken symlink)
broken symlink). This option is forced to be `false` when a custom
filesystem adapter is provided in the `fs` option.
* `absolute` Set to true to always receive absolute paths for matched
files. Unlike `realpath`, this also affects the values returned in
the `match` event.
* `fs` Provide custom implementations of the filesystem calls `readdir`,
`stat` and optionally `lstat`. With `glob.sync()`, their synchronous
counterparts are expected.

## Comparisons to other fnmatch/glob implementations

Expand Down
28 changes: 26 additions & 2 deletions common.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ function ownProp (obj, field) {
return Object.prototype.hasOwnProperty.call(obj, field)
}

var fs = require("fs")
var rp = require("fs.realpath")
var path = require("path")
var minimatch = require("minimatch")
var isAbsolute = require("path-is-absolute")
Expand All @@ -25,6 +27,26 @@ function alphasort (a, b) {
return a.localeCompare(b)
}

function setupFs (self, options) {
if (!options.fs) {
self.fs = fs
self.rp = rp
} else {
// if custom filesystem adapter is provided, force realpath option to false
self.realpath = false
// wire up the adapter functions, deferring to stat when lstat is absent
self.fs = self.sync ? {
readdirSync: options.fs.readdirSync,
statSync: options.fs.statSync,
lstatSync: options.fs.lstatSync || options.fs.statSync
} : {
readdir: options.fs.readdir,
stat: options.fs.stat,
lstat: options.fs.lstat || options.fs.stat
}
}
}

function setupIgnores (self, options) {
self.ignore = options.ignore || []

Expand All @@ -50,7 +72,7 @@ function ignoreMap (pattern) {
}
}

function setopts (self, pattern, options) {
function setopts (self, pattern, options, sync) {
if (!options)
options = {}

Expand All @@ -73,7 +95,7 @@ function setopts (self, pattern, options) {
self.nodir = !!options.nodir
if (self.nodir)
self.mark = true
self.sync = !!options.sync
self.sync = sync
self.nounique = !!options.nounique
self.nonull = !!options.nonull
self.nosort = !!options.nosort
Expand All @@ -87,6 +109,8 @@ function setopts (self, pattern, options) {
self.statCache = options.statCache || Object.create(null)
self.symlinks = options.symlinks || Object.create(null)

setupFs(self, options)

setupIgnores(self, options)

self.changedCwd = false
Expand Down
15 changes: 6 additions & 9 deletions glob.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,6 @@

module.exports = glob

var fs = require('fs')
var rp = require('fs.realpath')
var minimatch = require('minimatch')
var Minimatch = minimatch.Minimatch
var inherits = require('inherits')
Expand Down Expand Up @@ -132,7 +130,7 @@ function Glob (pattern, options, cb) {
if (!(this instanceof Glob))
return new Glob(pattern, options, cb)

setopts(this, pattern, options)
setopts(this, pattern, options, false)
this._didRealPath = false

// process each pattern in the minimatch set
Expand Down Expand Up @@ -237,7 +235,7 @@ Glob.prototype._realpathSet = function (index, cb) {
// one or more of the links in the realpath couldn't be
// resolved. just return the abs value in that case.
p = self._makeAbs(p)
rp.realpath(p, self.realpathCache, function (er, real) {
self.rp.realpath(p, self.realpathCache, function (er, real) {
if (!er)
set[real] = true
else if (er.syscall === 'stat')
Expand Down Expand Up @@ -505,7 +503,7 @@ Glob.prototype._readdirInGlobStar = function (abs, cb) {
var lstatcb = inflight(lstatkey, lstatcb_)

if (lstatcb)
fs.lstat(abs, lstatcb)
self.fs.lstat(abs, lstatcb)

function lstatcb_ (er, lstat) {
if (er && er.code === 'ENOENT')
Expand Down Expand Up @@ -545,8 +543,7 @@ Glob.prototype._readdir = function (abs, inGlobStar, cb) {
return cb(null, c)
}

var self = this
fs.readdir(abs, readdirCb(this, abs, cb))
this.fs.readdir(abs, readdirCb(this, abs, cb))
}

function readdirCb (self, abs, cb) {
Expand Down Expand Up @@ -750,13 +747,13 @@ Glob.prototype._stat = function (f, cb) {
var self = this
var statcb = inflight('stat\0' + abs, lstatcb_)
if (statcb)
fs.lstat(abs, statcb)
self.fs.lstat(abs, statcb)

function lstatcb_ (er, lstat) {
if (lstat && lstat.isSymbolicLink()) {
// If it's a symlink, then treat it as the target, unless
// the target does not exist, then treat it as a file.
return fs.stat(abs, function (er, stat) {
return self.fs.stat(abs, function (er, stat) {
if (er)
self._stat2(f, abs, null, lstat, cb)
else
Expand Down
14 changes: 6 additions & 8 deletions sync.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
module.exports = globSync
globSync.GlobSync = GlobSync

var fs = require('fs')
var rp = require('fs.realpath')
var minimatch = require('minimatch')
var Minimatch = minimatch.Minimatch
var Glob = require('./glob.js').Glob
Expand Down Expand Up @@ -37,7 +35,7 @@ function GlobSync (pattern, options) {
if (!(this instanceof GlobSync))
return new GlobSync(pattern, options)

setopts(this, pattern, options)
setopts(this, pattern, options, true)

if (this.noprocess)
return this
Expand All @@ -59,7 +57,7 @@ GlobSync.prototype._finish = function () {
for (var p in matchset) {
try {
p = self._makeAbs(p)
var real = rp.realpathSync(p, self.realpathCache)
var real = self.rp.realpathSync(p, self.realpathCache)
set[real] = true
} catch (er) {
if (er.syscall === 'stat')
Expand Down Expand Up @@ -248,7 +246,7 @@ GlobSync.prototype._readdirInGlobStar = function (abs) {
var lstat
var stat
try {
lstat = fs.lstatSync(abs)
lstat = this.fs.lstatSync(abs)
} catch (er) {
if (er.code === 'ENOENT') {
// lstat failed, doesn't exist
Expand Down Expand Up @@ -285,7 +283,7 @@ GlobSync.prototype._readdir = function (abs, inGlobStar) {
}

try {
return this._readdirEntries(abs, fs.readdirSync(abs))
return this._readdirEntries(abs, this.fs.readdirSync(abs))
} catch (er) {
this._readdirError(abs, er)
return null
Expand Down Expand Up @@ -444,7 +442,7 @@ GlobSync.prototype._stat = function (f) {
if (!stat) {
var lstat
try {
lstat = fs.lstatSync(abs)
lstat = this.fs.lstatSync(abs)
} catch (er) {
if (er && (er.code === 'ENOENT' || er.code === 'ENOTDIR')) {
this.statCache[abs] = false
Expand All @@ -454,7 +452,7 @@ GlobSync.prototype._stat = function (f) {

if (lstat && lstat.isSymbolicLink()) {
try {
stat = fs.statSync(abs)
stat = this.fs.statSync(abs)
} catch (er) {
stat = lstat
}
Expand Down
76 changes: 76 additions & 0 deletions test/fs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
require("./global-leakage.js")

var fs = require('fs')
var test = require('tap').test
var glob = require('../')

// pattern to (potentially) trigger all fs calls
var pattern = 'a/symlink/**/c'

// on win32, the fixtures will not include symlink, so use a different pattern
// and adjust expectations of stat / statSync being called
var win32 = process.platform === 'win32'
if (win32)
pattern = 'a/bc/**/f'


var asyncCases = [
// all adapter functions are called for our pattern, except stat on win32
{ readdir: true, stat: !win32, lstat: true },

// stat is called instead of lstat if adapter doesn't implement it
{ readdir: true, stat: true }
]

var syncCases = [
// all adapter functions are called for our pattern, except statSync on win32
{ readdirSync: true, statSync: !win32, lstatSync: true },

// statSync is called instead of lstatSync if adapter doesn't implement it
{ readdirSync: true, statSync: true }
]


process.chdir(__dirname + '/fixtures')


asyncCases.forEach(function(exp) {
test('fs adapter ' + JSON.stringify(exp), function(t) {
var fns = Object.keys(exp)
var opt = { fs: {} }
var spy = _spy(fns, opt)
glob(pattern, opt, function() {
fns.forEach(function(fn) {
t.ok(spy[fn] === exp[fn], 'expect ' + fn + ' called: ' + exp[fn])
})
t.end()
})
})
})


syncCases.forEach(function(exp) {
test('fs adapter ' + JSON.stringify(exp), function(t) {
var fns = Object.keys(exp)
var opt = { fs: {} }
var spy = _spy(fns, opt)
glob.sync(pattern, opt)
fns.forEach(function(fn) {
t.ok(spy[fn] === exp[fn], 'expect ' + fn + ' called: ' + exp[fn])
})
t.end()
})
})


function _spy(fns, opt) {
var spy = {}
fns.forEach(function(fn) {
spy[fn] = false
opt.fs[fn] = function() {
spy[fn] = true
return fs[fn].apply(null, arguments)
}
})
return spy
}