UNPKG

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