UNPKG

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