push all website files

This commit is contained in:
Jacob Levine
2019-01-06 13:14:45 -06:00
parent d7301e26c3
commit d2d5d4c04e
15662 changed files with 2166516 additions and 0 deletions

View File

@@ -0,0 +1,29 @@
'use strict';
module.exports = asyncForEach;
/**
* Simultaneously processes all items in the given array.
*
* @param {array} array - The array to iterate over
* @param {function} iterator - The function to call for each item in the array
* @param {function} done - The function to call when all iterators have completed
*/
function asyncForEach (array, iterator, done) {
if (array.length === 0) {
// NOTE: Normally a bad idea to mix sync and async, but it's safe here because
// of the way that this method is currently used by DirectoryReader.
done();
return;
}
// Simultaneously process all items in the array.
let pending = array.length;
array.forEach(item => {
iterator(item, () => {
if (--pending === 0) {
done();
}
});
});
}

View File

@@ -0,0 +1,48 @@
'use strict';
module.exports = readdirAsync;
const maybe = require('call-me-maybe');
const DirectoryReader = require('../directory-reader');
let asyncFacade = {
fs: require('fs'),
forEach: require('./for-each'),
async: true
};
/**
* Returns the buffered output from an asynchronous {@link DirectoryReader},
* via an error-first callback or a {@link Promise}.
*
* @param {string} dir
* @param {object} [options]
* @param {function} [callback]
* @param {object} internalOptions
*/
function readdirAsync (dir, options, callback, internalOptions) {
if (typeof options === 'function') {
callback = options;
options = undefined;
}
return maybe(callback, new Promise(((resolve, reject) => {
let results = [];
internalOptions.facade = asyncFacade;
let reader = new DirectoryReader(dir, options, internalOptions);
let stream = reader.stream;
stream.on('error', err => {
reject(err);
stream.pause();
});
stream.on('data', result => {
results.push(result);
});
stream.on('end', () => {
resolve(results);
});
})));
}

View File

@@ -0,0 +1,54 @@
'use strict';
let call = module.exports = {
safe: safeCall,
once: callOnce,
};
/**
* Calls a function with the given arguments, and ensures that the error-first callback is _always_
* invoked exactly once, even if the function throws an error.
*
* @param {function} fn - The function to invoke
* @param {...*} args - The arguments to pass to the function. The final argument must be a callback function.
*/
function safeCall (fn, args) {
// Get the function arguments as an array
args = Array.prototype.slice.call(arguments, 1);
// Replace the callback function with a wrapper that ensures it will only be called once
let callback = call.once(args.pop());
args.push(callback);
try {
fn.apply(null, args);
}
catch (err) {
callback(err);
}
}
/**
* Returns a wrapper function that ensures the given callback function is only called once.
* Subsequent calls are ignored, unless the first argument is an Error, in which case the
* error is thrown.
*
* @param {function} fn - The function that should only be called once
* @returns {function}
*/
function callOnce (fn) {
let fulfilled = false;
return function onceWrapper (err) {
if (!fulfilled) {
fulfilled = true;
return fn.apply(this, arguments);
}
else if (err) {
// The callback has already been called, but now an error has occurred
// (most likely inside the callback function). So re-throw the error,
// so it gets handled further up the call stack
throw err;
}
};
}

View File

@@ -0,0 +1,380 @@
'use strict';
const Readable = require('stream').Readable;
const EventEmitter = require('events').EventEmitter;
const path = require('path');
const normalizeOptions = require('./normalize-options');
const stat = require('./stat');
const call = require('./call');
/**
* Asynchronously reads the contents of a directory and streams the results
* via a {@link stream.Readable}.
*/
class DirectoryReader {
/**
* @param {string} dir - The absolute or relative directory path to read
* @param {object} [options] - User-specified options, if any (see {@link normalizeOptions})
* @param {object} internalOptions - Internal options that aren't part of the public API
* @class
*/
constructor (dir, options, internalOptions) {
this.options = options = normalizeOptions(options, internalOptions);
// Indicates whether we should keep reading
// This is set false if stream.Readable.push() returns false.
this.shouldRead = true;
// The directories to read
// (initialized with the top-level directory)
this.queue = [{
path: dir,
basePath: options.basePath,
posixBasePath: options.posixBasePath,
depth: 0
}];
// The number of directories that are currently being processed
this.pending = 0;
// The data that has been read, but not yet emitted
this.buffer = [];
this.stream = new Readable({ objectMode: true });
this.stream._read = () => {
// Start (or resume) reading
this.shouldRead = true;
// If we have data in the buffer, then send the next chunk
if (this.buffer.length > 0) {
this.pushFromBuffer();
}
// If we have directories queued, then start processing the next one
if (this.queue.length > 0) {
if (this.options.facade.sync) {
while (this.queue.length > 0) {
this.readNextDirectory();
}
}
else {
this.readNextDirectory();
}
}
this.checkForEOF();
};
}
/**
* Reads the next directory in the queue
*/
readNextDirectory () {
let facade = this.options.facade;
let dir = this.queue.shift();
this.pending++;
// Read the directory listing
call.safe(facade.fs.readdir, dir.path, (err, items) => {
if (err) {
// fs.readdir threw an error
this.emit('error', err);
return this.finishedReadingDirectory();
}
try {
// Process each item in the directory (simultaneously, if async)
facade.forEach(
items,
this.processItem.bind(this, dir),
this.finishedReadingDirectory.bind(this, dir)
);
}
catch (err2) {
// facade.forEach threw an error
// (probably because fs.readdir returned an invalid result)
this.emit('error', err2);
this.finishedReadingDirectory();
}
});
}
/**
* This method is called after all items in a directory have been processed.
*
* NOTE: This does not necessarily mean that the reader is finished, since there may still
* be other directories queued or pending.
*/
finishedReadingDirectory () {
this.pending--;
if (this.shouldRead) {
// If we have directories queued, then start processing the next one
if (this.queue.length > 0 && this.options.facade.async) {
this.readNextDirectory();
}
this.checkForEOF();
}
}
/**
* Determines whether the reader has finished processing all items in all directories.
* If so, then the "end" event is fired (via {@Readable#push})
*/
checkForEOF () {
if (this.buffer.length === 0 && // The stuff we've already read
this.pending === 0 && // The stuff we're currently reading
this.queue.length === 0) { // The stuff we haven't read yet
// There's no more stuff!
this.stream.push(null);
}
}
/**
* Processes a single item in a directory.
*
* If the item is a directory, and `option.deep` is enabled, then the item will be added
* to the directory queue.
*
* If the item meets the filter criteria, then it will be emitted to the reader's stream.
*
* @param {object} dir - A directory object from the queue
* @param {string} item - The name of the item (name only, no path)
* @param {function} done - A callback function that is called after the item has been processed
*/
processItem (dir, item, done) {
let stream = this.stream;
let options = this.options;
let itemPath = dir.basePath + item;
let posixPath = dir.posixBasePath + item;
let fullPath = path.join(dir.path, item);
// If `options.deep` is a number, and we've already recursed to the max depth,
// then there's no need to check fs.Stats to know if it's a directory.
// If `options.deep` is a function, then we'll need fs.Stats
let maxDepthReached = dir.depth >= options.recurseDepth;
// Do we need to call `fs.stat`?
let needStats =
!maxDepthReached || // we need the fs.Stats to know if it's a directory
options.stats || // the user wants fs.Stats objects returned
options.recurseFn || // we need fs.Stats for the recurse function
options.filterFn || // we need fs.Stats for the filter function
EventEmitter.listenerCount(stream, 'file') || // we need the fs.Stats to know if it's a file
EventEmitter.listenerCount(stream, 'directory') || // we need the fs.Stats to know if it's a directory
EventEmitter.listenerCount(stream, 'symlink'); // we need the fs.Stats to know if it's a symlink
// If we don't need stats, then exit early
if (!needStats) {
if (this.filter(itemPath, posixPath)) {
this.pushOrBuffer({ data: itemPath });
}
return done();
}
// Get the fs.Stats object for this path
stat(options.facade.fs, fullPath, (err, stats) => {
if (err) {
// fs.stat threw an error
this.emit('error', err);
return done();
}
try {
// Add the item's path to the fs.Stats object
// The base of this path, and its separators are determined by the options
// (i.e. options.basePath and options.sep)
stats.path = itemPath;
// Add depth of the path to the fs.Stats object for use this in the filter function
stats.depth = dir.depth;
if (this.shouldRecurse(stats, posixPath, maxDepthReached)) {
// Add this subdirectory to the queue
this.queue.push({
path: fullPath,
basePath: itemPath + options.sep,
posixBasePath: posixPath + '/',
depth: dir.depth + 1,
});
}
// Determine whether this item matches the filter criteria
if (this.filter(stats, posixPath)) {
this.pushOrBuffer({
data: options.stats ? stats : itemPath,
file: stats.isFile(),
directory: stats.isDirectory(),
symlink: stats.isSymbolicLink(),
});
}
done();
}
catch (err2) {
// An error occurred while processing the item
// (probably during a user-specified function, such as options.deep, options.filter, etc.)
this.emit('error', err2);
done();
}
});
}
/**
* Pushes the given chunk of data to the stream, or adds it to the buffer,
* depending on the state of the stream.
*
* @param {object} chunk
*/
pushOrBuffer (chunk) {
// Add the chunk to the buffer
this.buffer.push(chunk);
// If we're still reading, then immediately emit the next chunk in the buffer
// (which may or may not be the chunk that we just added)
if (this.shouldRead) {
this.pushFromBuffer();
}
}
/**
* Immediately pushes the next chunk in the buffer to the reader's stream.
* The "data" event will always be fired (via {@link Readable#push}).
* In addition, the "file", "directory", and/or "symlink" events may be fired,
* depending on the type of properties of the chunk.
*/
pushFromBuffer () {
let stream = this.stream;
let chunk = this.buffer.shift();
// Stream the data
try {
this.shouldRead = stream.push(chunk.data);
}
catch (err) {
this.emit('error', err);
}
// Also emit specific events, based on the type of chunk
chunk.file && this.emit('file', chunk.data);
chunk.symlink && this.emit('symlink', chunk.data);
chunk.directory && this.emit('directory', chunk.data);
}
/**
* Determines whether the given directory meets the user-specified recursion criteria.
* If the user didn't specify recursion criteria, then this function will default to true.
*
* @param {fs.Stats} stats - The directory's {@link fs.Stats} object
* @param {string} posixPath - The item's POSIX path (used for glob matching)
* @param {boolean} maxDepthReached - Whether we've already crawled the user-specified depth
* @returns {boolean}
*/
shouldRecurse (stats, posixPath, maxDepthReached) {
let options = this.options;
if (maxDepthReached) {
// We've already crawled to the maximum depth. So no more recursion.
return false;
}
else if (!stats.isDirectory()) {
// It's not a directory. So don't try to crawl it.
return false;
}
else if (options.recurseGlob) {
// Glob patterns are always tested against the POSIX path, even on Windows
// https://github.com/isaacs/node-glob#windows
return options.recurseGlob.test(posixPath);
}
else if (options.recurseRegExp) {
// Regular expressions are tested against the normal path
// (based on the OS or options.sep)
return options.recurseRegExp.test(stats.path);
}
else if (options.recurseFn) {
try {
// Run the user-specified recursion criteria
return options.recurseFn.call(null, stats);
}
catch (err) {
// An error occurred in the user's code.
// In Sync and Async modes, this will return an error.
// In Streaming mode, we emit an "error" event, but continue processing
this.emit('error', err);
}
}
else {
// No recursion function was specified, and we're within the maximum depth.
// So crawl this directory.
return true;
}
}
/**
* Determines whether the given item meets the user-specified filter criteria.
* If the user didn't specify a filter, then this function will always return true.
*
* @param {string|fs.Stats} value - Either the item's path, or the item's {@link fs.Stats} object
* @param {string} posixPath - The item's POSIX path (used for glob matching)
* @returns {boolean}
*/
filter (value, posixPath) {
let options = this.options;
if (options.filterGlob) {
// Glob patterns are always tested against the POSIX path, even on Windows
// https://github.com/isaacs/node-glob#windows
return options.filterGlob.test(posixPath);
}
else if (options.filterRegExp) {
// Regular expressions are tested against the normal path
// (based on the OS or options.sep)
return options.filterRegExp.test(value.path || value);
}
else if (options.filterFn) {
try {
// Run the user-specified filter function
return options.filterFn.call(null, value);
}
catch (err) {
// An error occurred in the user's code.
// In Sync and Async modes, this will return an error.
// In Streaming mode, we emit an "error" event, but continue processing
this.emit('error', err);
}
}
else {
// No filter was specified, so match everything
return true;
}
}
/**
* Emits an event. If one of the event listeners throws an error,
* then an "error" event is emitted.
*
* @param {string} eventName
* @param {*} data
*/
emit (eventName, data) {
let stream = this.stream;
try {
stream.emit(eventName, data);
}
catch (err) {
if (eventName === 'error') {
// Don't recursively emit "error" events.
// If the first one fails, then just throw
throw err;
}
else {
stream.emit('error', err);
}
}
}
}
module.exports = DirectoryReader;

View File

@@ -0,0 +1,85 @@
'use strict';
const readdirSync = require('./sync');
const readdirAsync = require('./async');
const readdirStream = require('./stream');
module.exports = exports = readdirAsyncPath;
exports.readdir = exports.readdirAsync = exports.async = readdirAsyncPath;
exports.readdirAsyncStat = exports.async.stat = readdirAsyncStat;
exports.readdirStream = exports.stream = readdirStreamPath;
exports.readdirStreamStat = exports.stream.stat = readdirStreamStat;
exports.readdirSync = exports.sync = readdirSyncPath;
exports.readdirSyncStat = exports.sync.stat = readdirSyncStat;
/**
* Synchronous readdir that returns an array of string paths.
*
* @param {string} dir
* @param {object} [options]
* @returns {string[]}
*/
function readdirSyncPath (dir, options) {
return readdirSync(dir, options, {});
}
/**
* Synchronous readdir that returns results as an array of {@link fs.Stats} objects
*
* @param {string} dir
* @param {object} [options]
* @returns {fs.Stats[]}
*/
function readdirSyncStat (dir, options) {
return readdirSync(dir, options, { stats: true });
}
/**
* Aynchronous readdir (accepts an error-first callback or returns a {@link Promise}).
* Results are an array of path strings.
*
* @param {string} dir
* @param {object} [options]
* @param {function} [callback]
* @returns {Promise<string[]>}
*/
function readdirAsyncPath (dir, options, callback) {
return readdirAsync(dir, options, callback, {});
}
/**
* Aynchronous readdir (accepts an error-first callback or returns a {@link Promise}).
* Results are an array of {@link fs.Stats} objects.
*
* @param {string} dir
* @param {object} [options]
* @param {function} [callback]
* @returns {Promise<fs.Stats[]>}
*/
function readdirAsyncStat (dir, options, callback) {
return readdirAsync(dir, options, callback, { stats: true });
}
/**
* Aynchronous readdir that returns a {@link stream.Readable} (which is also an {@link EventEmitter}).
* All stream data events ("data", "file", "directory", "symlink") are passed a path string.
*
* @param {string} dir
* @param {object} [options]
* @returns {stream.Readable}
*/
function readdirStreamPath (dir, options) {
return readdirStream(dir, options, {});
}
/**
* Aynchronous readdir that returns a {@link stream.Readable} (which is also an {@link EventEmitter})
* All stream data events ("data", "file", "directory", "symlink") are passed an {@link fs.Stats} object.
*
* @param {string} dir
* @param {object} [options]
* @returns {stream.Readable}
*/
function readdirStreamStat (dir, options) {
return readdirStream(dir, options, { stats: true });
}

View File

@@ -0,0 +1,177 @@
'use strict';
const path = require('path');
const globToRegExp = require('glob-to-regexp');
module.exports = normalizeOptions;
let isWindows = /^win/.test(process.platform);
/**
* @typedef {Object} FSFacade
* @property {fs.readdir} readdir
* @property {fs.stat} stat
* @property {fs.lstat} lstat
*/
/**
* Validates and normalizes the options argument
*
* @param {object} [options] - User-specified options, if any
* @param {object} internalOptions - Internal options that aren't part of the public API
*
* @param {number|boolean|function} [options.deep]
* The number of directories to recursively traverse. Any falsy value or negative number will
* default to zero, so only the top-level contents will be returned. Set to `true` or `Infinity`
* to traverse all subdirectories. Or provide a function that accepts a {@link fs.Stats} object
* and returns a truthy value if the directory's contents should be crawled.
*
* @param {function|string|RegExp} [options.filter]
* A function that accepts a {@link fs.Stats} object and returns a truthy value if the data should
* be returned. Or a RegExp or glob string pattern, to filter by file name.
*
* @param {string} [options.sep]
* The path separator to use. By default, the OS-specific separator will be used, but this can be
* set to a specific value to ensure consistency across platforms.
*
* @param {string} [options.basePath]
* The base path to prepend to each result. If empty, then all results will be relative to `dir`.
*
* @param {FSFacade} [options.fs]
* Synchronous or asynchronous facades for Node.js File System module
*
* @param {object} [internalOptions.facade]
* Synchronous or asynchronous facades for various methods, including for the Node.js File System module
*
* @param {boolean} [internalOptions.emit]
* Indicates whether the reader should emit "file", "directory", and "symlink" events
*
* @param {boolean} [internalOptions.stats]
* Indicates whether the reader should emit {@link fs.Stats} objects instead of path strings
*
* @returns {object}
*/
function normalizeOptions (options, internalOptions) {
if (options === null || options === undefined) {
options = {};
}
else if (typeof options !== 'object') {
throw new TypeError('options must be an object');
}
let recurseDepth, recurseFn, recurseRegExp, recurseGlob, deep = options.deep;
if (deep === null || deep === undefined) {
recurseDepth = 0;
}
else if (typeof deep === 'boolean') {
recurseDepth = deep ? Infinity : 0;
}
else if (typeof deep === 'number') {
if (deep < 0 || isNaN(deep)) {
throw new Error('options.deep must be a positive number');
}
else if (Math.floor(deep) !== deep) {
throw new Error('options.deep must be an integer');
}
else {
recurseDepth = deep;
}
}
else if (typeof deep === 'function') {
recurseDepth = Infinity;
recurseFn = deep;
}
else if (deep instanceof RegExp) {
recurseDepth = Infinity;
recurseRegExp = deep;
}
else if (typeof deep === 'string' && deep.length > 0) {
recurseDepth = Infinity;
recurseGlob = globToRegExp(deep, { extended: true, globstar: true });
}
else {
throw new TypeError('options.deep must be a boolean, number, function, regular expression, or glob pattern');
}
let filterFn, filterRegExp, filterGlob, filter = options.filter;
if (filter !== null && filter !== undefined) {
if (typeof filter === 'function') {
filterFn = filter;
}
else if (filter instanceof RegExp) {
filterRegExp = filter;
}
else if (typeof filter === 'string' && filter.length > 0) {
filterGlob = globToRegExp(filter, { extended: true, globstar: true });
}
else {
throw new TypeError('options.filter must be a function, regular expression, or glob pattern');
}
}
let sep = options.sep;
if (sep === null || sep === undefined) {
sep = path.sep;
}
else if (typeof sep !== 'string') {
throw new TypeError('options.sep must be a string');
}
let basePath = options.basePath;
if (basePath === null || basePath === undefined) {
basePath = '';
}
else if (typeof basePath === 'string') {
// Append a path separator to the basePath, if necessary
if (basePath && basePath.substr(-1) !== sep) {
basePath += sep;
}
}
else {
throw new TypeError('options.basePath must be a string');
}
// Convert the basePath to POSIX (forward slashes)
// so that glob pattern matching works consistently, even on Windows
let posixBasePath = basePath;
if (posixBasePath && sep !== '/') {
posixBasePath = posixBasePath.replace(new RegExp('\\' + sep, 'g'), '/');
/* istanbul ignore if */
if (isWindows) {
// Convert Windows root paths (C:\) and UNCs (\\) to POSIX root paths
posixBasePath = posixBasePath.replace(/^([a-zA-Z]\:\/|\/\/)/, '/');
}
}
// Determine which facade methods to use
let facade;
if (options.fs === null || options.fs === undefined) {
// The user didn't provide their own facades, so use our internal ones
facade = internalOptions.facade;
}
else if (typeof options.fs === 'object') {
// Merge the internal facade methods with the user-provided `fs` facades
facade = Object.assign({}, internalOptions.facade);
facade.fs = Object.assign({}, internalOptions.facade.fs, options.fs);
}
else {
throw new TypeError('options.fs must be an object');
}
return {
recurseDepth,
recurseFn,
recurseRegExp,
recurseGlob,
filterFn,
filterRegExp,
filterGlob,
sep,
basePath,
posixBasePath,
facade,
emit: !!internalOptions.emit,
stats: !!internalOptions.stats,
};
}

View File

@@ -0,0 +1,74 @@
'use strict';
const call = require('./call');
module.exports = stat;
/**
* Retrieves the {@link fs.Stats} for the given path. If the path is a symbolic link,
* then the Stats of the symlink's target are returned instead. If the symlink is broken,
* then the Stats of the symlink itself are returned.
*
* @param {object} fs - Synchronous or Asynchronouse facade for the "fs" module
* @param {string} path - The path to return stats for
* @param {function} callback
*/
function stat (fs, path, callback) {
let isSymLink = false;
call.safe(fs.lstat, path, (err, lstats) => {
if (err) {
// fs.lstat threw an eror
return callback(err);
}
try {
isSymLink = lstats.isSymbolicLink();
}
catch (err2) {
// lstats.isSymbolicLink() threw an error
// (probably because fs.lstat returned an invalid result)
return callback(err2);
}
if (isSymLink) {
// Try to resolve the symlink
symlinkStat(fs, path, lstats, callback);
}
else {
// It's not a symlink, so return the stats as-is
callback(null, lstats);
}
});
}
/**
* Retrieves the {@link fs.Stats} for the target of the given symlink.
* If the symlink is broken, then the Stats of the symlink itself are returned.
*
* @param {object} fs - Synchronous or Asynchronouse facade for the "fs" module
* @param {string} path - The path of the symlink to return stats for
* @param {object} lstats - The stats of the symlink
* @param {function} callback
*/
function symlinkStat (fs, path, lstats, callback) {
call.safe(fs.stat, path, (err, stats) => {
if (err) {
// The symlink is broken, so return the stats for the link itself
return callback(null, lstats);
}
try {
// Return the stats for the resolved symlink target,
// and override the `isSymbolicLink` method to indicate that it's a symlink
stats.isSymbolicLink = () => true;
}
catch (err2) {
// Setting stats.isSymbolicLink threw an error
// (probably because fs.stat returned an invalid result)
return callback(err2);
}
callback(null, stats);
});
}

View File

@@ -0,0 +1,25 @@
'use strict';
module.exports = readdirStream;
const DirectoryReader = require('../directory-reader');
let streamFacade = {
fs: require('fs'),
forEach: require('../async/for-each'),
async: true
};
/**
* Returns the {@link stream.Readable} of an asynchronous {@link DirectoryReader}.
*
* @param {string} dir
* @param {object} [options]
* @param {object} internalOptions
*/
function readdirStream (dir, options, internalOptions) {
internalOptions.facade = streamFacade;
let reader = new DirectoryReader(dir, options, internalOptions);
return reader.stream;
}

View File

@@ -0,0 +1,22 @@
'use strict';
module.exports = syncForEach;
/**
* A facade that allows {@link Array.forEach} to be called as though it were asynchronous.
*
* @param {array} array - The array to iterate over
* @param {function} iterator - The function to call for each item in the array
* @param {function} done - The function to call when all iterators have completed
*/
function syncForEach (array, iterator, done) {
array.forEach(item => {
iterator(item, () => {
// Note: No error-handling here because this is currently only ever called
// by DirectoryReader, which never passes an `error` parameter to the callback.
// Instead, DirectoryReader emits an "error" event if an error occurs.
});
});
done();
}

View File

@@ -0,0 +1,64 @@
'use strict';
const fs = require('fs');
const call = require('../call');
/**
* A facade around {@link fs.readdirSync} that allows it to be called
* the same way as {@link fs.readdir}.
*
* @param {string} dir
* @param {function} callback
*/
exports.readdir = function (dir, callback) {
// Make sure the callback is only called once
callback = call.once(callback);
try {
let items = fs.readdirSync(dir);
callback(null, items);
}
catch (err) {
callback(err);
}
};
/**
* A facade around {@link fs.statSync} that allows it to be called
* the same way as {@link fs.stat}.
*
* @param {string} path
* @param {function} callback
*/
exports.stat = function (path, callback) {
// Make sure the callback is only called once
callback = call.once(callback);
try {
let stats = fs.statSync(path);
callback(null, stats);
}
catch (err) {
callback(err);
}
};
/**
* A facade around {@link fs.lstatSync} that allows it to be called
* the same way as {@link fs.lstat}.
*
* @param {string} path
* @param {function} callback
*/
exports.lstat = function (path, callback) {
// Make sure the callback is only called once
callback = call.once(callback);
try {
let stats = fs.lstatSync(path);
callback(null, stats);
}
catch (err) {
callback(err);
}
};

View File

@@ -0,0 +1,34 @@
'use strict';
module.exports = readdirSync;
const DirectoryReader = require('../directory-reader');
let syncFacade = {
fs: require('./fs'),
forEach: require('./for-each'),
sync: true
};
/**
* Returns the buffered output from a synchronous {@link DirectoryReader}.
*
* @param {string} dir
* @param {object} [options]
* @param {object} internalOptions
*/
function readdirSync (dir, options, internalOptions) {
internalOptions.facade = syncFacade;
let reader = new DirectoryReader(dir, options, internalOptions);
let stream = reader.stream;
let results = [];
let data = stream.read();
while (data !== null) {
results.push(data);
data = stream.read();
}
return results;
}