file-writer.js 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. /*
  2. Copyright 2012-2015, Yahoo Inc.
  3. Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
  4. */
  5. const util = require('util');
  6. const path = require('path');
  7. const fs = require('fs');
  8. const mkdirp = require('make-dir');
  9. const supportsColor = require('supports-color');
  10. const isAbsolute =
  11. path.isAbsolute ||
  12. /* istanbul ignore next */ function(p) {
  13. return path.resolve(p) === path.normalize(p);
  14. };
  15. /**
  16. * abstract interface for writing content
  17. * @class ContentWriter
  18. * @constructor
  19. */
  20. /* istanbul ignore next: abstract class */
  21. function ContentWriter() {}
  22. /**
  23. * writes a string as-is to the destination
  24. * @param {String} str the string to write
  25. */
  26. /* istanbul ignore next: abstract class */
  27. ContentWriter.prototype.write = function() {
  28. throw new Error('write: must be overridden');
  29. };
  30. /**
  31. * returns the colorized version of a string. Typically,
  32. * content writers that write to files will return the
  33. * same string and ones writing to a tty will wrap it in
  34. * appropriate escape sequences.
  35. * @param {String} str the string to colorize
  36. * @param {String} clazz one of `high`, `medium` or `low`
  37. * @returns {String} the colorized form of the string
  38. */
  39. ContentWriter.prototype.colorize = function(str /*, clazz*/) {
  40. return str;
  41. };
  42. /**
  43. * writes a string appended with a newline to the destination
  44. * @param {String} str the string to write
  45. */
  46. ContentWriter.prototype.println = function(str) {
  47. this.write(str + '\n');
  48. };
  49. /**
  50. * closes this content writer. Should be called after all writes are complete.
  51. */
  52. ContentWriter.prototype.close = function() {};
  53. /**
  54. * a content writer that writes to a file
  55. * @param {Number} fd - the file descriptor
  56. * @extends ContentWriter
  57. * @constructor
  58. */
  59. function FileContentWriter(fd) {
  60. this.fd = fd;
  61. }
  62. util.inherits(FileContentWriter, ContentWriter);
  63. FileContentWriter.prototype.write = function(str) {
  64. fs.writeSync(this.fd, str);
  65. };
  66. FileContentWriter.prototype.close = function() {
  67. fs.closeSync(this.fd);
  68. };
  69. /**
  70. * a content writer that writes to the console
  71. * @extends ContentWriter
  72. * @constructor
  73. */
  74. function ConsoleWriter() {}
  75. util.inherits(ConsoleWriter, ContentWriter);
  76. // allow stdout to be captured for tests.
  77. let capture = false;
  78. let output = '';
  79. ConsoleWriter.prototype.write = function(str) {
  80. if (capture) {
  81. output += str;
  82. } else {
  83. process.stdout.write(str);
  84. }
  85. };
  86. ConsoleWriter.prototype.colorize = function(str, clazz) {
  87. const colors = {
  88. low: '31;1',
  89. medium: '33;1',
  90. high: '32;1'
  91. };
  92. /* istanbul ignore next: different modes for CI and local */
  93. if (supportsColor.stdout && colors[clazz]) {
  94. return '\u001b[' + colors[clazz] + 'm' + str + '\u001b[0m';
  95. }
  96. return str;
  97. };
  98. /**
  99. * utility for writing files under a specific directory
  100. * @class FileWriter
  101. * @param {String} baseDir the base directory under which files should be written
  102. * @constructor
  103. */
  104. function FileWriter(baseDir) {
  105. if (!baseDir) {
  106. throw new Error('baseDir must be specified');
  107. }
  108. this.baseDir = baseDir;
  109. }
  110. /**
  111. * static helpers for capturing stdout report output;
  112. * super useful for tests!
  113. */
  114. FileWriter.startCapture = function() {
  115. capture = true;
  116. };
  117. FileWriter.stopCapture = function() {
  118. capture = false;
  119. };
  120. FileWriter.getOutput = function() {
  121. return output;
  122. };
  123. FileWriter.resetOutput = function() {
  124. output = '';
  125. };
  126. /**
  127. * returns a FileWriter that is rooted at the supplied subdirectory
  128. * @param {String} subdir the subdirectory under which to root the
  129. * returned FileWriter
  130. * @returns {FileWriter}
  131. */
  132. FileWriter.prototype.writerForDir = function(subdir) {
  133. if (isAbsolute(subdir)) {
  134. throw new Error(
  135. 'Cannot create subdir writer for absolute path: ' + subdir
  136. );
  137. }
  138. return new FileWriter(this.baseDir + '/' + subdir);
  139. };
  140. /**
  141. * copies a file from a source directory to a destination name
  142. * @param {String} source path to source file
  143. * @param {String} dest relative path to destination file
  144. * @param {String} [header=undefined] optional text to prepend to destination
  145. * (e.g., an "this file is autogenerated" comment, copyright notice, etc.)
  146. */
  147. FileWriter.prototype.copyFile = function(source, dest, header) {
  148. if (isAbsolute(dest)) {
  149. throw new Error('Cannot write to absolute path: ' + dest);
  150. }
  151. dest = path.resolve(this.baseDir, dest);
  152. mkdirp.sync(path.dirname(dest));
  153. let contents;
  154. if (header) {
  155. contents = header + fs.readFileSync(source, 'utf8');
  156. } else {
  157. contents = fs.readFileSync(source);
  158. }
  159. fs.writeFileSync(dest, contents);
  160. };
  161. /**
  162. * returns a content writer for writing content to the supplied file.
  163. * @param {String|null} file the relative path to the file or the special
  164. * values `"-"` or `null` for writing to the console
  165. * @returns {ContentWriter}
  166. */
  167. FileWriter.prototype.writeFile = function(file) {
  168. if (file === null || file === '-') {
  169. return new ConsoleWriter();
  170. }
  171. if (isAbsolute(file)) {
  172. throw new Error('Cannot write to absolute path: ' + file);
  173. }
  174. file = path.resolve(this.baseDir, file);
  175. mkdirp.sync(path.dirname(file));
  176. return new FileContentWriter(fs.openSync(file, 'w'));
  177. };
  178. module.exports = FileWriter;