In many applications in which you work with the file system, you may want to traverse the directory structure in some form or another.
Solution
Traversing the directory structure of your machine with your Node.js application is accomplished by using the fs module. This solution begins where the solution for Section 3-1 left off, in that it starts by reading a directory and then it will move throughout the directory accordingly. The parsing of the directory structure is recursive and results in an array containing the files and directories contained within. This Node.js application is shown in Listing 3-7.
Listing 3-7. Traversing Directories var fs = require('fs'); var out;
var args; /**
* To parse directory structure given a starting point - recursive */
function traverseDirectory(startDir, usePath, callback) {
if (arguments.length === 2 && typeof arguments[1] === 'function') { callback = usePath;
usePath = false; }
//Hold onto the array of items var parsedDirectory = [];
//start reading a list of whats contained fs.readdir(startDir, function(err, dirList) {
ChapTer 3 ■ UsIng The FIle sysTem if (usePath) { startDir = fs.realpathSync(startDir); } if (err) { return callback(err); }
//keep track of how deep we need to go before callback var listlength = dirList.length;
if (!listlength) {
return callback(null, parsedDirectory); }
//loop through the directory list dirList.forEach(function(file) { file = startDir + '/' + file; fs.stat(file, function(err, stat) { //note the directory or file parsedDirectory.push(file); //recursive if this is a directory if (stat && stat.isDirectory()) { //recurse
traverseDirectory(file, function(err, parsed) { // read this directory into our output
parsedDirectory = parsedDirectory.concat(parsed); //check to see if we've exhausted our search if (!--listlength) {
callback(null, parsedDirectory); }
}); } else {
//check to see if we've exhausted the search if (!--listlength) { callback(null, parsedDirectory); } } }); }); }); }
//Normalize the arguments args = process.argv.splice(2); //loop through the directories args.forEach(function(arg) { // use provided path
traverseDirectory(arg, function(err, result) { if (err) {
console.log(err); }
ChapTer 3 ■ UsIng The FIle sysTem
//use full path
traverseDirectory(arg, true, function(err, result) { if (err) { console.log(err); } console.log(result); }); });
This traversal results in a console output that looks something like that shown in Listing 3-8. Listing 3-8. Output of Traversal
gack~/Dropbox/book/code/Ch03: node 3-2-1.js. [ './3-1-1.js', './3-1-2.js', './3-2', './3-2-1.js', './3-2/file.txt', './3-2/sub directory', './3-2/sub directory/file.txt' ] [ '/Users/gack/Dropbox/book/code/Ch03/3-1-1.js', '/Users/gack/Dropbox/book/code/Ch03/3-1-2.js', '/Users/gack/Dropbox/book/code/Ch03/3-2', '/Users/gack/Dropbox/book/code/Ch03/3-2-1.js', '/Users/gack/Dropbox/book/code/Ch03/3-2/file.txt', '/Users/gack/Dropbox/book/code/Ch03/3-2/sub directory', '/Users/gack/Dropbox/book/code/Ch03/3-2/sub directory/file.txt' ]
How It Works
This solution works by first taking a lesson from Section 3-1 and providing the arguments via the command line, which are then normalized. This directs the application which paths to use when beginning the traversal of the directory structure, which is handled in the function traverseDirectory.
The traverseDirectory function accepts a path (or starting directory), an optional flag to translate the starting path to a full path, and a callback function. The optional usePath flag is determined by checking if there are only two parameters passed in and that the second parameter is a function, indicating that the callback was supplied. if (arguments.length === 2 && typeof arguments[1] === 'function') {
callback = usePath; usePath = false; }
The usePath flag is an option that, if set, will then parse the directory provided to the traverseDirectory method using the fs.realpath function. So this would convert a path provided as ‘.’, representing the current working directory, to the actual path for your application (i.e. ‘/home/username/apps/’).
The actual traversal through the directory structure begins with a call to fs.readdir, which, as you saw in Section 3-1, provides a callback with a list of what resides in the directory. Then a check is made on this returned list to ensure that there is information in the directory available to parse. If no result exists, the function exits with
ChapTer 3 ■ UsIng The FIle sysTem
the provided callback. Alternatively, if there are results in the directory listing, you store the length of that array (listlength) to keep track of the remaining items to be parsed from the directory tree.
You then loop through the directory list array, applying the function fs.stat to each item it contains. The fs.stat function returns an fs.stat object detailing information about a file on the file system. The traverseDirectory function then stores the file (or directory) on which the fs.stat object was invoked into the output array
parsedDirectory. The stat object is then checked to see if the result is a directory via the stat.isDirectory function. If the result is true, then the traverseDirectory function is called, passing in that directory—recursively parsing the directory. If the stat is not a directory, the function assumes it is a file, but, regardless if it is a file or not, the directory list length variable is decremented and checked to see if there are any entries remaining, if (!—listlength). In the case where there are not any entries remaining, the function returns the callback passing the parsedDirectory array. The results are passed on to the callback function, which, in this example, logs them to the console.