wrench.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  1. /* wrench.js
  2. *
  3. * A collection of various utility functions I've found myself in need of
  4. * for use with Node.js (http://nodejs.org/). This includes things like:
  5. *
  6. * - Recursively deleting directories in Node.js (Sync, not Async)
  7. * - Recursively copying directories in Node.js (Sync, not Async)
  8. * - Recursively chmoding a directory structure from Node.js (Sync, not Async)
  9. * - Other things that I'll add here as time goes on. Shhhh...
  10. *
  11. * ~ Ryan McGrath (ryan [at] venodesigns.net)
  12. */
  13. var fs = require("fs"),
  14. _path = require("path");
  15. /* wrench.readdirSyncRecursive("directory_path");
  16. *
  17. * Recursively dives through directories and read the contents of all the
  18. * children directories.
  19. */
  20. exports.readdirSyncRecursive = function(baseDir) {
  21. baseDir = baseDir.replace(/\/$/, '');
  22. var readdirSyncRecursive = function(baseDir) {
  23. var files = [],
  24. curFiles,
  25. nextDirs,
  26. isDir = function(fname){
  27. return fs.statSync( _path.join(baseDir, fname) ).isDirectory();
  28. },
  29. prependBaseDir = function(fname){
  30. return _path.join(baseDir, fname);
  31. };
  32. curFiles = fs.readdirSync(baseDir);
  33. nextDirs = curFiles.filter(isDir);
  34. curFiles = curFiles.map(prependBaseDir);
  35. files = files.concat( curFiles );
  36. while (nextDirs.length) {
  37. files = files.concat( readdirSyncRecursive( _path.join(baseDir, nextDirs.shift()) ) );
  38. }
  39. return files;
  40. };
  41. // convert absolute paths to relative
  42. var fileList = readdirSyncRecursive(baseDir).map(function(val){
  43. return _path.relative(baseDir, val);
  44. });
  45. return fileList;
  46. };
  47. /* wrench.readdirRecursive("directory_path", function(error, files) {});
  48. *
  49. * Recursively dives through directories and read the contents of all the
  50. * children directories.
  51. *
  52. * Asynchronous, so returns results/error in callback.
  53. * Callback receives the of files in currently recursed directory.
  54. * When no more directories are left, callback is called with null for all arguments.
  55. *
  56. */
  57. exports.readdirRecursive = function(baseDir, fn) {
  58. baseDir = baseDir.replace(/\/$/, '');
  59. var waitCount = 0;
  60. function readdirRecursive(curDir) {
  61. var files = [],
  62. curFiles,
  63. nextDirs,
  64. prependcurDir = function(fname){
  65. return _path.join(curDir, fname);
  66. };
  67. waitCount++;
  68. fs.readdir(curDir, function(e, curFiles) {
  69. waitCount--;
  70. curFiles = curFiles.map(prependcurDir);
  71. curFiles.forEach(function(it) {
  72. waitCount++;
  73. fs.stat(it, function(e, stat) {
  74. waitCount--;
  75. if (e) {
  76. fn(e);
  77. } else {
  78. if (stat.isDirectory()) {
  79. readdirRecursive(it);
  80. }
  81. }
  82. if (waitCount == 0) {
  83. fn(null, null);
  84. }
  85. });
  86. });
  87. fn(null, curFiles.map(function(val) {
  88. // convert absolute paths to relative
  89. return _path.relative(baseDir, val);
  90. }));
  91. if (waitCount == 0) {
  92. fn(null, null);
  93. }
  94. });
  95. };
  96. readdirRecursive(baseDir);
  97. };
  98. /* wrench.rmdirSyncRecursive("directory_path", forceDelete, failSilent);
  99. *
  100. * Recursively dives through directories and obliterates everything about it. This is a
  101. * Sync-function, which blocks things until it's done. No idea why anybody would want an
  102. * Asynchronous version. :\
  103. */
  104. exports.rmdirSyncRecursive = function(path, failSilent) {
  105. var files;
  106. try {
  107. files = fs.readdirSync(path);
  108. } catch (err) {
  109. if(failSilent) return;
  110. throw new Error(err.message);
  111. }
  112. /* Loop through and delete everything in the sub-tree after checking it */
  113. for(var i = 0; i < files.length; i++) {
  114. var currFile = fs.lstatSync(path + "/" + files[i]);
  115. if(currFile.isDirectory()) // Recursive function back to the beginning
  116. exports.rmdirSyncRecursive(path + "/" + files[i]);
  117. else if(currFile.isSymbolicLink()) // Unlink symlinks
  118. fs.unlinkSync(path + "/" + files[i]);
  119. else // Assume it's a file - perhaps a try/catch belongs here?
  120. fs.unlinkSync(path + "/" + files[i]);
  121. }
  122. /* Now that we know everything in the sub-tree has been deleted, we can delete the main
  123. directory. Huzzah for the shopkeep. */
  124. return fs.rmdirSync(path);
  125. };
  126. /* wrench.copyDirSyncRecursive("directory_to_copy", "new_directory_location", opts);
  127. *
  128. * Recursively dives through a directory and moves all its files to a new location. This is a
  129. * Synchronous function, which blocks things until it's done. If you need/want to do this in
  130. * an Asynchronous manner, look at wrench.copyDirRecursively() below.
  131. *
  132. * Note: Directories should be passed to this function without a trailing slash.
  133. */
  134. exports.copyDirSyncRecursive = function(sourceDir, newDirLocation, opts) {
  135. if (!opts || !opts.preserve) {
  136. try {
  137. if(fs.statSync(newDirLocation).isDirectory()) exports.rmdirSyncRecursive(newDirLocation);
  138. } catch(e) { }
  139. }
  140. /* Create the directory where all our junk is moving to; read the mode of the source directory and mirror it */
  141. var checkDir = fs.statSync(sourceDir);
  142. try {
  143. fs.mkdirSync(newDirLocation, checkDir.mode);
  144. } catch (e) {
  145. //if the directory already exists, that's okay
  146. if (e.code !== 'EEXIST') throw e;
  147. }
  148. var files = fs.readdirSync(sourceDir);
  149. for(var i = 0; i < files.length; i++) {
  150. var currFile = fs.lstatSync(sourceDir + "/" + files[i]);
  151. if(currFile.isDirectory()) {
  152. /* recursion this thing right on back. */
  153. exports.copyDirSyncRecursive(sourceDir + "/" + files[i], newDirLocation + "/" + files[i], opts);
  154. } else if(currFile.isSymbolicLink()) {
  155. var symlinkFull = fs.readlinkSync(sourceDir + "/" + files[i]);
  156. fs.symlinkSync(symlinkFull, newDirLocation + "/" + files[i]);
  157. } else {
  158. /* At this point, we've hit a file actually worth copying... so copy it on over. */
  159. var contents = fs.readFileSync(sourceDir + "/" + files[i]);
  160. fs.writeFileSync(newDirLocation + "/" + files[i], contents);
  161. }
  162. }
  163. };
  164. /* wrench.chmodSyncRecursive("directory", filemode);
  165. *
  166. * Recursively dives through a directory and chmods everything to the desired mode. This is a
  167. * Synchronous function, which blocks things until it's done.
  168. *
  169. * Note: Directories should be passed to this function without a trailing slash.
  170. */
  171. exports.chmodSyncRecursive = function(sourceDir, filemode) {
  172. var files = fs.readdirSync(sourceDir);
  173. for(var i = 0; i < files.length; i++) {
  174. var currFile = fs.lstatSync(sourceDir + "/" + files[i]);
  175. if(currFile.isDirectory()) {
  176. /* ...and recursion this thing right on back. */
  177. exports.chmodSyncRecursive(sourceDir + "/" + files[i], filemode);
  178. } else {
  179. /* At this point, we've hit a file actually worth copying... so copy it on over. */
  180. fs.chmod(sourceDir + "/" + files[i], filemode);
  181. }
  182. }
  183. /* Finally, chmod the parent directory */
  184. fs.chmod(sourceDir, filemode);
  185. };
  186. /* wrench.chownSyncRecursive("directory", uid, gid);
  187. *
  188. * Recursively dives through a directory and chowns everything to the desired user and group. This is a
  189. * Synchronous function, which blocks things until it's done.
  190. *
  191. * Note: Directories should be passed to this function without a trailing slash.
  192. */
  193. exports.chownSyncRecursive = function(sourceDir, uid, gid) {
  194. var files = fs.readdirSync(sourceDir);
  195. for(var i = 0; i < files.length; i++) {
  196. var currFile = fs.lstatSync(sourceDir + "/" + files[i]);
  197. if(currFile.isDirectory()) {
  198. /* ...and recursion this thing right on back. */
  199. exports.chownSyncRecursive(sourceDir + "/" + files[i], uid, gid);
  200. } else {
  201. /* At this point, we've hit a file actually worth chowning... so own it. */
  202. fs.chownSync(sourceDir + "/" + files[i], uid, gid);
  203. }
  204. }
  205. /* Finally, chown the parent directory */
  206. fs.chownSync(sourceDir, uid, gid);
  207. };
  208. /* wrench.rmdirRecursive("directory_path", callback);
  209. *
  210. * Recursively dives through directories and obliterates everything about it.
  211. */
  212. exports.rmdirRecursive = function rmdirRecursive(dir, clbk){
  213. fs.readdir(dir, function(err, files){
  214. if (err) return clbk(err);
  215. (function rmFile(err){
  216. if (err) return clbk(err);
  217. var filename = files.shift();
  218. if (filename === null || typeof filename == 'undefined')
  219. return fs.rmdir(dir, clbk);
  220. var file = dir+'/'+filename;
  221. fs.stat(file, function(err, stat){
  222. if (err) return clbk(err);
  223. if (stat.isDirectory())
  224. rmdirRecursive(file, rmFile);
  225. else
  226. fs.unlink(file, rmFile);
  227. });
  228. })();
  229. });
  230. };
  231. /* wrench.copyDirRecursive("directory_to_copy", "new_location", callback);
  232. *
  233. * Recursively dives through a directory and moves all its files to a new
  234. * location.
  235. *
  236. * Note: Directories should be passed to this function without a trailing slash.
  237. */
  238. exports.copyDirRecursive = function copyDirRecursive(srcDir, newDir, clbk) {
  239. fs.stat(newDir, function(err, newDirStat){
  240. if (!err) return exports.rmdirRecursive(newDir, function(err){
  241. copyDirRecursive(srcDir, newDir, clbk);
  242. });
  243. fs.stat(srcDir, function(err, srcDirStat){
  244. if (err) return clbk(err);
  245. fs.mkdir(newDir, srcDirStat.mode, function(err){
  246. if (err) return clbk(err);
  247. fs.readdir(srcDir, function(err, files){
  248. if (err) return clbk(err);
  249. (function copyFiles(err){
  250. if (err) return clbk(err);
  251. var filename = files.shift();
  252. if (filename === null || typeof filename == 'undefined')
  253. return clbk();
  254. var file = srcDir+'/'+filename,
  255. newFile = newDir+'/'+filename;
  256. fs.stat(file, function(err, fileStat){
  257. if (fileStat.isDirectory())
  258. copyDirRecursive(file, newFile, copyFiles);
  259. else if (fileStat.isSymbolicLink())
  260. fs.readlink(file, function(err, link){
  261. fs.symlink(link, newFile, copyFiles);
  262. });
  263. else
  264. fs.readFile(file, function(err, data){
  265. fs.writeFile(newFile, data, copyFiles);
  266. });
  267. });
  268. })();
  269. });
  270. });
  271. });
  272. });
  273. };
  274. var mkdirSyncRecursive = function(path, mode) {
  275. var self = this;
  276. try {
  277. fs.mkdirSync(path, mode);
  278. } catch(err) {
  279. if(err.code == "ENOENT") {
  280. var slashIdx = path.lastIndexOf("/");
  281. if(slashIdx < 0) {
  282. slashIdx = path.lastIndexOf("\\");
  283. }
  284. if(slashIdx > 0) {
  285. var parentPath = path.substring(0, slashIdx);
  286. mkdirSyncRecursive(parentPath, mode);
  287. mkdirSyncRecursive(path, mode);
  288. } else {
  289. throw err;
  290. }
  291. } else if(err.code == "EEXIST") {
  292. return;
  293. } else {
  294. throw err;
  295. }
  296. }
  297. };
  298. exports.mkdirSyncRecursive = mkdirSyncRecursive;
  299. exports.LineReader = function(filename, bufferSize) {
  300. this.bufferSize = bufferSize || 8192;
  301. this.buffer = "";
  302. this.fd = fs.openSync(filename, "r");
  303. this.currentPosition = 0;
  304. };
  305. exports.LineReader.prototype = {
  306. getBufferAndSetCurrentPosition: function(position) {
  307. var res = fs.readSync(this.fd, this.bufferSize, position, "ascii");
  308. this.buffer += res[0];
  309. if(res[1] === 0) {
  310. this.currentPosition = -1;
  311. } else {
  312. this.currentPosition = position + res[1];
  313. }
  314. return this.currentPosition;
  315. },
  316. hasNextLine: function() {
  317. while(this.buffer.indexOf('\n') === -1) {
  318. this.getBufferAndSetCurrentPosition(this.currentPosition);
  319. if(this.currentPosition === -1) return false;
  320. }
  321. if(this.buffer.indexOf("\n") > -1) return true;
  322. return false;
  323. },
  324. getNextLine: function() {
  325. var lineEnd = this.buffer.indexOf("\n"),
  326. result = this.buffer.substring(0, lineEnd);
  327. this.buffer = this.buffer.substring(result.length + 1, this.buffer.length);
  328. return result;
  329. }
  330. };
  331. // vim: et ts=4 sw=4