util.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. const path = require('path');
  2. const Git = require('./git.js');
  3. const async = require('async');
  4. const fs = require('fs-extra');
  5. /**
  6. * Generate a list of unique directory paths given a list of file paths.
  7. * @param {Array<string>} files List of file paths.
  8. * @return {Array<string>} List of directory paths.
  9. */
  10. function uniqueDirs(files) {
  11. const dirs = {};
  12. files.forEach((filepath) => {
  13. const parts = path.dirname(filepath).split(path.sep);
  14. let partial = parts[0] || '/';
  15. dirs[partial] = true;
  16. for (let i = 1, ii = parts.length; i < ii; ++i) {
  17. partial = path.join(partial, parts[i]);
  18. dirs[partial] = true;
  19. }
  20. });
  21. return Object.keys(dirs);
  22. }
  23. exports.uniqueDirs = uniqueDirs;
  24. /**
  25. * Sort function for paths. Sorter paths come first. Paths of equal length are
  26. * sorted alphanumerically in path segment order.
  27. * @param {string} a First path.
  28. * @param {string} b Second path.
  29. * @return {number} Comparison.
  30. */
  31. function byShortPath(a, b) {
  32. const aParts = a.split(path.sep);
  33. const bParts = b.split(path.sep);
  34. const aLength = aParts.length;
  35. const bLength = bParts.length;
  36. let cmp = 0;
  37. if (aLength < bLength) {
  38. cmp = -1;
  39. } else if (aLength > bLength) {
  40. cmp = 1;
  41. } else {
  42. let aPart, bPart;
  43. for (let i = 0; i < aLength; ++i) {
  44. aPart = aParts[i];
  45. bPart = bParts[i];
  46. if (aPart < bPart) {
  47. cmp = -1;
  48. break;
  49. } else if (aPart > bPart) {
  50. cmp = 1;
  51. break;
  52. }
  53. }
  54. }
  55. return cmp;
  56. }
  57. exports.byShortPath = byShortPath;
  58. /**
  59. * Generate a list of directories to create given a list of file paths.
  60. * @param {Array<string>} files List of file paths.
  61. * @return {Array<string>} List of directory paths ordered by path length.
  62. */
  63. function dirsToCreate(files) {
  64. return uniqueDirs(files).sort(byShortPath);
  65. }
  66. exports.dirsToCreate = dirsToCreate;
  67. /**
  68. * Copy a file.
  69. * @param {Object} obj Object with src and dest properties.
  70. * @param {function(Error)} callback Callback
  71. */
  72. function copyFile(obj, callback) {
  73. let called = false;
  74. function done(err) {
  75. if (!called) {
  76. called = true;
  77. callback(err);
  78. }
  79. }
  80. const read = fs.createReadStream(obj.src);
  81. read.on('error', (err) => {
  82. done(err);
  83. });
  84. const write = fs.createWriteStream(obj.dest);
  85. write.on('error', (err) => {
  86. done(err);
  87. });
  88. write.on('close', () => {
  89. done();
  90. });
  91. read.pipe(write);
  92. }
  93. exports.copyFile = copyFile;
  94. /**
  95. * Make directory, ignoring errors if directory already exists.
  96. * @param {string} path Directory path.
  97. * @param {function(Error)} callback Callback.
  98. */
  99. function makeDir(path, callback) {
  100. fs.mkdir(path, (err) => {
  101. if (err) {
  102. // check if directory exists
  103. fs.stat(path, (err2, stat) => {
  104. if (err2 || !stat.isDirectory()) {
  105. callback(err);
  106. } else {
  107. callback();
  108. }
  109. });
  110. } else {
  111. callback();
  112. }
  113. });
  114. }
  115. /**
  116. * Copy a list of files.
  117. * @param {Array<string>} files Files to copy.
  118. * @param {string} base Base directory.
  119. * @param {string} dest Destination directory.
  120. * @return {Promise} A promise.
  121. */
  122. exports.copy = function (files, base, dest) {
  123. return new Promise((resolve, reject) => {
  124. const pairs = [];
  125. const destFiles = [];
  126. files.forEach((file) => {
  127. const src = path.resolve(base, file);
  128. const relative = path.relative(base, src);
  129. const target = path.join(dest, relative);
  130. pairs.push({
  131. src: src,
  132. dest: target,
  133. });
  134. destFiles.push(target);
  135. });
  136. async.eachSeries(dirsToCreate(destFiles), makeDir, (err) => {
  137. if (err) {
  138. return reject(err);
  139. }
  140. async.each(pairs, copyFile, (err) => {
  141. if (err) {
  142. return reject(err);
  143. } else {
  144. return resolve();
  145. }
  146. });
  147. });
  148. });
  149. };
  150. exports.getUser = function (cwd) {
  151. return Promise.all([
  152. new Git(cwd).exec('config', 'user.name'),
  153. new Git(cwd).exec('config', 'user.email'),
  154. ])
  155. .then((results) => {
  156. return {name: results[0].output.trim(), email: results[1].output.trim()};
  157. })
  158. .catch((err) => {
  159. // git config exits with 1 if name or email is not set
  160. return null;
  161. });
  162. };