├── .gitignore ├── package.json ├── LICENSE ├── README.md ├── tests └── file_spec.js └── lib └── file.js /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | node_modules 3 | npm-debug.log 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { "name" : "file" 2 | , "description" : "Higher level path and file manipulation functions." 3 | , "tags" : ["file", "path", "fs", "walk"] 4 | , "version" : "0.2.2" 5 | , "author" : "Anders Conbere " 6 | , "directories" : 7 | { "lib" : "lib" } 8 | , "repository" : 9 | { "type" : "git" 10 | , "url" : "http://github.com/aconbere/node-file-utils.git" 11 | } 12 | , "bugs" : 13 | { "url" : "http://github.com/aconbere/node-file-utils" } 14 | , "main" : "./lib/file" 15 | , "license": "MIT" 16 | , "devDependencies": { "mocha": "1.9.x" } 17 | } 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2012 Anders Conbere 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # File - Common higher level file and path operations 2 | 3 | ## Install 4 | 5 |
 6 |   npm install file
 7 | 
8 | 9 |
10 |   var file = require("file");
11 | 
12 | 13 | ## API 14 | 15 | ### file.walk(start, callback) 16 | 17 | Navigates a file tree, calling callback for each directory, passing in (null, dirPath, dirs, files). 18 | 19 | 20 | ### file.walkSync(start, callback) 21 | 22 | Synchronus version of file.walk, calling callback for each directory, passing in (dirPath, dirs, files). 23 | 24 | 25 | ### file.mkdirs(path, mode, callback) 26 | 27 | Makes all the directories in a path. (analgous to mkdir -P) For example given a path like "test/this/path" in an empty directory, mkdirs would make the directories "test", "this" and "path". 28 | 29 | 30 | ### file.mkdirsSync(path, mode) 31 | 32 | Like file.mkdirs but synchronous. 33 | 34 | 35 | ### file.path.abspath(path) 36 | 37 | Expands ".", "..", "~" and non root paths to their full absolute path. Relative paths default to being children of the current working directory. 38 | 39 | 40 | ### file.path.relativePath(root, fullPath) 41 | 42 | Given a root path, and a fullPath attempts to diff between the two to give us an acurate path relative to root. 43 | 44 | 45 | ### file.path.join(head, tail) 46 | 47 | Just like path.join but haves a little more sanely when give a head equal to "". file.path.join("", "tail") returns "tail", path.join("", "tail") returns "/tail" 48 | -------------------------------------------------------------------------------- /tests/file_spec.js: -------------------------------------------------------------------------------- 1 | var assert = require("assert"); 2 | var util = require("util"); 3 | var mocha = require("mocha"); 4 | var file = require("../lib/file"); 5 | var fs = require("fs"); 6 | var path = require("path"); 7 | 8 | var madeDirs = []; 9 | fs.mkdir = function (dir, mode, callback) { 10 | madeDirs.push(dir); 11 | callback(); 12 | }; 13 | 14 | fs.mkdirSync = function (dir, mode) { 15 | madeDirs.push(dir); 16 | }; 17 | 18 | global.fs = fs; 19 | 20 | describe("file#mkdirs", function () { 21 | beforeEach(function (done) { 22 | madeDirs = []; 23 | done(); 24 | }); 25 | 26 | it("should make all the directories in the tree", function (done) { 27 | file.mkdirs("/test/test/test/test", 0755, function(err) { 28 | if (err) throw new Error(err); 29 | assert.equal(madeDirs[0], "/test"); 30 | assert.equal(madeDirs[1], "/test/test"); 31 | assert.equal(madeDirs[2], "/test/test/test"); 32 | assert.equal(madeDirs[3], "/test/test/test/test"); 33 | done(); 34 | }); 35 | }); 36 | }); 37 | 38 | describe("file#mkdirsSync", function () { 39 | beforeEach(function (done) { 40 | madeDirs = []; 41 | done(); 42 | }); 43 | 44 | it("should make all the directories in the tree", function (done) { 45 | file.mkdirsSync("/test/test/test/test", 0755, function(err) { 46 | if (err) throw new Error(err); 47 | }); 48 | assert.equal(madeDirs[0], "/test"); 49 | assert.equal(madeDirs[1], "/test/test"); 50 | assert.equal(madeDirs[2], "/test/test/test"); 51 | assert.equal(madeDirs[3], "/test/test/test/test"); 52 | done(); 53 | }); 54 | }); 55 | 56 | // TODO: File walk tests are obviously not really working 57 | describe("file#walk", function () { 58 | it("should call \"callback\" for ever file in the tree", function (done) { 59 | file.walk("./tests", function(start, dirs, names) {}); 60 | done(); 61 | }); 62 | }); 63 | 64 | describe("file#walkSync", function () { 65 | it("should call \"callback\" for ever file in the tree", function (done) { 66 | file.walkSync("./tests", function(start, dirs, names) {}); 67 | done(); 68 | }); 69 | }); 70 | 71 | describe("file.path#abspath", function () { 72 | it("should convert . to the current directory", function (done) { 73 | assert.equal(file.path.abspath("."), process.cwd()); 74 | assert.equal(file.path.abspath("./test/dir"), file.path.join(process.cwd(), "test/dir")); 75 | done(); 76 | }); 77 | 78 | it("should convert .. to the parrent directory", function (done) { 79 | assert.equal(file.path.abspath(".."), path.dirname(process.cwd())); 80 | assert.equal(file.path.abspath("../test/dir"), file.path.join(path.dirname(process.cwd()), "test/dir")); 81 | done(); 82 | }); 83 | 84 | it("should convert ~ to the home directory", function (done) { 85 | assert.equal(file.path.abspath("~"), file.path.join(process.env.HOME, "")); 86 | assert.equal(file.path.abspath("~/test/dir"), file.path.join(process.env.HOME, "test/dir")); 87 | done(); 88 | }); 89 | 90 | it("should not convert paths begining with /", function (done) { 91 | assert.equal(file.path.abspath("/x/y/z"), "/x/y/z"); 92 | done(); 93 | }); 94 | }); 95 | 96 | 97 | describe("file.path#relativePath", function () { 98 | it("should return the relative path", function (done) { 99 | var rel = file.path.relativePath("/", "/test.js"); 100 | assert.equal(rel, "test.js"); 101 | 102 | var rel = file.path.relativePath("/test/loc", "/test/loc/test.js"); 103 | assert.equal(rel, "test.js"); 104 | 105 | done(); 106 | }); 107 | 108 | it("should take two equal paths and return \"\"", function (done) { 109 | var rel = file.path.relativePath("/test.js", "/test.js"); 110 | assert.equal(rel, ""); 111 | done(); 112 | }); 113 | }); 114 | -------------------------------------------------------------------------------- /lib/file.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var fs = require('fs'); 3 | var assert = require("assert"); 4 | 5 | // file.mkdirs 6 | // 7 | // Given a path to a directory, create it, and all the intermediate directories 8 | // as well 9 | // 10 | // @path: the path to create 11 | // @mode: the file mode to create the directory with: 12 | // ex: file.mkdirs("/tmp/dir", 755, function () {}) 13 | // @callback: called when finished. 14 | exports.mkdirs = function (_path, mode, callback) { 15 | _path = exports.path.abspath(_path); 16 | 17 | var dirs = _path.split(path.sep); 18 | var walker = [dirs.shift()]; 19 | 20 | // walk 21 | // @ds: A list of directory names 22 | // @acc: An accumulator of walked dirs 23 | // @m: The mode 24 | // @cb: The callback 25 | var walk = function (ds, acc, m, cb) { 26 | if (ds.length > 0) { 27 | var d = ds.shift(); 28 | 29 | acc.push(d); 30 | var dir = acc.join(path.sep); 31 | 32 | // look for dir on the fs, if it doesn't exist then create it, and 33 | // continue our walk, otherwise if it's a file, we have a name 34 | // collision, so exit. 35 | fs.stat(dir, function (err, stat) { 36 | // if the directory doesn't exist then create it 37 | if (err) { 38 | // 2 means it's wasn't there 39 | if (err.errno == 2 || err.errno == 34) { 40 | fs.mkdir(dir, m, function (erro) { 41 | if (erro && erro.errno != 17 && erro.errno != 34) { 42 | return cb(erro); 43 | } else { 44 | return walk(ds, acc, m, cb); 45 | } 46 | }); 47 | } else { 48 | return cb(err); 49 | } 50 | } else { 51 | if (stat.isDirectory()) { 52 | return walk(ds, acc, m, cb); 53 | } else { 54 | return cb(new Error("Failed to mkdir " + dir + ": File exists\n")); 55 | } 56 | } 57 | }); 58 | } else { 59 | return cb(); 60 | } 61 | }; 62 | return walk(dirs, walker, mode, callback); 63 | }; 64 | 65 | // file.mkdirsSync 66 | // 67 | // Synchronus version of file.mkdirs 68 | // 69 | // Given a path to a directory, create it, and all the intermediate directories 70 | // as well 71 | // 72 | // @path: the path to create 73 | // @mode: the file mode to create the directory with: 74 | // ex: file.mkdirs("/tmp/dir", 755, function () {}) 75 | exports.mkdirsSync = function (_path, mode) { 76 | if (_path[0] !== path.sep) { 77 | _path = path.join(process.cwd(), _path) 78 | } 79 | 80 | var dirs = _path.split(path.sep); 81 | var walker = [dirs.shift()]; 82 | 83 | dirs.reduce(function (acc, d) { 84 | acc.push(d); 85 | var dir = acc.join(path.sep); 86 | 87 | try { 88 | var stat = fs.statSync(dir); 89 | if (!stat.isDirectory()) { 90 | throw "Failed to mkdir " + dir + ": File exists"; 91 | } 92 | } catch (err) { 93 | fs.mkdirSync(dir, mode); 94 | } 95 | return acc; 96 | }, walker); 97 | }; 98 | 99 | // file.walk 100 | // 101 | // Given a path to a directory, walk the fs below that directory 102 | // 103 | // @start: the path to startat 104 | // @callback: called for each new directory we enter 105 | // ex: file.walk("/tmp", function(error, path, dirs, name) {}) 106 | // 107 | // path is the current directory we're in 108 | // dirs is the list of directories below it 109 | // names is the list of files in it 110 | // 111 | exports.walk = function (start, callback) { 112 | fs.lstat(start, function (err, stat) { 113 | if (err) { return callback(err) } 114 | if (stat.isDirectory()) { 115 | 116 | fs.readdir(start, function (err, files) { 117 | var coll = files.reduce(function (acc, i) { 118 | var abspath = path.join(start, i); 119 | 120 | if (fs.statSync(abspath).isDirectory()) { 121 | exports.walk(abspath, callback); 122 | acc.dirs.push(abspath); 123 | } else { 124 | acc.names.push(abspath); 125 | } 126 | 127 | return acc; 128 | }, {"names": [], "dirs": []}); 129 | 130 | return callback(null, start, coll.dirs, coll.names); 131 | }); 132 | } else { 133 | return callback(new Error("path: " + start + " is not a directory")); 134 | } 135 | }); 136 | }; 137 | 138 | // file.walkSync 139 | // 140 | // Synchronus version of file.walk 141 | // 142 | // Given a path to a directory, walk the fs below that directory 143 | // 144 | // @start: the path to startat 145 | // @callback: called for each new directory we enter 146 | // ex: file.walk("/tmp", function(error, path, dirs, name) {}) 147 | // 148 | // path is the current directory we're in 149 | // dirs is the list of directories below it 150 | // names is the list of files in it 151 | // 152 | exports.walkSync = function (start, callback) { 153 | var stat = fs.statSync(start); 154 | 155 | if (stat.isDirectory()) { 156 | var filenames = fs.readdirSync(start); 157 | 158 | var coll = filenames.reduce(function (acc, name) { 159 | var abspath = path.join(start, name); 160 | 161 | if (fs.statSync(abspath).isDirectory()) { 162 | acc.dirs.push(name); 163 | } else { 164 | acc.names.push(name); 165 | } 166 | 167 | return acc; 168 | }, {"names": [], "dirs": []}); 169 | 170 | callback(start, coll.dirs, coll.names); 171 | 172 | coll.dirs.forEach(function (d) { 173 | var abspath = path.join(start, d); 174 | exports.walkSync(abspath, callback); 175 | }); 176 | 177 | } else { 178 | throw new Error("path: " + start + " is not a directory"); 179 | } 180 | }; 181 | 182 | exports.path = {}; 183 | 184 | exports.path.abspath = function (to) { 185 | var from; 186 | switch (to.charAt(0)) { 187 | case "~": from = process.env.HOME; to = to.substr(1); break 188 | case path.sep: from = ""; break 189 | default : from = process.cwd(); break 190 | } 191 | return path.join(from, to); 192 | } 193 | 194 | exports.path.relativePath = function (base, compare) { 195 | base = base.split(path.sep); 196 | compare = compare.split(path.sep); 197 | 198 | if (base[0] == "") { 199 | base.shift(); 200 | } 201 | 202 | if (compare[0] == "") { 203 | compare.shift(); 204 | } 205 | 206 | var l = compare.length; 207 | 208 | for (var i = 0; i < l; i++) { 209 | if (!base[i] || (base[i] != compare[i])) { 210 | return compare.slice(i).join(path.sep); 211 | } 212 | } 213 | 214 | return "" 215 | }; 216 | 217 | exports.path.join = function (head, tail) { 218 | if (head == "") { 219 | return tail; 220 | } else { 221 | return path.join(head, tail); 222 | } 223 | }; 224 | 225 | --------------------------------------------------------------------------------