file-writer.js 4.8 KB

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