Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save rsms/319051 to your computer and use it in GitHub Desktop.
Save rsms/319051 to your computer and use it in GitHub Desktop.
From c7c0b49be9e272aa6421ed26f7b03d3c465d2313 Mon Sep 17 00:00:00 2001
From: Rasmus Andersson <rasmus@notion.se>
Date: Tue, 2 Mar 2010 03:13:41 +0100
Subject: [PATCH] Added fs.mkdirs and fs.mkdirsSync for recursively creating directories as needed
---
doc/api.txt | 10 +++++
lib/fs.js | 82 +++++++++++++++++++++++++++++++++++++++++
test/simple/test-fs-mkdirs.js | 47 +++++++++++++++++++++++
3 files changed, 139 insertions(+), 0 deletions(-)
create mode 100644 test/simple/test-fs-mkdirs.js
diff --git a/doc/api.txt b/doc/api.txt
index 071b88a..50baa79 100644
--- a/doc/api.txt
+++ b/doc/api.txt
@@ -646,6 +646,16 @@ No arguments other than a possible exception are given to the completion callbac
Synchronous mkdir(2).
++fs.mkdirs(path, [mode=(0777^umask)], [callback])+ ::
+Asynchronous recursive mkdir(2).
+The callback gets two arguments +(err, directories)+ where +directories+ is an
+array of the names of the directories which was created.
+
++fs.mkdirsSync(path, [mode=(0777^umask)])+ ::
+Synchronous vesion of +fs.mkdirs+.
+Returns a list with the names of the directories which was created.
+
+
+fs.readdir(path, callback)+ ::
Asynchronous readdir(3). Reads the contents of a directory.
The callback gets two arguments +(err, files)+ where +files+ is an array of
diff --git a/lib/fs.js b/lib/fs.js
index 0bc68a5..cfdf88d 100644
--- a/lib/fs.js
+++ b/lib/fs.js
@@ -1,3 +1,5 @@
+var path = require('path');
+
exports.Stats = process.Stats;
process.Stats.prototype._checkModeProperty = function (property) {
@@ -289,4 +291,84 @@ exports.unwatchFile = function (filename) {
}
};
+// Recursive mkdir
+
+// mkdirsSync(path, [mode=(0777^umask)]) -> pathsCreated
+exports.mkdirsSync = function (dirname, mode) {
+ if (mode === undefined) mode = 0777 ^ process.umask();
+ var pathsCreated = [], pathsFound = [];
+ var fn = dirname;
+ while (true) {
+ try {
+ var stats = exports.statSync(fn);
+ if (stats.isDirectory())
+ break;
+ throw new Error('Unable to create directory at '+fn);
+ }
+ catch (e) {
+ if (e.errno === process.ENOENT) {
+ pathsFound.push(fn);
+ fn = path.dirname(fn);
+ }
+ else {
+ throw e;
+ }
+ }
+ }
+ for (var i=pathsFound.length-1; i>-1; i--) {
+ var fn = pathsFound[i];
+ exports.mkdirSync(fn, mode);
+ pathsCreated.push(fn);
+ }
+ return pathsCreated;
+};
+// mkdirs(path, [mode=(0777^umask)], [callback(err, pathsCreated)])
+exports.mkdirs = function (dirname, mode, callback) {
+ if (typeof mode === 'function') {
+ callback = mode;
+ mode = undefined;
+ }
+ if (mode === undefined) mode = 0777 ^ process.umask();
+ var pathsCreated = [], pathsFound = [];
+ var makeNext = function() {
+ var fn = pathsFound.pop();
+ if (!fn) {
+ if (callback) callback(null, pathsCreated);
+ }
+ else {
+ exports.mkdir(fn, mode, function(err) {
+ if (!err) {
+ pathsCreated.push(fn);
+ makeNext();
+ }
+ else if (callback) {
+ callback(err);
+ }
+ });
+ }
+ }
+ var findNext = function(fn){
+ exports.stat(fn, function(err, stats) {
+ if (err) {
+ if (err.errno === process.ENOENT) {
+ pathsFound.push(fn);
+ findNext(path.dirname(fn));
+ }
+ else if (callback) {
+ callback(err);
+ }
+ }
+ else if (stats.isDirectory()) {
+ // create all dirs we found up to this dir
+ makeNext();
+ }
+ else {
+ if (callback) {
+ callback(new Error('Unable to create directory at '+fn));
+ }
+ }
+ });
+ }
+ findNext(dirname);
+};
diff --git a/test/simple/test-fs-mkdirs.js b/test/simple/test-fs-mkdirs.js
new file mode 100644
index 0000000..e96f67c
--- /dev/null
+++ b/test/simple/test-fs-mkdirs.js
@@ -0,0 +1,47 @@
+process.mixin(require("../common"));
+
+var dirname = path.dirname(__filename);
+var fixtures = path.join(dirname, "../fixtures");
+var dbase = path.join(fixtures, "mkdirs");
+var d = path.join(dbase, "foo/bar/deep");
+var mkdirs_error = false;
+
+function rmrf(fn, cb) {
+ return exec("rm -rf '"+fn.replace("'","\\'")+"'", function(err){
+ if (err) puts("rm -rf error: " + err.message);
+ cb(err);
+ });
+}
+
+function testAsync() {
+ fs.mkdirs(d, function (err) {
+ if (err) {
+ puts("mkdirs error: " + err.message);
+ mkdir_errors = err;
+ } else {
+ rmrf(dbase, function(err) {
+ if (err) rmdir_error = err;
+ });
+ }
+ });
+}
+
+function testSync() {
+ fs.mkdirsSync(d);
+}
+
+// rm -rf dbase
+rmrf(dbase, function(err) {
+// mkdir -p d
+ testSync();
+ // rm -rf dbase
+ rmrf(dbase, function(err) {
+ // mkdir -p d & # implies rm -rf dbase
+ testAsync();
+ });
+});
+
+process.addListener("exit", function () {
+ assert.equal(mkdirs_error, false);
+ puts("fs.mkdirs test OK");
+});
--
1.6.6
@kennyb
Copy link

kennyb commented Mar 6, 2011

two modifications need to be done to this though...

  1. node is beginning to use strict mode so 0777 won't work... it can be 0x1ff though... you also won't be able to use ^ either, as it's passed in as a string
  2. it will always fail because process.ENOENT does not exist... should read: err.code === 'ENOENT'

@rsms
Copy link
Author

rsms commented Mar 6, 2011

Thanks. This code is more than a year old — it was made for a much older Node.js than we have today. Maybe we should consider this fore node-core again...

@lapo-luchini
Copy link

In a project of mine I used the following simplified form:

function mkdirsSync(pathname, mode) {
    try {
    if (!fs.statSync(pathname).isDirectory())
        throw new Error('Unable to create directory at: ' + pathname);
    } catch (e) {
        if (e.code === 'ENOENT') {
            mkdirsSync(path.dirname(pathname));
            fs.mkdirSync(pathname, mode);
    } else
            throw e;
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment