Skip to content

Commit

Permalink
fs: readdir optionally returning type information
Browse files Browse the repository at this point in the history
readdir and readdirSync now have a "withFileTypes" option, which, when
enabled, provides an array of DirectoryEntry objects, similar to Stats
objects, which have the filename and the type information.

Ref: nodejs#15699
  • Loading branch information
bengl committed Aug 13, 2018
1 parent 78584b6 commit 9b7b4d8
Show file tree
Hide file tree
Showing 7 changed files with 276 additions and 17 deletions.
96 changes: 94 additions & 2 deletions doc/api/fs.md
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,87 @@ synchronous use libuv's threadpool, which can have surprising and negative
performance implications for some applications. See the
[`UV_THREADPOOL_SIZE`][] documentation for more information.

## Class: fs.DirectoryEntry
<!-- YAML
added: REPLACEME
-->

When [`fs.readdir()`][] or [`fs.readdirSync()`][] is called with the `withTypes`
option set to `true`, the resulting array is filled with `fs.DirectoryEntry`
objects, rather than strings or `Buffers`.

### dirent.name

* {string|Buffer}

The file name that this `fs.DirectoryEntry` object refers to. The type of this
value is determined by the `options.encoding` passed to [`fs.readdir()`][] or
[`fs.readdirSync()`][].

### dirent.isBlockDevice()
<!-- YAML
added: REPLACEME
-->

* Returns: {boolean}

Returns `true` if the `fs.DirectoryEntry` object describes a block device.

### dirent.isCharacterDevice()
<!-- YAML
added: REPLACEME
-->

* Returns: {boolean}

Returns `true` if the `fs.DirectoryEntry` object describes a character device.

### dirent.isDirectory()
<!-- YAML
added: REPLACEME
-->

* Returns: {boolean}

Returns `true` if the `fs.DirectoryEntry` object describes a file system directory.

### dirent.isFIFO()
<!-- YAML
added: REPLACEME
-->

* Returns: {boolean}

Returns `true` if the `fs.DirectoryEntry` object describes a first-in-first-out (FIFO)
pipe.

### dirent.isFile()
<!-- YAML
added: REPLACEME
-->

* Returns: {boolean}

Returns `true` if the `fs.DirectoryEntry` object describes a regular file.

### dirent.isSocket()
<!-- YAML
added: REPLACEME
-->

* Returns: {boolean}

Returns `true` if the `fs.DirectoryEntry` object describes a socket.

### dirent.isSymbolicLink()
<!-- YAML
added: REPLACEME
-->

* Returns: {boolean}

Returns `true` if the `fs.DirectoryEntry` object describes a symbolic link.

## Class: fs.FSWatcher
<!-- YAML
added: v0.5.8
Expand Down Expand Up @@ -2319,9 +2400,10 @@ changes:
* `path` {string|Buffer|URL}
* `options` {string|Object}
* `encoding` {string} **Default:** `'utf8'`
* `withTypes` {boolean} **Default:** `false`
* `callback` {Function}
* `err` {Error}
* `files` {string[]|Buffer[]}
* `files` {string[]|Buffer[]|fs.DirectoryEntry[]}

Asynchronous readdir(3). Reads the contents of a directory.
The callback gets two arguments `(err, files)` where `files` is an array of
Expand All @@ -2332,6 +2414,9 @@ object with an `encoding` property specifying the character encoding to use for
the filenames passed to the callback. If the `encoding` is set to `'buffer'`,
the filenames returned will be passed as `Buffer` objects.

If `options.withTypes` is set to `true`, the `files` array will contain
[`fs.DirectoryEntry`][] objects.

## fs.readdirSync(path[, options])
<!-- YAML
added: v0.1.21
Expand All @@ -2345,7 +2430,8 @@ changes:
* `path` {string|Buffer|URL}
* `options` {string|Object}
* `encoding` {string} **Default:** `'utf8'`
* Returns: {string[]} An array of filenames excluding `'.'` and `'..'`.
* `withTypes` {boolean} **Default:** `false`
* Returns: {string[]|Buffer[]|fs.DirectoryEntry[]}

Synchronous readdir(3).

Expand All @@ -2354,6 +2440,9 @@ object with an `encoding` property specifying the character encoding to use for
the filenames returned. If the `encoding` is set to `'buffer'`,
the filenames returned will be passed as `Buffer` objects.

If `options.withTypes` is set to `true`, the result will contain
[`fs.DirectoryEntry`][] objects.

## fs.readFile(path[, options], callback)
<!-- YAML
added: v0.1.29
Expand Down Expand Up @@ -4637,6 +4726,7 @@ the file contents.
[`WriteStream`]: #fs_class_fs_writestream
[`EventEmitter`]: events.html
[`event ports`]: http://illumos.org/man/port_create
[`fs.DirectoryEntry`]: #fs_class_fs_directoryentry
[`fs.FSWatcher`]: #fs_class_fs_fswatcher
[`fs.Stats`]: #fs_class_fs_stats
[`fs.access()`]: #fs_fs_access_path_mode_callback
Expand All @@ -4652,6 +4742,8 @@ the file contents.
[`fs.mkdtemp()`]: #fs_fs_mkdtemp_prefix_options_callback
[`fs.open()`]: #fs_fs_open_path_flags_mode_callback
[`fs.read()`]: #fs_fs_read_fd_buffer_offset_length_position_callback
[`fs.readdir()`]: #fs_fs_readdir_path_options_callback
[`fs.readdirSync()`]: #fs_fs_readdirsync_path_options
[`fs.readFile()`]: #fs_fs_readfile_path_options_callback
[`fs.readFileSync()`]: #fs_fs_readfilesync_path_options
[`fs.realpath()`]: #fs_fs_realpath_path_options_callback
Expand Down
36 changes: 32 additions & 4 deletions lib/fs.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ const { getPathFromURL } = require('internal/url');
const internalUtil = require('internal/util');
const {
copyObject,
DirectoryEntry,
getOptions,
nullCheck,
preprocessSymlinkDestination,
Expand Down Expand Up @@ -773,8 +774,24 @@ function readdir(path, options, callback) {
validatePath(path);

const req = new FSReqCallback();
req.oncomplete = callback;
binding.readdir(pathModule.toNamespacedPath(path), options.encoding, req);
if (!options.withTypes) {
req.oncomplete = callback;
} else {
req.oncomplete = (err, result) => {
if (err) {
callback(err);
return;
}
const len = result.length;
const newResult = new Array(len / 2);
for (var i = 0; i < len; i += 2) {
newResult[i / 2] = new DirectoryEntry(result[i], result[i + 1]);
}
callback(null, newResult);
};
}
binding.readdir(pathModule.toNamespacedPath(path), options.encoding,
!!options.withTypes, req);
}

function readdirSync(path, options) {
Expand All @@ -783,9 +800,19 @@ function readdirSync(path, options) {
validatePath(path);
const ctx = { path };
const result = binding.readdir(pathModule.toNamespacedPath(path),
options.encoding, undefined, ctx);
options.encoding, !!options.withTypes,
undefined, ctx);
handleErrorFromBinding(ctx);
return result;
if (!options.withTypes) {
return result;
} else {
const len = result.length;
const newResult = new Array(len / 2);
for (var i = 0; i < len; i += 2) {
newResult[i / 2] = new DirectoryEntry(result[i], result[i + 1]);
}
return newResult;
}
}

function fstat(fd, options, callback) {
Expand Down Expand Up @@ -1819,6 +1846,7 @@ module.exports = fs = {
writeFileSync,
write,
writeSync,
DirectoryEntry,
Stats,

get ReadStream() {
Expand Down
2 changes: 1 addition & 1 deletion lib/internal/fs/promises.js
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ async function readdir(path, options) {
path = getPathFromURL(path);
validatePath(path);
return binding.readdir(pathModule.toNamespacedPath(path),
options.encoding, kUsePromises);
options.encoding, !!options.withTypes, kUsePromises);
}

async function readlink(path, options) {
Expand Down
50 changes: 49 additions & 1 deletion lib/internal/fs/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,15 @@ const {
S_IFREG,
S_IFSOCK,
UV_FS_SYMLINK_DIR,
UV_FS_SYMLINK_JUNCTION
UV_FS_SYMLINK_JUNCTION,
UV_DIRENT_UNKNOWN,
UV_DIRENT_FILE,
UV_DIRENT_DIR,
UV_DIRENT_LINK,
UV_DIRENT_FIFO,
UV_DIRENT_SOCKET,
UV_DIRENT_CHAR,
UV_DIRENT_BLOCK
} = process.binding('constants').fs;

const isWindows = process.platform === 'win32';
Expand All @@ -42,6 +50,45 @@ function assertEncoding(encoding) {
}
}

class DirectoryEntry {
constructor(name, type) {
this.name = name;
this.type = type;
}

isDirectory() {
return this.type === UV_DIRENT_DIR;
}

isFile() {
return this.type === UV_DIRENT_FILE;
}

isBlockDevice() {
return this.type === UV_DIRENT_BLOCK;
}

isCharacterDevice() {
return this.type === UV_DIRENT_CHAR;
}

isSymbolicLink() {
return this.type === UV_DIRENT_LINK;
}

isFIFO() {
return this.type === UV_DIRENT_FIFO;
}

isSocket() {
return this.type === UV_DIRENT_SOCKET;
}

isUnknown() {
return this.type === UV_DIRENT_UNKNOWN;
}
}

function copyObject(source) {
var target = {};
for (var key in source)
Expand Down Expand Up @@ -342,6 +389,7 @@ function validatePath(path, propName = 'path') {
module.exports = {
assertEncoding,
copyObject,
DirectoryEntry,
getOptions,
nullCheck,
preprocessSymlinkDestination,
Expand Down
10 changes: 10 additions & 0 deletions src/node_constants.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1022,6 +1022,16 @@ void DefineSystemConstants(Local<Object> target) {
NODE_DEFINE_CONSTANT(target, O_WRONLY);
NODE_DEFINE_CONSTANT(target, O_RDWR);

// file types from readdir
NODE_DEFINE_CONSTANT(target, UV_DIRENT_UNKNOWN);
NODE_DEFINE_CONSTANT(target, UV_DIRENT_FILE);
NODE_DEFINE_CONSTANT(target, UV_DIRENT_DIR);
NODE_DEFINE_CONSTANT(target, UV_DIRENT_LINK);
NODE_DEFINE_CONSTANT(target, UV_DIRENT_FIFO);
NODE_DEFINE_CONSTANT(target, UV_DIRENT_SOCKET);
NODE_DEFINE_CONSTANT(target, UV_DIRENT_CHAR);
NODE_DEFINE_CONSTANT(target, UV_DIRENT_BLOCK);

NODE_DEFINE_CONSTANT(target, S_IFMT);
NODE_DEFINE_CONSTANT(target, S_IFREG);
NODE_DEFINE_CONSTANT(target, S_IFDIR);
Expand Down
Loading

0 comments on commit 9b7b4d8

Please sign in to comment.