MemoryFileSystem.js 8.3 KB

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