UNPKG

6.79 kBJavaScriptView Raw
1'use strict';
2
3var os = require('os');
4var path = require('path');
5
6var Directory = require('./directory');
7var File = require('./file');
8var FSError = require('./error');
9var SymbolicLink = require('./symlink');
10
11
12var isWindows = process.platform === 'win32';
13
14function getPathParts(filepath) {
15 var parts = path._makeLong(path.resolve(filepath)).split(path.sep);
16 parts.shift();
17 if (isWindows) {
18 // parts currently looks like ['', '?', 'c:', ...]
19 parts.shift();
20 var q = parts.shift(); // should be '?'
21 var base = '\\\\' + q + '\\' + parts.shift().toLowerCase();
22 parts.unshift(base);
23 }
24 if (parts[parts.length - 1] === '') {
25 parts.pop();
26 }
27 return parts;
28}
29
30
31
32/**
33 * Create a new file system.
34 * @constructor
35 */
36function FileSystem() {
37
38 var root = new Directory();
39
40 // populate with default directories
41 var defaults = [os.tmpdir && os.tmpdir() || os.tmpDir(), process.cwd()];
42 defaults.forEach(function(dir) {
43 var parts = getPathParts(dir);
44 var directory = root;
45 var i, ii, name, candidate;
46 for (i = 0, ii = parts.length; i < ii; ++i) {
47 name = parts[i];
48 candidate = directory.getItem(name);
49 if (!candidate) {
50 directory = directory.addItem(name, new Directory());
51 } else if (candidate instanceof Directory) {
52 directory = candidate;
53 } else {
54 throw new Error('Failed to create directory: ' + dir);
55 }
56 }
57 });
58
59 /**
60 * Root directory.
61 * @type {Directory}
62 */
63 this._root = root;
64
65}
66
67
68/**
69 * Get a file system item.
70 * @param {string} filepath Path to item.
71 * @return {Item} The item (or null if not found).
72 */
73FileSystem.prototype.getItem = function(filepath) {
74 var parts = getPathParts(filepath);
75 var currentParts = getPathParts(process.cwd());
76 var item = this._root;
77 var name;
78 for (var i = 0, ii = parts.length; i < ii; ++i) {
79 name = parts[i];
80 if (item instanceof Directory && name !== currentParts[i]) {
81 // make sure traversal is allowed
82 if (!item.canExecute()) {
83 throw new FSError('EACCES', filepath);
84 }
85 }
86 item = item.getItem(name);
87 if (!item) {
88 break;
89 }
90 }
91 return item;
92};
93
94
95/**
96 * Populate a directory with an item.
97 * @param {Directory} directory The directory to populate.
98 * @param {string} name The name of the item.
99 * @param {string|Buffer|function|Object} obj Instructions for creating the
100 * item.
101 */
102function populate(directory, name, obj) {
103 var item;
104 if (typeof obj === 'string' || Buffer.isBuffer(obj)) {
105 // contents for a file
106 item = new File();
107 item.setContent(obj);
108 } else if (typeof obj === 'function') {
109 // item factory
110 item = obj();
111 } else {
112 // directory with more to populate
113 item = new Directory();
114 for (var key in obj) {
115 populate(item, key, obj[key]);
116 }
117 }
118 /**
119 * Special exception for redundant adding of empty directories.
120 */
121 if (item instanceof Directory &&
122 item.list().length === 0 &&
123 directory.getItem(name) instanceof Directory) {
124 // pass
125 } else {
126 directory.addItem(name, item);
127 }
128}
129
130
131/**
132 * Configure a mock file system.
133 * @param {Object} paths Config object.
134 * @return {FileSystem} Mock file system.
135 */
136FileSystem.create = function(paths) {
137 var system = new FileSystem();
138
139 for (var filepath in paths) {
140 var parts = getPathParts(filepath);
141 var directory = system._root;
142 var i, ii, name, candidate;
143 for (i = 0, ii = parts.length - 1; i < ii; ++i) {
144 name = parts[i];
145 candidate = directory.getItem(name);
146 if (!candidate) {
147 directory = directory.addItem(name, new Directory());
148 } else if (candidate instanceof Directory) {
149 directory = candidate;
150 } else {
151 throw new Error('Failed to create directory: ' + filepath);
152 }
153 }
154 populate(directory, parts[i], paths[filepath]);
155 }
156
157 return system;
158};
159
160
161/**
162 * Generate a factory for new files.
163 * @param {Object} config File config.
164 * @return {function():File} Factory that creates a new file.
165 */
166FileSystem.file = function(config) {
167 config = config || {};
168 return function() {
169 var file = new File();
170 if (config.hasOwnProperty('content')) {
171 file.setContent(config.content);
172 }
173 if (config.hasOwnProperty('mode')) {
174 file.setMode(config.mode);
175 } else {
176 file.setMode(438); // 0666
177 }
178 if (config.hasOwnProperty('uid')) {
179 file.setUid(config.uid);
180 }
181 if (config.hasOwnProperty('gid')) {
182 file.setGid(config.gid);
183 }
184 if (config.hasOwnProperty('atime')) {
185 file.setATime(config.atime);
186 }
187 if (config.hasOwnProperty('ctime')) {
188 file.setCTime(config.ctime);
189 }
190 if (config.hasOwnProperty('mtime')) {
191 file.setMTime(config.mtime);
192 }
193 return file;
194 };
195};
196
197
198/**
199 * Generate a factory for new symbolic links.
200 * @param {Object} config File config.
201 * @return {function():File} Factory that creates a new symbolic link.
202 */
203FileSystem.symlink = function(config) {
204 config = config || {};
205 return function() {
206 var link = new SymbolicLink();
207 if (config.hasOwnProperty('mode')) {
208 link.setMode(config.mode);
209 } else {
210 link.setMode(438); // 0666
211 }
212 if (config.hasOwnProperty('uid')) {
213 link.setUid(config.uid);
214 }
215 if (config.hasOwnProperty('gid')) {
216 link.setGid(config.gid);
217 }
218 if (config.hasOwnProperty('path')) {
219 link.setPath(config.path);
220 } else {
221 throw new Error('Missing "path" property');
222 }
223 if (config.hasOwnProperty('atime')) {
224 link.setATime(config.atime);
225 }
226 if (config.hasOwnProperty('ctime')) {
227 link.setCTime(config.ctime);
228 }
229 if (config.hasOwnProperty('mtime')) {
230 link.setMTime(config.mtime);
231 }
232 return link;
233 };
234};
235
236
237/**
238 * Generate a factory for new directories.
239 * @param {Object} config File config.
240 * @return {function():Directory} Factory that creates a new directory.
241 */
242FileSystem.directory = function(config) {
243 config = config || {};
244 return function() {
245 var dir = new Directory();
246 if (config.hasOwnProperty('mode')) {
247 dir.setMode(config.mode);
248 }
249 if (config.hasOwnProperty('uid')) {
250 dir.setUid(config.uid);
251 }
252 if (config.hasOwnProperty('gid')) {
253 dir.setGid(config.gid);
254 }
255 if (config.hasOwnProperty('items')) {
256 for (var name in config.items) {
257 populate(dir, name, config.items[name]);
258 }
259 }
260 if (config.hasOwnProperty('atime')) {
261 dir.setATime(config.atime);
262 }
263 if (config.hasOwnProperty('ctime')) {
264 dir.setCTime(config.ctime);
265 }
266 if (config.hasOwnProperty('mtime')) {
267 dir.setMTime(config.mtime);
268 }
269 return dir;
270 };
271};
272
273
274/**
275 * Module exports.
276 * @type {function}
277 */
278module.exports = FileSystem;