/* | |
MIT License http://www.opensource.org/licenses/mit-license.php | |
Author Tobias Koppers @sokra | |
*/ | |
var normalize = require("./normalize"); | |
var errors = require("errno"); | |
var stream = require("readable-stream"); | |
var ReadableStream = stream.Readable; | |
var WritableStream = stream.Writable; | |
function MemoryFileSystemError(err, path) { | |
Error.call(this) | |
if (Error.captureStackTrace) | |
Error.captureStackTrace(this, arguments.callee) | |
this.code = err.code; | |
this.errno = err.errno; | |
this.message = err.description; | |
this.path = path; | |
} | |
MemoryFileSystemError.prototype = new Error(); | |
function MemoryFileSystem(data) { | |
this.data = data || {}; | |
} | |
module.exports = MemoryFileSystem; | |
function isDir(item) { | |
if(typeof item !== "object") return false; | |
return item[""] === true; | |
} | |
function isFile(item) { | |
if(typeof item !== "object") return false; | |
return !item[""]; | |
} | |
function pathToArray(path) { | |
path = normalize(path); | |
var nix = /^\//.test(path); | |
if(!nix) { | |
if(!/^[A-Za-z]:/.test(path)) { | |
throw new MemoryFileSystemError(errors.code.EINVAL, path); | |
} | |
path = path.replace(/[\\\/]+/g, "\\"); // multi slashs | |
path = path.split(/[\\\/]/); | |
path[0] = path[0].toUpperCase(); | |
} else { | |
path = path.replace(/\/+/g, "/"); // multi slashs | |
path = path.substr(1).split("/"); | |
} | |
if(!path[path.length-1]) path.pop(); | |
return path; | |
} | |
function trueFn() { return true; } | |
function falseFn() { return false; } | |
MemoryFileSystem.prototype.meta = function(_path) { | |
var path = pathToArray(_path); | |
var current = this.data; | |
for(var i = 0; i < path.length - 1; i++) { | |
if(!isDir(current[path[i]])) | |
return; | |
current = current[path[i]]; | |
} | |
return current[path[i]]; | |
} | |
MemoryFileSystem.prototype.existsSync = function(_path) { | |
return !!this.meta(_path); | |
} | |
MemoryFileSystem.prototype.statSync = function(_path) { | |
var current = this.meta(_path); | |
if(_path === "/" || isDir(current)) { | |
return { | |
isFile: falseFn, | |
isDirectory: trueFn, | |
isBlockDevice: falseFn, | |
isCharacterDevice: falseFn, | |
isSymbolicLink: falseFn, | |
isFIFO: falseFn, | |
isSocket: falseFn | |
}; | |
} else if(isFile(current)) { | |
return { | |
isFile: trueFn, | |
isDirectory: falseFn, | |
isBlockDevice: falseFn, | |
isCharacterDevice: falseFn, | |
isSymbolicLink: falseFn, | |
isFIFO: falseFn, | |
isSocket: falseFn | |
}; | |
} else { | |
throw new MemoryFileSystemError(errors.code.ENOENT, _path); | |
} | |
}; | |
MemoryFileSystem.prototype.readFileSync = function(_path, encoding) { | |
var path = pathToArray(_path); | |
var current = this.data; | |
for(var i = 0; i < path.length - 1; i++) { | |
if(!isDir(current[path[i]])) | |
throw new MemoryFileSystemError(errors.code.ENOENT, _path); | |
current = current[path[i]]; | |
} | |
if(!isFile(current[path[i]])) { | |
if(isDir(current[path[i]])) | |
throw new MemoryFileSystemError(errors.code.EISDIR, _path); | |
else | |
throw new MemoryFileSystemError(errors.code.ENOENT, _path); | |
} | |
current = current[path[i]]; | |
return encoding ? current.toString(encoding) : current; | |
}; | |
MemoryFileSystem.prototype.readdirSync = function(_path) { | |
if(_path === "/") return Object.keys(this.data).filter(Boolean); | |
var path = pathToArray(_path); | |
var current = this.data; | |
for(var i = 0; i < path.length - 1; i++) { | |
if(!isDir(current[path[i]])) | |
throw new MemoryFileSystemError(errors.code.ENOENT, _path); | |
current = current[path[i]]; | |
} | |
if(!isDir(current[path[i]])) { | |
if(isFile(current[path[i]])) | |
throw new MemoryFileSystemError(errors.code.ENOTDIR, _path); | |
else | |
throw new MemoryFileSystemError(errors.code.ENOENT, _path); | |
} | |
return Object.keys(current[path[i]]).filter(Boolean); | |
}; | |
MemoryFileSystem.prototype.mkdirpSync = function(_path) { | |
var path = pathToArray(_path); | |
if(path.length === 0) return; | |
var current = this.data; | |
for(var i = 0; i < path.length; i++) { | |
if(isFile(current[path[i]])) | |
throw new MemoryFileSystemError(errors.code.ENOTDIR, _path); | |
else if(!isDir(current[path[i]])) | |
current[path[i]] = {"":true}; | |
current = current[path[i]]; | |
} | |
return; | |
}; | |
MemoryFileSystem.prototype.mkdirSync = function(_path) { | |
var path = pathToArray(_path); | |
if(path.length === 0) return; | |
var current = this.data; | |
for(var i = 0; i < path.length - 1; i++) { | |
if(!isDir(current[path[i]])) | |
throw new MemoryFileSystemError(errors.code.ENOENT, _path); | |
current = current[path[i]]; | |
} | |
if(isDir(current[path[i]])) | |
throw new MemoryFileSystemError(errors.code.EEXIST, _path); | |
else if(isFile(current[path[i]])) | |
throw new MemoryFileSystemError(errors.code.ENOTDIR, _path); | |
current[path[i]] = {"":true}; | |
return; | |
}; | |
MemoryFileSystem.prototype._remove = function(_path, name, testFn) { | |
var path = pathToArray(_path); | |
if(path.length === 0) { | |
throw new MemoryFileSystemError(errors.code.EPERM, _path); | |
} | |
var current = this.data; | |
for(var i = 0; i < path.length - 1; i++) { | |
if(!isDir(current[path[i]])) | |
throw new MemoryFileSystemError(errors.code.ENOENT, _path); | |
current = current[path[i]]; | |
} | |
if(!testFn(current[path[i]])) | |
throw new MemoryFileSystemError(errors.code.ENOENT, _path); | |
delete current[path[i]]; | |
return; | |
}; | |
MemoryFileSystem.prototype.rmdirSync = function(_path) { | |
return this._remove(_path, "Directory", isDir); | |
}; | |
MemoryFileSystem.prototype.unlinkSync = function(_path) { | |
return this._remove(_path, "File", isFile); | |
}; | |
MemoryFileSystem.prototype.readlinkSync = function(_path) { | |
throw new MemoryFileSystemError(errors.code.ENOSYS, _path); | |
}; | |
MemoryFileSystem.prototype.writeFileSync = function(_path, content, encoding) { | |
if(!content && !encoding) throw new Error("No content"); | |
var path = pathToArray(_path); | |
if(path.length === 0) { | |
throw new MemoryFileSystemError(errors.code.EISDIR, _path); | |
} | |
var current = this.data; | |
for(var i = 0; i < path.length - 1; i++) { | |
if(!isDir(current[path[i]])) | |
throw new MemoryFileSystemError(errors.code.ENOENT, _path); | |
current = current[path[i]]; | |
} | |
if(isDir(current[path[i]])) | |
throw new MemoryFileSystemError(errors.code.EISDIR, _path); | |
current[path[i]] = encoding || typeof content === "string" ? new Buffer(content, encoding) : content; | |
return; | |
}; | |
MemoryFileSystem.prototype.join = require("./join"); | |
MemoryFileSystem.prototype.pathToArray = pathToArray; | |
MemoryFileSystem.prototype.normalize = normalize; | |
// stream functions | |
MemoryFileSystem.prototype.createReadStream = function(path, options) { | |
var stream = new ReadableStream(); | |
var done = false; | |
var data; | |
try { | |
data = this.readFileSync(path); | |
} catch (e) { | |
stream._read = function() { | |
if (done) { | |
return; | |
} | |
done = true; | |
this.emit('error', e); | |
this.push(null); | |
}; | |
return stream; | |
} | |
options = options || { }; | |
options.start = options.start || 0; | |
options.end = options.end || data.length; | |
stream._read = function() { | |
if (done) { | |
return; | |
} | |
done = true; | |
this.push(data.slice(options.start, options.end)); | |
this.push(null); | |
}; | |
return stream; | |
}; | |
MemoryFileSystem.prototype.createWriteStream = function(path, options) { | |
var stream = new WritableStream(), self = this; | |
try { | |
// Zero the file and make sure it is writable | |
this.writeFileSync(path, new Buffer(0)); | |
} catch(e) { | |
// This or setImmediate? | |
stream.once('prefinish', function() { | |
stream.emit('error', e); | |
}); | |
return stream; | |
} | |
var bl = [ ], len = 0; | |
stream._write = function(chunk, encoding, callback) { | |
bl.push(chunk); | |
len += chunk.length; | |
self.writeFile(path, Buffer.concat(bl, len), callback); | |
} | |
return stream; | |
}; | |
// async functions | |
["stat", "readdir", "mkdirp", "rmdir", "unlink", "readlink"].forEach(function(fn) { | |
MemoryFileSystem.prototype[fn] = function(path, callback) { | |
try { | |
var result = this[fn + "Sync"](path); | |
} catch(e) { | |
setImmediate(function() { | |
callback(e); | |
}); | |
return; | |
} | |
setImmediate(function() { | |
callback(null, result); | |
}); | |
}; | |
}); | |
["mkdir", "readFile"].forEach(function(fn) { | |
MemoryFileSystem.prototype[fn] = function(path, optArg, callback) { | |
if(!callback) { | |
callback = optArg; | |
optArg = undefined; | |
} | |
try { | |
var result = this[fn + "Sync"](path, optArg); | |
} catch(e) { | |
setImmediate(function() { | |
callback(e); | |
}); | |
return; | |
} | |
setImmediate(function() { | |
callback(null, result); | |
}); | |
}; | |
}); | |
MemoryFileSystem.prototype.exists = function(path, callback) { | |
return callback(this.existsSync(path)); | |
} | |
MemoryFileSystem.prototype.writeFile = function (path, content, encoding, callback) { | |
if(!callback) { | |
callback = encoding; | |
encoding = undefined; | |
} | |
try { | |
this.writeFileSync(path, content, encoding); | |
} catch(e) { | |
return callback(e); | |
} | |
return callback(); | |
}; |