You want to access the structure of a directory, or a set of directories, from within your Node.js application.
Solution
To get a grasp of the Node.js utilities for retrieving a directory structure, you must first require the file-system module by using require('fs') in your code. You then want to get some information about the directory that you are
ChapTer 3 ■ UsIng The FIle sysTem
targeting. Let us assume you want to print all the information pertaining to the current directory from your Node.js application. First, you can target the current directory, the directory from which the Node.js script is executed, as shown in Listing 3-1.
Listing 3-1. Target the Current Directory for Node.js var fs = require('fs');
var out;
console.log(__dirname);
//read current directory asynchronously
fs.realpath(__dirname, function(err, /* [cache], */ path) { if (err) {
console.log(err); return;
}
console.log('realpath async: ' + path); });
out = fs.realpathSync(__dirname); console.log('real path sync: ' + out); fs.stat(__dirname, function(err, stat) { if (err) return;
var isDir = false;
fs.readdir(__dirname, function(err, contents) { if (err) return; contents.forEach(function(f) { console.log('contents: ' + f); }); }); });
//get list of what’s in the directory out = fs.readdirSync(__dirname); console.log('readdir sync: ' + out);
What results does this solution present? It produces a list, based in the current working directory, of what is contained within that directory. This list looks like what follows in Listing 3-2.
Listing 3-2. Output from Listing 3-1 $ node 3-1-1.js
/home/cgack/Dropbox/book/code/Ch03
real path sync: /home/cgack/Dropbox/book/code/Ch03 readdir sync: 3-1-1.js,3-1-2.js
contents: 3-1-1.js contents: 3-1-2.js
ChapTer 3 ■ UsIng The FIle sysTem
While that solution is effective for running your code in an attempt to get the directory structure of where your application is instantiated, it makes it difficult to parse the structure of arbitrary directories and directories relative to where you invoke your Node.js script. This can be solved by refactoring Listing 3-1 slightly to allow for a command-line parameter as shown in Listing 3-3.
Listing 3-3. Refactoring the Directory Digest var fs = require('fs');
var out; var args;
//Normalize the arguments args = process.argv.splice(2); args.forEach(function(arg) { console.log(arg);
//read current directory asynchronous
fs.realpath(arg, function(err, /* [cache], */ path) { if (err) {
console.log(err); return;
}
console.log('realpath async: ' + path); });
out = fs.realpathSync(arg);
console.log('real path sync: ' + out); fs.stat(arg, function(err, stat) { if (err) return;
fs.readdir(arg, function(err, contents) { if (err) return; contents.forEach(function(f) { console.log('contents: ' + f); }); }); });
//get list of what’s in the directory out = fs.readdirSync(arg);
console.log('readdir sync: ' + out); });
You can see that this solution offers a little more. It takes a list of arguments, normalizes them, and then loops through the directories provided, producing an output. This means that you can pass two relative paths to your Node. js application, and it will loop through and produce a result similar to the output in Listing 3-4.
ChapTer 3 ■ UsIng The FIle sysTem
Listing 3-4. Multiple Paths Output $ node 3-1-2.js ...
.
real path sync: /home/cgack/Dropbox/book/code/Ch03 readdir sync: 3-1-1.js,3-1-2.js
..
real path sync: /home/cgack/Dropbox/book/code readdir sync: 2-6-2.js,Ch01,Ch02,Ch03 contents: 3-1-1.js contents: 3-1-2.js contents: 2-6-2.js contents: Ch01 contents: Ch02 contents: Ch03
realpath async: /home/cgack/Dropbox/book/code
How It Works
Now you examine how all of this works. You see that, in general, the difference between calling on a static, hard-coded directory and allowing a command-line argument to be passed into your command is the added benefit of flexibility. Let’s start with the code that reads the directory information, and then you can examine the differences in
implementation.
The Node.js file-system module presents a plethora of useful wrappers around the standard POSIX commands, which are semiubiquitous across platforms (some operating systems vary in implementation). The commands that are utilized in this section are readdir, stat, and realpath.
The Node.js implementation of readdir is a simple command to read a directory. You will note, however, that there are two separate calls of the readdir in the solution. One is to readdir ()and the other to readdirSync (). The readdirSync is the synchronous implementation of the file-system directory read, implemented as shown in Listing 3-5. Listing 3-5. readdirSync fs.readdirSync = function(path) { nullCheck(path); return binding.readdir(pathModule._makeLong(path)); };
This simply checks if the path exists, then returns that path. The alternative version of this call is asynchronous (Listing 3-6) and accepts a callback, as you might expect. The callback accepts two arguments: an error parameter and another that holds the information of the path.
Listing 3-6. readdir ()
fs.readdir = function(path, callback) { callback = makeCallback(callback); if (!nullCheck(path, callback)) return;
binding.readdir(pathModule._makeLong(path), callback); };
ChapTer 3 ■ UsIng The FIle sysTem
Similar to readdir, the Node.js function named realpath has both synchronous and asynchronous forms. The realpath function returns the absolute pathname for the provided path. So this is essentially two ways to gather information of the current directory. The function realpath retrieves the absolute path, and readdir retrieves information about its contents. Readdir can only retrieve a list of files or directories that are present in a directory, so in order to find out more details about a directory you need something different. This different method is stat (). stat() will gather all the information it can about the named file and you will see it used in more detail in subsequent sections, but in this case it is used as an entry point before you readdir on a path.
Now what about the two different versions of the example provided? One was based on a static path, which was in fact a Node.js global variable called __dirname. The __dirname variable is relative to each module, and it represents the path to the Node.js script that is currently executing. So when you use this in Listing 3-1, you are telling Node.js, and the file-system modules you invoke, to utilize the path to the Node.js module as the path argument for each file-system invocation.
This is rather limiting, so you see that in Listing 3-3 the module was opened up to utilize a set of command-line arguments that are passed to the module Node.js. This is utilizing the global process object that contains a list of the arguments in the argv element. In the listing, you see that these arguments are normalized to remove the first two parameters—'node <file>'—and then parse the rest of the arguments as an array. This array is then utilized as the path arguments for each of the Node.js file-system methods, giving much more versatility to the initial implementation of the directory information retrieval code.