Skip to content

Commit

Permalink
Allow custom fs adapter to be passed as option (isaacs#280)
Browse files Browse the repository at this point in the history
  • Loading branch information
erikkemperman committed Sep 6, 2016
1 parent 98327d8 commit 551f67a
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 9 deletions.
7 changes: 6 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,7 +274,10 @@ 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 given in the `fs` option doesn't implement `realpath`.
* `fs` Provide custom implementations of the filesystem calls `readdir`,
`stat` and optionally `lstat` and/or `realpath`.

## Comparisons to other fnmatch/glob implementations

Expand Down
17 changes: 17 additions & 0 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 Down Expand Up @@ -86,6 +88,21 @@ function setopts (self, pattern, options) {
self.statCache = options.statCache || Object.create(null)
self.symlinks = options.symlinks || Object.create(null)

if (!options.fs) {
self.fs = fs
self.rp = rp.realpath
} else {
self.fs = options.fs
// if fs adapter doesn't implement lstat, defer to stat
if (!self.fs.lstat)
self.fs.lstat = self.fs.stat
// if fs adapter doesn't implement realpath, force the option to false
if (!self.fs.realpath)
self.realpath = false
else
self.rp = self.fs.realpath
}

setupIgnores(self, options)

self.changedCwd = false
Expand Down
13 changes: 5 additions & 8 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 @@ -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(p, self.realpathCache, function (er, real) {
if (!er)
set[real] = true
else if (er.syscall === 'stat')
Expand Down Expand Up @@ -502,7 +500,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)
Expand Down Expand Up @@ -542,8 +540,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 @@ -747,13 +744,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
56 changes: 56 additions & 0 deletions test/fs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
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 being called
var win32 = process.platform === 'win32'
if (win32)
pattern = 'a/bc/**/f'


// [ expected calls ]
var cases = [
// all adapter functions are called for our pattern, excepting stat on win32
{ readdir: true, stat: !win32, lstat: true, realpath: true },

// realpath isn't called if adapter doesn't have it
{ readdir: true, stat: !win32, lstat: true },

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


process.chdir(__dirname + '/fixtures')


cases.forEach(function(exp, i) {
test('custom fs ' + i + ' ' + JSON.stringify(exp), function(t) {
var fns = Object.keys(exp)
var opt = { realpath: true }
var spy = {}

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

glob(pattern, opt, function(err) {
t.ok(!err, 'expect no error')
fns.forEach(function(fn) {
t.ok(spy[fn] === exp[fn], 'expect ' + fn + ' called: ' + exp[fn])
})
t.end()
})
})
})

0 comments on commit 551f67a

Please sign in to comment.