index.js 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. var path = require('path');
  2. var clone = require('clone');
  3. var cloneStats = require('clone-stats');
  4. var cloneBuffer = require('./lib/cloneBuffer');
  5. var isBuffer = require('./lib/isBuffer');
  6. var isStream = require('./lib/isStream');
  7. var isNull = require('./lib/isNull');
  8. var inspectStream = require('./lib/inspectStream');
  9. var Stream = require('stream');
  10. var replaceExt = require('replace-ext');
  11. function File(file) {
  12. if (!file) {
  13. file = {};
  14. }
  15. // Record path change
  16. var history = file.path ? [file.path] : file.history;
  17. this.history = history || [];
  18. this.cwd = file.cwd || process.cwd();
  19. this.base = file.base || this.cwd;
  20. // Stat = files stats object
  21. this.stat = file.stat || null;
  22. // Contents = stream, buffer, or null if not read
  23. this.contents = file.contents || null;
  24. this.contentType = file.contentType || null;
  25. this._isVinyl = true;
  26. }
  27. File.prototype.isBuffer = function() {
  28. return isBuffer(this.contents);
  29. };
  30. File.prototype.isStream = function() {
  31. return isStream(this.contents);
  32. };
  33. File.prototype.isNull = function() {
  34. return isNull(this.contents);
  35. };
  36. // TODO: Should this be moved to vinyl-fs?
  37. File.prototype.isDirectory = function() {
  38. return this.isNull() && this.stat && this.stat.isDirectory();
  39. };
  40. File.prototype.toJSON = function() {
  41. var self = this;
  42. var contents;
  43. if (self.contents === null) {
  44. contents = null;
  45. } else {
  46. contents = self.contents.toString();
  47. }
  48. var metadata = {
  49. basename: self.basename,
  50. contents: contents,
  51. dirname: self.dirname,
  52. extname: self.extname,
  53. path: self.path,
  54. relative: self.relative,
  55. stem: self.stem,
  56. contentType: self.contentType,
  57. };
  58. // copy all Fs.stat data
  59. for (var p in self.stat) {
  60. metadata[p] = self.stat[p];
  61. }
  62. return metadata;
  63. };
  64. File.prototype.clone = function(opt) {
  65. if (typeof opt === 'boolean') {
  66. opt = {
  67. deep: opt,
  68. contents: true,
  69. };
  70. } else if (!opt) {
  71. opt = {
  72. deep: true,
  73. contents: true,
  74. };
  75. } else {
  76. opt.deep = opt.deep === true;
  77. opt.contents = opt.contents !== false;
  78. }
  79. // Clone our file contents
  80. var contents;
  81. if (this.isStream()) {
  82. contents = this.contents.pipe(new Stream.PassThrough());
  83. this.contents = this.contents.pipe(new Stream.PassThrough());
  84. } else if (this.isBuffer()) {
  85. contents = opt.contents ? cloneBuffer(this.contents) : this.contents;
  86. }
  87. var file = new File({
  88. cwd: this.cwd,
  89. base: this.base,
  90. stat: (this.stat ? cloneStats(this.stat) : null),
  91. history: this.history.slice(),
  92. contents: contents,
  93. contentType: this.contentType
  94. });
  95. // Clone our custom properties
  96. Object.keys(this).forEach(function(key) {
  97. // Ignore built-in fields
  98. if (key === '_contents' || key === 'stat' ||
  99. key === 'history' || key === 'path' ||
  100. key === 'base' || key === 'cwd') {
  101. return;
  102. }
  103. file[key] = opt.deep ? clone(this[key], true) : this[key];
  104. }, this);
  105. return file;
  106. };
  107. File.prototype.pipe = function(stream, opt) {
  108. if (!opt) {
  109. opt = {};
  110. }
  111. if (typeof opt.end === 'undefined') {
  112. opt.end = true;
  113. }
  114. if (this.isStream()) {
  115. return this.contents.pipe(stream, opt);
  116. }
  117. if (this.isBuffer()) {
  118. if (opt.end) {
  119. stream.end(this.contents);
  120. } else {
  121. stream.write(this.contents);
  122. }
  123. return stream;
  124. }
  125. // Check if isNull
  126. if (opt.end) {
  127. stream.end();
  128. }
  129. return stream;
  130. };
  131. File.prototype.inspect = function() {
  132. var inspect = [];
  133. // Use relative path if possible
  134. var filePath = (this.base && this.path) ? this.relative : this.path;
  135. if (filePath) {
  136. inspect.push('"' + filePath + '"');
  137. }
  138. if (this.isBuffer()) {
  139. inspect.push(this.contents.inspect());
  140. }
  141. if (this.isStream()) {
  142. inspect.push(inspectStream(this.contents));
  143. }
  144. return '<File ' + inspect.join(' ') + '>';
  145. };
  146. File.isVinyl = function(file) {
  147. return (file && file._isVinyl === true) || false;
  148. };
  149. // Virtual attributes
  150. // Or stuff with extra logic
  151. Object.defineProperty(File.prototype, 'contents', {
  152. get: function() {
  153. return this._contents;
  154. },
  155. set: function(val) {
  156. if (!isBuffer(val) && !isStream(val) && !isNull(val)) {
  157. throw new Error('File.contents can only be a Buffer, a Stream, or null.');
  158. }
  159. this._contents = val;
  160. },
  161. });
  162. // TODO: Should this be moved to vinyl-fs?
  163. Object.defineProperty(File.prototype, 'relative', {
  164. get: function() {
  165. if (!this.base) {
  166. throw new Error('No base specified! Can not get relative.');
  167. }
  168. if (!this.path) {
  169. throw new Error('No path specified! Can not get relative.');
  170. }
  171. return path.relative(this.base, this.path);
  172. },
  173. set: function() {
  174. throw new Error('File.relative is generated from the base and path attributes. Do not modify it.');
  175. },
  176. });
  177. Object.defineProperty(File.prototype, 'dirname', {
  178. get: function() {
  179. if (!this.path) {
  180. throw new Error('No path specified! Can not get dirname.');
  181. }
  182. return path.dirname(this.path);
  183. },
  184. set: function(dirname) {
  185. if (!this.path) {
  186. throw new Error('No path specified! Can not set dirname.');
  187. }
  188. this.path = path.join(dirname, path.basename(this.path));
  189. },
  190. });
  191. Object.defineProperty(File.prototype, 'basename', {
  192. get: function() {
  193. if (!this.path) {
  194. throw new Error('No path specified! Can not get basename.');
  195. }
  196. return path.basename(this.path);
  197. },
  198. set: function(basename) {
  199. if (!this.path) {
  200. throw new Error('No path specified! Can not set basename.');
  201. }
  202. this.path = path.join(path.dirname(this.path), basename);
  203. },
  204. });
  205. // Property for getting/setting stem of the filename.
  206. Object.defineProperty(File.prototype, 'stem', {
  207. get: function() {
  208. if (!this.path) {
  209. throw new Error('No path specified! Can not get stem.');
  210. }
  211. return path.basename(this.path, this.extname);
  212. },
  213. set: function(stem) {
  214. if (!this.path) {
  215. throw new Error('No path specified! Can not set stem.');
  216. }
  217. this.path = path.join(path.dirname(this.path), stem + this.extname);
  218. },
  219. });
  220. Object.defineProperty(File.prototype, 'extname', {
  221. get: function() {
  222. if (!this.path) {
  223. throw new Error('No path specified! Can not get extname.');
  224. }
  225. return path.extname(this.path);
  226. },
  227. set: function(extname) {
  228. if (!this.path) {
  229. throw new Error('No path specified! Can not set extname.');
  230. }
  231. this.path = replaceExt(this.path, extname);
  232. },
  233. });
  234. Object.defineProperty(File.prototype, 'path', {
  235. get: function() {
  236. return this.history[this.history.length - 1];
  237. },
  238. set: function(path) {
  239. if (typeof path !== 'string') {
  240. throw new Error('path should be string');
  241. }
  242. // Record history only when path changed
  243. if (path && path !== this.path) {
  244. this.history.push(path);
  245. }
  246. },
  247. });
  248. module.exports = File;