UNPKG

7.87 kBJavaScriptView Raw
1'use strict';
2
3var path = require('path');
4var util = require('util');
5var Buffer = require('buffer').Buffer;
6
7var clone = require('clone');
8var teex = require('teex');
9var replaceExt = require('replace-ext');
10var cloneStats = require('clone-stats');
11var removeTrailingSep = require('remove-trailing-separator');
12
13var isStream = require('./lib/is-stream');
14var normalize = require('./lib/normalize');
15var inspectStream = require('./lib/inspect-stream');
16
17var builtInFields = [
18 '_contents',
19 '_symlink',
20 'contents',
21 'stat',
22 'history',
23 'path',
24 '_base',
25 'base',
26 '_cwd',
27 'cwd',
28];
29
30function File(file) {
31 var self = this;
32
33 if (!file) {
34 file = {};
35 }
36
37 // Stat = files stats object
38 this.stat = file.stat || null;
39
40 // Contents = stream, buffer, or null if not read
41 this.contents = file.contents || null;
42
43 // Replay path history to ensure proper normalization and trailing sep
44 var history = Array.prototype.slice.call(file.history || []);
45 if (file.path) {
46 history.push(file.path);
47 }
48 this.history = [];
49 history.forEach(function (path) {
50 self.path = path;
51 });
52
53 this.cwd = file.cwd || process.cwd();
54 this.base = file.base;
55
56 this._isVinyl = true;
57
58 this._symlink = null;
59
60 // Set custom properties
61 Object.keys(file).forEach(function (key) {
62 if (self.constructor.isCustomProp(key)) {
63 self[key] = file[key];
64 }
65 });
66}
67
68File.prototype.isBuffer = function () {
69 return Buffer.isBuffer(this.contents);
70};
71
72File.prototype.isStream = function () {
73 return isStream(this.contents);
74};
75
76File.prototype.isNull = function () {
77 return this.contents === null;
78};
79
80File.prototype.isDirectory = function () {
81 if (!this.isNull()) {
82 return false;
83 }
84
85 if (this.stat && typeof this.stat.isDirectory === 'function') {
86 return this.stat.isDirectory();
87 }
88
89 return false;
90};
91
92File.prototype.isSymbolic = function () {
93 if (!this.isNull()) {
94 return false;
95 }
96
97 if (this.stat && typeof this.stat.isSymbolicLink === 'function') {
98 return this.stat.isSymbolicLink();
99 }
100
101 return false;
102};
103
104File.prototype.clone = function (opt) {
105 var self = this;
106
107 if (typeof opt === 'boolean') {
108 opt = {
109 deep: opt,
110 contents: true,
111 };
112 } else if (!opt) {
113 opt = {
114 deep: true,
115 contents: true,
116 };
117 } else {
118 opt.deep = opt.deep === true;
119 opt.contents = opt.contents !== false;
120 }
121
122 // Clone our file contents
123 var contents;
124 if (this.isStream()) {
125 var streams = teex(this._contents);
126 this._contents = streams[0];
127 contents = streams[1];
128 } else if (this.isBuffer()) {
129 contents = opt.contents ? Buffer.from(this.contents) : this.contents;
130 }
131
132 var file = new this.constructor({
133 cwd: this.cwd,
134 base: this.base,
135 stat: this.stat ? cloneStats(this.stat) : null,
136 history: this.history.slice(),
137 contents: contents,
138 });
139
140 if (this.isSymbolic()) {
141 file.symlink = this.symlink;
142 }
143
144 // Clone our custom properties
145 Object.keys(this).forEach(function (key) {
146 if (self.constructor.isCustomProp(key)) {
147 file[key] = opt.deep ? clone(self[key], true) : self[key];
148 }
149 });
150 return file;
151};
152
153// Node.js v6.6.0+ use this symbol for custom inspection.
154File.prototype[util.inspect.custom] = function () {
155 var inspect = [];
156
157 // Use relative path if possible
158 var filePath = this.path ? this.relative : null;
159
160 if (filePath) {
161 inspect.push('"' + filePath + '"');
162 }
163
164 if (this.isBuffer()) {
165 inspect.push(this.contents.inspect());
166 }
167
168 if (this.isStream()) {
169 inspect.push(inspectStream(this.contents));
170 }
171
172 return '<File ' + inspect.join(' ') + '>';
173};
174
175File.isCustomProp = function (key) {
176 return builtInFields.indexOf(key) === -1;
177};
178
179File.isVinyl = function (file) {
180 return (file && file._isVinyl === true) || false;
181};
182
183// Virtual attributes
184// Or stuff with extra logic
185Object.defineProperty(File.prototype, 'contents', {
186 get: function () {
187 return this._contents;
188 },
189 set: function (val) {
190 if (!Buffer.isBuffer(val) && !isStream(val) && val !== null) {
191 throw new Error('File.contents can only be a Buffer, a Stream, or null.');
192 }
193
194 this._contents = val;
195 },
196});
197
198Object.defineProperty(File.prototype, 'cwd', {
199 get: function () {
200 return this._cwd;
201 },
202 set: function (cwd) {
203 if (!cwd || typeof cwd !== 'string') {
204 throw new Error('cwd must be a non-empty string.');
205 }
206 this._cwd = removeTrailingSep(normalize(cwd));
207 },
208});
209
210Object.defineProperty(File.prototype, 'base', {
211 get: function () {
212 return this._base || this._cwd;
213 },
214 set: function (base) {
215 if (base == null) {
216 delete this._base;
217 return;
218 }
219 if (typeof base !== 'string' || !base) {
220 throw new Error('base must be a non-empty string, or null/undefined.');
221 }
222 base = removeTrailingSep(normalize(base));
223 if (base !== this._cwd) {
224 this._base = base;
225 } else {
226 delete this._base;
227 }
228 },
229});
230
231// TODO: Should this be moved to vinyl-fs?
232Object.defineProperty(File.prototype, 'relative', {
233 get: function () {
234 if (!this.path) {
235 throw new Error('No path specified! Can not get relative.');
236 }
237 return path.relative(this.base, this.path);
238 },
239 set: function () {
240 throw new Error(
241 'File.relative is generated from the base and path attributes. Do not modify it.'
242 );
243 },
244});
245
246Object.defineProperty(File.prototype, 'dirname', {
247 get: function () {
248 if (!this.path) {
249 throw new Error('No path specified! Can not get dirname.');
250 }
251 return path.dirname(this.path);
252 },
253 set: function (dirname) {
254 if (!this.path) {
255 throw new Error('No path specified! Can not set dirname.');
256 }
257 this.path = path.join(dirname, this.basename);
258 },
259});
260
261Object.defineProperty(File.prototype, 'basename', {
262 get: function () {
263 if (!this.path) {
264 throw new Error('No path specified! Can not get basename.');
265 }
266 return path.basename(this.path);
267 },
268 set: function (basename) {
269 if (!this.path) {
270 throw new Error('No path specified! Can not set basename.');
271 }
272 this.path = path.join(this.dirname, basename);
273 },
274});
275
276// Property for getting/setting stem of the filename.
277Object.defineProperty(File.prototype, 'stem', {
278 get: function () {
279 if (!this.path) {
280 throw new Error('No path specified! Can not get stem.');
281 }
282 return path.basename(this.path, this.extname);
283 },
284 set: function (stem) {
285 if (!this.path) {
286 throw new Error('No path specified! Can not set stem.');
287 }
288 this.path = path.join(this.dirname, stem + this.extname);
289 },
290});
291
292Object.defineProperty(File.prototype, 'extname', {
293 get: function () {
294 if (!this.path) {
295 throw new Error('No path specified! Can not get extname.');
296 }
297 return path.extname(this.path);
298 },
299 set: function (extname) {
300 if (!this.path) {
301 throw new Error('No path specified! Can not set extname.');
302 }
303 this.path = replaceExt(this.path, extname);
304 },
305});
306
307Object.defineProperty(File.prototype, 'path', {
308 get: function () {
309 var path = this.history[this.history.length - 1];
310 if (path) {
311 return path;
312 } else {
313 return null;
314 }
315 },
316 set: function (path) {
317 if (typeof path !== 'string') {
318 throw new Error('path should be a string.');
319 }
320 path = removeTrailingSep(normalize(path));
321
322 // Record history only when path changed
323 if (path && path !== this.path) {
324 this.history.push(path);
325 }
326 },
327});
328
329Object.defineProperty(File.prototype, 'symlink', {
330 get: function () {
331 return this._symlink;
332 },
333 set: function (symlink) {
334 // TODO: should this set the mode to symbolic if set?
335 if (typeof symlink !== 'string') {
336 throw new Error('symlink should be a string');
337 }
338
339 this._symlink = removeTrailingSep(normalize(symlink));
340 },
341});
342
343module.exports = File;