/fstack

A lightweight driver stack to easily manage and maintain a filesystem.

Primary LanguageJavaScript

fstack

Build Status NPM version Dependency Status

A lightweight and efficient driver stack to easily manage and maintain a filesystem. fstack is the missing filesystem wrangler for Node.js.

fstack provides a simple and optimized interface for accessing a filesystem and deriving logical structures by utilizing the fs core library. fstack features high-level filesystem operations such as asynchronous atomic implementations of copy, move, rm -rf, and mkdir -p. fstack can also derive a filesystem tree object based on a supplied path, providing numerous solutions for filesystem dependent applications. fstack is designed to replace other npm packages such as fs-extra, file, and node-dir. fstack also extends path and os, for convenience.

fstack's implementations of mkdir -p and rm -rf are more efficient than those found in the mkdirp and rimraf packages, respectively.

Read more on why rimraf is a hack.

Installing

npm install --save fstack

Usage

var fstack = require('fstack');

Benchmark (As of 0.2.18)

i in {0..99} `mkdir -p (i)\(i * 2)`
mkdirp 0 s, 9.906 ms
fstack 0 s, 3.743 ms
i in {0..99} `rm -rf i`
rimraf 0 s, 23.996 ms
fstack 0 s, 16.413 ms

Documentation

fstack.ents(path, callback, [stat])

fstack.ents(path, callback, [stat]) will return all of the immediate child entities inside path and callback with (err, ents), where ents is an array containing entity names if stat is not specified, and will callback with (err, ents, stats) if stat is specified.

/*
foo/
    bar/
        baz
    qux
*/

fstack.ents('./foo', function(err, ents) {
    console.log(ents);
});

// ['bar', 'qux']

fstack.dirs(path, callback)

fstack.dirs(path, callback) will return all of the immediate child directories inside path and callback with (err, dirs), where dirs is an associative array containing directory names as keys and stats as values.

/*
foo/
    bar/
        baz
    qux
*/

fstack.dirs('./foo', function(err, dirs) {
    console.log(dirs);
});

// ['bar']

fstack.files(path, callback)

fstack.ents(path, callback) will return all of the immediate child non-directories inside path and callback with (err, files), where files is an associative array containing non-directory names as keys and stats as values.

/*
foo/
    bar/
        baz
    qux
*/

fstack.files('./foo', function(err, files) {
    console.log(files);
});

// ['qux']

fstack.fst(path, callback, [depth])

fstack.fst(path, callback, [depth]) will callback with (err, fst), where fst is an object representing the filesystem with path as the root node. depth is the number of levels to recursively crawl the filesystem, and defaults to null, which does a complete crawl. Calling fstack#fst with a depth of 1 or 2 is a good strategy for returning an object that will represent a filesystem client-side. Directories will contain an object representing its children, while non-directories will contain their device types, which is usually 'file'.

/*
foo/
    bar/
        baz
    quux/
        corge/
            garply
        grault
    qux
*/

fstack.fst('./foo', function(err, fst) {
    console.log(JSON.stringify(fst, null, 4));
});

/*
{
    "qux": "file",
    "bar": {
        "baz": "file"
    },
    "quux": {
        "grault": "file",
        "corge": {
            "garply": "file"
        }
    }
}
*/

fstack.fst('./foo', function(err, fst) {
    console.log(JSON.stringify(fst, null, 4));
}, 1);

/*
{
    "bar": {},
    "quux": {},
    "qux": "file"
}
*/

fstack.fso

Alias of fstack#fst.

fstack.device(path, callback)

fstack.device(path, callback) will check the device type of path and callback with (err, mode), where mode is the determined device type of path.

Device types are file, directory, block-device, char-device, link, fifo, socket, and unknown.

/*
foo/
    bar/
        baz
    qux
*/

fstack.device('./foo', function(err, mode) {
    console.log(mode);
});

// 'directory'

fstack.mkdir(path, callback, [force])

fstack.mkdir(path, callback, [force]) will create the directory path if it does not already exist, and callback with (err). If force is specified, the directory will be created without checks. fstack#mkdir will callback with (err, made).

/*
foo/
    bar/
        baz
    qux
*/

fstack.mkdir('./foo/quux', function(err) {
    
});

/*
foo/
    bar/
        baz
    quux/
    qux
*/

fstack.mkdirp(path, callback, [force])

fstack.mkdirp(path, callback, [force]) will recursively create each directory that leads to path if any do not already exist, and callback with (err). If force is specified, directories will be created without checks. fstack#mkdirp will callback with (err).

/*
foo/
    bar/
        baz
    qux
*/

fstack.mkdirp('./foo/quux/corge', function(err) {
    
});

/*
foo/
    bar/
        baz
    quux/
        corge/
    qux
*/

fstack.copy(source, destination, callback)

fstack.copy(source, destination, callback) will recursively copy each directory and file from source to destination if source is a directory, and it will copy source to directory if source is a file. fstack#copy will create any missing directories in the process. Files in destination with the same name as files in source will be overwritten. fstack#copy will callback with (err).

/*
foo/
    bar/
        baz
    qux
*/

fstack.copy('./foo', './quux', function(err) {
    
});

/*
foo/
    bar/
        baz
    qux
quux/
    bar/
        baz
    qux
*/

fstack.move(source, destination, callback)

fstack.move(source, destination, callback) will recursively move each directory and file from source to destination if source is a directory, using fs#rename when possible. It will move source to directory if source is a file. fstack#move will create any missing directories in the process. Files in destination with the same name as files in source will be overwritten. fstack#move will callback with (err).

/*
foo/
    bar/
        baz
    qux
*/

fstack.move('./foo', './quux', function(err) {
    
});

/*
quux/
    bar/
        baz
    qux
*/

fstack.delete(path, callback)

fstack.delete(path, callback) will recursively delete each directory and file in path, if path is a directory, while it will delete path if it is a non-directory. fstack#delete will callback with (err).

/*
foo/
    bar/
        baz
    qux
*/

fstack.delete('./foo', function(err) {
    
});

/*

*/

fstack.checkFile(path, callback)

fstack.checkFile(path, callback) will check if path exists as a non-directory and callback with (err, stat), where stat is the stat of path.

fstack.checkDir(path, callback)

fstack.checkDir(path, callback) will check if path exists as a directory and callback with (err, stat), where stat is the stat of path.

fstack.json(path, callback, [explicit])

fstack.json(path, callback, [explicit]) will read the json from path if it is a file. explicit defaults to false, where it may append '.json' to the supplied path so that the file may be accessed without requiring the '.json' extension. fstack#json(path, callback, explicit) will callback with (err, json), where json is the parsed object for the file at path.

/*
test.json
    {
        "a": "b",
        "c": {
            "d": "e"
        }
    }
*/

fstack.json('./test.json', function(err, json) {
    console.log(json.c);
});

// { d: 'e' }

fstack.read(path, callback)

fstack#read behaves the same way as fs#readFile, but first performs a check to see whether the supplied file exists.

fstack.readStream(path, callback)

fstack.readStream(path, callback) will create a Readable Stream for path and callback with (err, stream).

fstack.write(path, callback)

fstack#write behaves the same way as fs#writeFile, but first performs a check to see whether the supplied file exists.

fstack.writeStream(path, callback)

fstack.writeStream(path, callback) will create a Writable Stream for path and callback with (err, stream).

fstack.append(path, callback)

fstack#append behaves the same way as fs#append, but first performs a check to see whether the supplied file exists.

rimraf

The package rimraf, which is a popular implementation of rm -rf for Node.js, actually depends on making assumptions about files and directories during the removal of entities. rimraf must choose between assuming that there is a file and assuming that there is a directory, and makes the smarter decision favoring the existence files, but this also means that for any call to rimraf where the path given contains at least one child directory, the kernel will return an error, and a new deletion request must be made. Such system calls are expensive. Instead, fstack uses its built-in entity separator to asynchronously unlink files and rmdir directories, after the children of those directories have been unlink'd or rmdir'd.