[6a3a178] | 1 | /*
|
---|
| 2 | MIT License http://www.opensource.org/licenses/mit-license.php
|
---|
| 3 | Author Tobias Koppers @sokra
|
---|
| 4 | */
|
---|
| 5 |
|
---|
| 6 | var normalize = require("./normalize");
|
---|
| 7 | var errors = require("errno");
|
---|
| 8 | var stream = require("readable-stream");
|
---|
| 9 |
|
---|
| 10 | var ReadableStream = stream.Readable;
|
---|
| 11 | var WritableStream = stream.Writable;
|
---|
| 12 |
|
---|
| 13 | function MemoryFileSystemError(err, path) {
|
---|
| 14 | Error.call(this)
|
---|
| 15 | if (Error.captureStackTrace)
|
---|
| 16 | Error.captureStackTrace(this, arguments.callee)
|
---|
| 17 | this.code = err.code;
|
---|
| 18 | this.errno = err.errno;
|
---|
| 19 | this.message = err.description;
|
---|
| 20 | this.path = path;
|
---|
| 21 | }
|
---|
| 22 | MemoryFileSystemError.prototype = new Error();
|
---|
| 23 |
|
---|
| 24 | function MemoryFileSystem(data) {
|
---|
| 25 | this.data = data || {};
|
---|
| 26 | }
|
---|
| 27 | module.exports = MemoryFileSystem;
|
---|
| 28 |
|
---|
| 29 | function isDir(item) {
|
---|
| 30 | if(typeof item !== "object") return false;
|
---|
| 31 | return item[""] === true;
|
---|
| 32 | }
|
---|
| 33 |
|
---|
| 34 | function isFile(item) {
|
---|
| 35 | if(typeof item !== "object") return false;
|
---|
| 36 | return !item[""];
|
---|
| 37 | }
|
---|
| 38 |
|
---|
| 39 | function pathToArray(path) {
|
---|
| 40 | path = normalize(path);
|
---|
| 41 | var nix = /^\//.test(path);
|
---|
| 42 | if(!nix) {
|
---|
| 43 | if(!/^[A-Za-z]:/.test(path)) {
|
---|
| 44 | throw new MemoryFileSystemError(errors.code.EINVAL, path);
|
---|
| 45 | }
|
---|
| 46 | path = path.replace(/[\\\/]+/g, "\\"); // multi slashs
|
---|
| 47 | path = path.split(/[\\\/]/);
|
---|
| 48 | path[0] = path[0].toUpperCase();
|
---|
| 49 | } else {
|
---|
| 50 | path = path.replace(/\/+/g, "/"); // multi slashs
|
---|
| 51 | path = path.substr(1).split("/");
|
---|
| 52 | }
|
---|
| 53 | if(!path[path.length-1]) path.pop();
|
---|
| 54 | return path;
|
---|
| 55 | }
|
---|
| 56 |
|
---|
| 57 | function trueFn() { return true; }
|
---|
| 58 | function falseFn() { return false; }
|
---|
| 59 |
|
---|
| 60 | MemoryFileSystem.prototype.meta = function(_path) {
|
---|
| 61 | var path = pathToArray(_path);
|
---|
| 62 | var current = this.data;
|
---|
| 63 | for(var i = 0; i < path.length - 1; i++) {
|
---|
| 64 | if(!isDir(current[path[i]]))
|
---|
| 65 | return;
|
---|
| 66 | current = current[path[i]];
|
---|
| 67 | }
|
---|
| 68 | return current[path[i]];
|
---|
| 69 | }
|
---|
| 70 |
|
---|
| 71 | MemoryFileSystem.prototype.existsSync = function(_path) {
|
---|
| 72 | return !!this.meta(_path);
|
---|
| 73 | }
|
---|
| 74 |
|
---|
| 75 | MemoryFileSystem.prototype.statSync = function(_path) {
|
---|
| 76 | var current = this.meta(_path);
|
---|
| 77 | if(_path === "/" || isDir(current)) {
|
---|
| 78 | return {
|
---|
| 79 | isFile: falseFn,
|
---|
| 80 | isDirectory: trueFn,
|
---|
| 81 | isBlockDevice: falseFn,
|
---|
| 82 | isCharacterDevice: falseFn,
|
---|
| 83 | isSymbolicLink: falseFn,
|
---|
| 84 | isFIFO: falseFn,
|
---|
| 85 | isSocket: falseFn
|
---|
| 86 | };
|
---|
| 87 | } else if(isFile(current)) {
|
---|
| 88 | return {
|
---|
| 89 | isFile: trueFn,
|
---|
| 90 | isDirectory: falseFn,
|
---|
| 91 | isBlockDevice: falseFn,
|
---|
| 92 | isCharacterDevice: falseFn,
|
---|
| 93 | isSymbolicLink: falseFn,
|
---|
| 94 | isFIFO: falseFn,
|
---|
| 95 | isSocket: falseFn
|
---|
| 96 | };
|
---|
| 97 | } else {
|
---|
| 98 | throw new MemoryFileSystemError(errors.code.ENOENT, _path);
|
---|
| 99 | }
|
---|
| 100 | };
|
---|
| 101 |
|
---|
| 102 | MemoryFileSystem.prototype.readFileSync = function(_path, encoding) {
|
---|
| 103 | var path = pathToArray(_path);
|
---|
| 104 | var current = this.data;
|
---|
| 105 | for(var i = 0; i < path.length - 1; i++) {
|
---|
| 106 | if(!isDir(current[path[i]]))
|
---|
| 107 | throw new MemoryFileSystemError(errors.code.ENOENT, _path);
|
---|
| 108 | current = current[path[i]];
|
---|
| 109 | }
|
---|
| 110 | if(!isFile(current[path[i]])) {
|
---|
| 111 | if(isDir(current[path[i]]))
|
---|
| 112 | throw new MemoryFileSystemError(errors.code.EISDIR, _path);
|
---|
| 113 | else
|
---|
| 114 | throw new MemoryFileSystemError(errors.code.ENOENT, _path);
|
---|
| 115 | }
|
---|
| 116 | current = current[path[i]];
|
---|
| 117 | return encoding ? current.toString(encoding) : current;
|
---|
| 118 | };
|
---|
| 119 |
|
---|
| 120 | MemoryFileSystem.prototype.readdirSync = function(_path) {
|
---|
| 121 | if(_path === "/") return Object.keys(this.data).filter(Boolean);
|
---|
| 122 | var path = pathToArray(_path);
|
---|
| 123 | var current = this.data;
|
---|
| 124 | for(var i = 0; i < path.length - 1; i++) {
|
---|
| 125 | if(!isDir(current[path[i]]))
|
---|
| 126 | throw new MemoryFileSystemError(errors.code.ENOENT, _path);
|
---|
| 127 | current = current[path[i]];
|
---|
| 128 | }
|
---|
| 129 | if(!isDir(current[path[i]])) {
|
---|
| 130 | if(isFile(current[path[i]]))
|
---|
| 131 | throw new MemoryFileSystemError(errors.code.ENOTDIR, _path);
|
---|
| 132 | else
|
---|
| 133 | throw new MemoryFileSystemError(errors.code.ENOENT, _path);
|
---|
| 134 | }
|
---|
| 135 | return Object.keys(current[path[i]]).filter(Boolean);
|
---|
| 136 | };
|
---|
| 137 |
|
---|
| 138 | MemoryFileSystem.prototype.mkdirpSync = function(_path) {
|
---|
| 139 | var path = pathToArray(_path);
|
---|
| 140 | if(path.length === 0) return;
|
---|
| 141 | var current = this.data;
|
---|
| 142 | for(var i = 0; i < path.length; i++) {
|
---|
| 143 | if(isFile(current[path[i]]))
|
---|
| 144 | throw new MemoryFileSystemError(errors.code.ENOTDIR, _path);
|
---|
| 145 | else if(!isDir(current[path[i]]))
|
---|
| 146 | current[path[i]] = {"":true};
|
---|
| 147 | current = current[path[i]];
|
---|
| 148 | }
|
---|
| 149 | return;
|
---|
| 150 | };
|
---|
| 151 |
|
---|
| 152 | MemoryFileSystem.prototype.mkdirSync = function(_path) {
|
---|
| 153 | var path = pathToArray(_path);
|
---|
| 154 | if(path.length === 0) return;
|
---|
| 155 | var current = this.data;
|
---|
| 156 | for(var i = 0; i < path.length - 1; i++) {
|
---|
| 157 | if(!isDir(current[path[i]]))
|
---|
| 158 | throw new MemoryFileSystemError(errors.code.ENOENT, _path);
|
---|
| 159 | current = current[path[i]];
|
---|
| 160 | }
|
---|
| 161 | if(isDir(current[path[i]]))
|
---|
| 162 | throw new MemoryFileSystemError(errors.code.EEXIST, _path);
|
---|
| 163 | else if(isFile(current[path[i]]))
|
---|
| 164 | throw new MemoryFileSystemError(errors.code.ENOTDIR, _path);
|
---|
| 165 | current[path[i]] = {"":true};
|
---|
| 166 | return;
|
---|
| 167 | };
|
---|
| 168 |
|
---|
| 169 | MemoryFileSystem.prototype._remove = function(_path, name, testFn) {
|
---|
| 170 | var path = pathToArray(_path);
|
---|
| 171 | if(path.length === 0) {
|
---|
| 172 | throw new MemoryFileSystemError(errors.code.EPERM, _path);
|
---|
| 173 | }
|
---|
| 174 | var current = this.data;
|
---|
| 175 | for(var i = 0; i < path.length - 1; i++) {
|
---|
| 176 | if(!isDir(current[path[i]]))
|
---|
| 177 | throw new MemoryFileSystemError(errors.code.ENOENT, _path);
|
---|
| 178 | current = current[path[i]];
|
---|
| 179 | }
|
---|
| 180 | if(!testFn(current[path[i]]))
|
---|
| 181 | throw new MemoryFileSystemError(errors.code.ENOENT, _path);
|
---|
| 182 | delete current[path[i]];
|
---|
| 183 | return;
|
---|
| 184 | };
|
---|
| 185 |
|
---|
| 186 | MemoryFileSystem.prototype.rmdirSync = function(_path) {
|
---|
| 187 | return this._remove(_path, "Directory", isDir);
|
---|
| 188 | };
|
---|
| 189 |
|
---|
| 190 | MemoryFileSystem.prototype.unlinkSync = function(_path) {
|
---|
| 191 | return this._remove(_path, "File", isFile);
|
---|
| 192 | };
|
---|
| 193 |
|
---|
| 194 | MemoryFileSystem.prototype.readlinkSync = function(_path) {
|
---|
| 195 | throw new MemoryFileSystemError(errors.code.ENOSYS, _path);
|
---|
| 196 | };
|
---|
| 197 |
|
---|
| 198 | MemoryFileSystem.prototype.writeFileSync = function(_path, content, encoding) {
|
---|
| 199 | if(!content && !encoding) throw new Error("No content");
|
---|
| 200 | var path = pathToArray(_path);
|
---|
| 201 | if(path.length === 0) {
|
---|
| 202 | throw new MemoryFileSystemError(errors.code.EISDIR, _path);
|
---|
| 203 | }
|
---|
| 204 | var current = this.data;
|
---|
| 205 | for(var i = 0; i < path.length - 1; i++) {
|
---|
| 206 | if(!isDir(current[path[i]]))
|
---|
| 207 | throw new MemoryFileSystemError(errors.code.ENOENT, _path);
|
---|
| 208 | current = current[path[i]];
|
---|
| 209 | }
|
---|
| 210 | if(isDir(current[path[i]]))
|
---|
| 211 | throw new MemoryFileSystemError(errors.code.EISDIR, _path);
|
---|
| 212 | current[path[i]] = encoding || typeof content === "string" ? new Buffer(content, encoding) : content;
|
---|
| 213 | return;
|
---|
| 214 | };
|
---|
| 215 |
|
---|
| 216 | MemoryFileSystem.prototype.join = require("./join");
|
---|
| 217 | MemoryFileSystem.prototype.pathToArray = pathToArray;
|
---|
| 218 | MemoryFileSystem.prototype.normalize = normalize;
|
---|
| 219 |
|
---|
| 220 | // stream functions
|
---|
| 221 |
|
---|
| 222 | MemoryFileSystem.prototype.createReadStream = function(path, options) {
|
---|
| 223 | var stream = new ReadableStream();
|
---|
| 224 | var done = false;
|
---|
| 225 | var data;
|
---|
| 226 | try {
|
---|
| 227 | data = this.readFileSync(path);
|
---|
| 228 | } catch (e) {
|
---|
| 229 | stream._read = function() {
|
---|
| 230 | if (done) {
|
---|
| 231 | return;
|
---|
| 232 | }
|
---|
| 233 | done = true;
|
---|
| 234 | this.emit('error', e);
|
---|
| 235 | this.push(null);
|
---|
| 236 | };
|
---|
| 237 | return stream;
|
---|
| 238 | }
|
---|
| 239 | options = options || { };
|
---|
| 240 | options.start = options.start || 0;
|
---|
| 241 | options.end = options.end || data.length;
|
---|
| 242 | stream._read = function() {
|
---|
| 243 | if (done) {
|
---|
| 244 | return;
|
---|
| 245 | }
|
---|
| 246 | done = true;
|
---|
| 247 | this.push(data.slice(options.start, options.end));
|
---|
| 248 | this.push(null);
|
---|
| 249 | };
|
---|
| 250 | return stream;
|
---|
| 251 | };
|
---|
| 252 |
|
---|
| 253 | MemoryFileSystem.prototype.createWriteStream = function(path, options) {
|
---|
| 254 | var stream = new WritableStream(), self = this;
|
---|
| 255 | try {
|
---|
| 256 | // Zero the file and make sure it is writable
|
---|
| 257 | this.writeFileSync(path, new Buffer(0));
|
---|
| 258 | } catch(e) {
|
---|
| 259 | // This or setImmediate?
|
---|
| 260 | stream.once('prefinish', function() {
|
---|
| 261 | stream.emit('error', e);
|
---|
| 262 | });
|
---|
| 263 | return stream;
|
---|
| 264 | }
|
---|
| 265 | var bl = [ ], len = 0;
|
---|
| 266 | stream._write = function(chunk, encoding, callback) {
|
---|
| 267 | bl.push(chunk);
|
---|
| 268 | len += chunk.length;
|
---|
| 269 | self.writeFile(path, Buffer.concat(bl, len), callback);
|
---|
| 270 | }
|
---|
| 271 | return stream;
|
---|
| 272 | };
|
---|
| 273 |
|
---|
| 274 | // async functions
|
---|
| 275 |
|
---|
| 276 | ["stat", "readdir", "mkdirp", "rmdir", "unlink", "readlink"].forEach(function(fn) {
|
---|
| 277 | MemoryFileSystem.prototype[fn] = function(path, callback) {
|
---|
| 278 | try {
|
---|
| 279 | var result = this[fn + "Sync"](path);
|
---|
| 280 | } catch(e) {
|
---|
| 281 | setImmediate(function() {
|
---|
| 282 | callback(e);
|
---|
| 283 | });
|
---|
| 284 |
|
---|
| 285 | return;
|
---|
| 286 | }
|
---|
| 287 | setImmediate(function() {
|
---|
| 288 | callback(null, result);
|
---|
| 289 | });
|
---|
| 290 | };
|
---|
| 291 | });
|
---|
| 292 |
|
---|
| 293 | ["mkdir", "readFile"].forEach(function(fn) {
|
---|
| 294 | MemoryFileSystem.prototype[fn] = function(path, optArg, callback) {
|
---|
| 295 | if(!callback) {
|
---|
| 296 | callback = optArg;
|
---|
| 297 | optArg = undefined;
|
---|
| 298 | }
|
---|
| 299 | try {
|
---|
| 300 | var result = this[fn + "Sync"](path, optArg);
|
---|
| 301 | } catch(e) {
|
---|
| 302 | setImmediate(function() {
|
---|
| 303 | callback(e);
|
---|
| 304 | });
|
---|
| 305 |
|
---|
| 306 | return;
|
---|
| 307 | }
|
---|
| 308 | setImmediate(function() {
|
---|
| 309 | callback(null, result);
|
---|
| 310 | });
|
---|
| 311 | };
|
---|
| 312 | });
|
---|
| 313 |
|
---|
| 314 | MemoryFileSystem.prototype.exists = function(path, callback) {
|
---|
| 315 | return callback(this.existsSync(path));
|
---|
| 316 | }
|
---|
| 317 |
|
---|
| 318 | MemoryFileSystem.prototype.writeFile = function (path, content, encoding, callback) {
|
---|
| 319 | if(!callback) {
|
---|
| 320 | callback = encoding;
|
---|
| 321 | encoding = undefined;
|
---|
| 322 | }
|
---|
| 323 | try {
|
---|
| 324 | this.writeFileSync(path, content, encoding);
|
---|
| 325 | } catch(e) {
|
---|
| 326 | return callback(e);
|
---|
| 327 | }
|
---|
| 328 | return callback();
|
---|
| 329 | };
|
---|