util.js 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  1. var assert = require("assert");
  2. var path = require("path");
  3. var fs = require("graceful-fs");
  4. var Q = require("q");
  5. var createHash = require("crypto").createHash;
  6. var mkdirp = require("mkdirp");
  7. var iconv = require("iconv-lite");
  8. var Ap = Array.prototype;
  9. var slice = Ap.slice;
  10. var join = Ap.join;
  11. // The graceful-fs module attempts to limit the total number of open files
  12. // by queueing fs operations, but it doesn't know about all open files, so
  13. // we set the limit somewhat lower than the default to provide a healthy
  14. // buffer against EMFILE (too many open files) errors.
  15. fs.MAX_OPEN = 512;
  16. function makePromise(callback, context) {
  17. var deferred = Q.defer();
  18. function finish(err, result) {
  19. if (err) {
  20. deferred.reject(err);
  21. } else {
  22. deferred.resolve(result);
  23. }
  24. }
  25. process.nextTick(function() {
  26. try {
  27. callback.call(context || null, finish);
  28. } catch (err) {
  29. finish(err);
  30. }
  31. });
  32. return deferred.promise;
  33. }
  34. exports.makePromise = makePromise;
  35. exports.cachedMethod = function(fn, keyFn) {
  36. var p = require("private").makeAccessor();
  37. function wrapper() {
  38. var priv = p(this);
  39. var cache = priv.cache || (priv.cache = {});
  40. var args = arguments;
  41. var key = keyFn
  42. ? keyFn.apply(this, args)
  43. : join.call(args, "\0");
  44. return cache.hasOwnProperty(key)
  45. ? cache[key]
  46. : cache[key] = fn.apply(this, args);
  47. }
  48. wrapper.originalFn = fn;
  49. return wrapper;
  50. };
  51. function readFileP(file, charset) {
  52. return makePromise(charset ? function(callback) {
  53. return fs.readFile(file, function(err, data) {
  54. if (err) {
  55. callback(err);
  56. } else {
  57. callback(null, iconv.decode(data, charset));
  58. }
  59. });
  60. } : function(callback) {
  61. return fs.readFile(file, "utf8", callback);
  62. });
  63. }
  64. exports.readFileP = readFileP;
  65. exports.readJsonFileP = function(file) {
  66. return readFileP(file).then(function(json) {
  67. return JSON.parse(json);
  68. });
  69. };
  70. function mkdirP(dir) {
  71. return makePromise(function(callback) {
  72. mkdirp(dir, function(err) {
  73. callback(err, dir);
  74. });
  75. });
  76. }
  77. exports.mkdirP = mkdirP;
  78. function readFromStdinP(timeLimit, message, color) {
  79. var deferred = Q.defer();
  80. var ins = [];
  81. timeLimit = timeLimit || 1000;
  82. var timeout = setTimeout(function() {
  83. log.err(
  84. message || ("Warning: still waiting for STDIN after " +
  85. timeLimit + "ms"),
  86. color || "yellow"
  87. );
  88. }, timeLimit);
  89. try {
  90. // On Windows, just accessing process.stdin throws an exception
  91. // when no standard input has been provided. For consistency with
  92. // other platforms, log the error but continue waiting (until
  93. // killed) for the nonexistent input.
  94. var stdin = process.stdin;
  95. } catch (err) {
  96. log.err(err);
  97. }
  98. if (stdin) {
  99. stdin.resume();
  100. stdin.setEncoding("utf8");
  101. stdin.on("data", function(data) {
  102. ins.push(data);
  103. }).on("end", function() {
  104. clearTimeout(timeout);
  105. deferred.resolve(ins.join(""));
  106. });
  107. }
  108. return deferred.promise;
  109. }
  110. exports.readFromStdinP = readFromStdinP;
  111. exports.readJsonFromStdinP = function(timeLimit) {
  112. return readFromStdinP(timeLimit).then(function(input) {
  113. return JSON.parse(input);
  114. });
  115. };
  116. function deepHash(val) {
  117. var hash = createHash("sha1");
  118. var type = typeof val;
  119. if (val === null) {
  120. type = "null";
  121. }
  122. switch (type) {
  123. case "object":
  124. Object.keys(val).sort().forEach(function(key) {
  125. if (typeof val[key] === "function") {
  126. // Silently ignore nested methods, but nevertheless
  127. // complain below if the root value is a function.
  128. return;
  129. }
  130. hash.update(key + "\0")
  131. .update(deepHash(val[key]));
  132. });
  133. break;
  134. case "function":
  135. assert.ok(false, "cannot hash function objects");
  136. break;
  137. default:
  138. hash.update(val + "");
  139. break;
  140. }
  141. return hash.digest("hex");
  142. }
  143. exports.deepHash = deepHash;
  144. exports.existsP = function(fullPath) {
  145. return makePromise(function(callback) {
  146. fs.exists(fullPath, function(exists) {
  147. callback(null, exists);
  148. });
  149. });
  150. };
  151. function writeFdP(fd, content) {
  152. return makePromise(function(callback) {
  153. content += "";
  154. var buffer = new Buffer(content, "utf8");
  155. var length = fs.writeSync(fd, buffer, 0, buffer.length, null);
  156. assert.strictEqual(length, buffer.length);
  157. callback(null, content);
  158. }).finally(function() {
  159. fs.closeSync(fd);
  160. });
  161. }
  162. exports.writeFdP = writeFdP;
  163. function openFileP(file, mode) {
  164. return makePromise(function(callback) {
  165. fs.open(file, mode || "w+", callback);
  166. });
  167. }
  168. exports.openFileP = openFileP;
  169. function openExclusiveP(file) {
  170. // The 'x' in "wx+" means the file must be newly created.
  171. return openFileP(file, "wx+");
  172. }
  173. exports.openExclusiveP = openExclusiveP;
  174. exports.copyP = function(srcFile, dstFile) {
  175. return makePromise(function(callback) {
  176. var reader = fs.createReadStream(srcFile);
  177. function onError(err) {
  178. callback(err || new Error(
  179. "error in util.copyP(" +
  180. JSON.stringify(srcFile) + ", " +
  181. JSON.stringify(dstFile) + ")"
  182. ));
  183. }
  184. reader.on("error", onError).pipe(
  185. fs.createWriteStream(dstFile)
  186. ).on("finish", function() {
  187. callback(null, dstFile);
  188. }).on("error", onError);
  189. });
  190. };
  191. // Even though they use synchronous operations to avoid race conditions,
  192. // linkP and unlinkP have promise interfaces, for consistency. Note that
  193. // this means the operation will not happen until at least the next tick
  194. // of the event loop, but it will be atomic when it happens.
  195. exports.linkP = function(srcFile, dstFile) {
  196. return mkdirP(path.dirname(dstFile)).then(function() {
  197. if (fs.existsSync(dstFile))
  198. fs.unlinkSync(dstFile);
  199. fs.linkSync(srcFile, dstFile);
  200. return dstFile;
  201. });
  202. };
  203. exports.unlinkP = function(file) {
  204. return makePromise(function(callback) {
  205. try {
  206. if (fs.existsSync(file))
  207. fs.unlinkSync(file);
  208. callback(null, file);
  209. } catch (err) {
  210. callback(err);
  211. }
  212. });
  213. };
  214. var colors = {
  215. bold: "\033[1m",
  216. red: "\033[31m",
  217. green: "\033[32m",
  218. yellow: "\033[33m",
  219. cyan: "\033[36m",
  220. reset: "\033[0m"
  221. };
  222. Object.keys(colors).forEach(function(key) {
  223. if (key !== "reset") {
  224. exports[key] = function(text) {
  225. return colors[key] + text + colors.reset;
  226. };
  227. }
  228. });
  229. var log = exports.log = {
  230. out: function(text, color) {
  231. text = (text + "").trim();
  232. if (colors.hasOwnProperty(color))
  233. text = colors[color] + text + colors.reset;
  234. process.stdout.write(text + "\n");
  235. },
  236. err: function(text, color) {
  237. text = (text + "").trim();
  238. if (!colors.hasOwnProperty(color))
  239. color = "red";
  240. text = colors[color] + text + colors.reset;
  241. process.stderr.write(text + "\n");
  242. }
  243. };
  244. var slugExp = /[^a-z\-]/ig;
  245. exports.idToSlug = function(id) {
  246. return id.replace(slugExp, "_");
  247. };
  248. var moduleIdExp = /^[ a-z0-9\-_\/\.]+$/i;
  249. exports.isValidModuleId = function(id) {
  250. return id === "<stdin>" || moduleIdExp.test(id);
  251. };
  252. var objToStr = Object.prototype.toString;
  253. var arrStr = objToStr.call([]);
  254. function flatten(value, into) {
  255. if (objToStr.call(value) === arrStr) {
  256. into = into || [];
  257. for (var i = 0, len = value.length; i < len; ++i)
  258. if (i in value) // Skip holes.
  259. flatten(value[i], into);
  260. } else if (into) {
  261. into.push(value);
  262. } else {
  263. return value;
  264. }
  265. return into;
  266. };
  267. exports.flatten = flatten;
  268. exports.inherits = function(ctor, base) {
  269. return ctor.prototype = Object.create(base.prototype, {
  270. constructor: { value: ctor }
  271. });
  272. };
  273. function absolutize(moduleId, requiredId) {
  274. if (requiredId.charAt(0) === ".")
  275. requiredId = path.join(moduleId, "..", requiredId);
  276. return path.normalize(requiredId).replace(/\\/g, '/');
  277. }
  278. exports.absolutize = absolutize;
  279. function relativize(moduleId, requiredId) {
  280. requiredId = absolutize(moduleId, requiredId);
  281. if (requiredId.charAt(0) === ".") {
  282. // Keep the required ID relative.
  283. } else {
  284. // Relativize the required ID.
  285. requiredId = path.relative(
  286. path.join(moduleId, ".."),
  287. requiredId
  288. );
  289. }
  290. if (requiredId.charAt(0) !== ".")
  291. requiredId = "./" + requiredId;
  292. return requiredId.replace(/\\/g, '/');
  293. }
  294. exports.relativize = relativize;
  295. function waitForValuesP(obj, makeCopy) {
  296. if (typeof obj !== "object")
  297. return Q(obj);
  298. var result = makeCopy ? {} : obj;
  299. var keys = Object.keys(obj);
  300. if (keys.length === 0)
  301. return Q(result);
  302. return Q.all(keys.map(function(key) {
  303. return obj[key];
  304. })).then(function(values) {
  305. for (var i = values.length - 1; i >= 0; --i)
  306. result[keys[i]] = values[i];
  307. return result;
  308. });
  309. }
  310. exports.waitForValuesP = waitForValuesP;
  311. function camelize(hyphenated) {
  312. return hyphenated.replace(/-(.)/g, function(_, ch) {
  313. return ch.toUpperCase();
  314. });
  315. }
  316. exports.camelize = camelize;