fs-hospitality.js

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.readdirRecursivelySync = exports.readdirRecursively = exports.readdirPromise = exports.mklinkSync = exports.mklink = exports.writeAsTextSync = exports.writeAsText = exports.trimAllLines = exports.writeTmpFileSync = exports.makeTmpPath = exports.convertEOL = exports.readAsTextSync = exports.readAsText = exports.readFilePromise = exports.detectTextEol = exports.decodeTextBuffer = exports.detectTextEncoding = exports.textDataToBuf = void 0;
const tslib_1 = require("tslib");
const chardet = tslib_1.__importStar(require("chardet"));
const child_process_1 = require("child_process");
const EncodingJp = tslib_1.__importStar(require("encoding-japanese"));
const fs = tslib_1.__importStar(require("fs"));
const iconv = tslib_1.__importStar(require("iconv-lite"));
const _ = tslib_1.__importStar(require("lodash"));
const os = tslib_1.__importStar(require("os"));
const path = tslib_1.__importStar(require("path"));
const uuid_1 = require("uuid");
/** @namespace API */
/** @private */
const ARG_ERR = 'TypeError [ERR_INVALID_ARG_VALUE]: ';
/**
 * @private
 * @param {Function} fn - Function where the Error occurred
 * @returns {string} - Returns "  at ${Function.name} (${__filename})"
 */
const _errLoc = (fn) => `\n    at ${fn.name} (${__filename})`;
/**
 * Reads the entire contents of a file. If a type of the param is Buffer, direct return it.
 *
 * @memberof API
 * @param {(Buffer|string)} textData - A Buffer or a file-path
 * @returns {Buffer} - The entire contents
 * @example
const { textDataToBuf } = require('@tuckn/fs-hospitality');

const buf = textDataToBuf('D:\\Test\\SjisNote.txt'); // file-path
// Returns: fs.readFileSync('D:\\Test\\SjisNote.txt')

const buf2 = textDataToBuf(buf); // Buffer
// Returns: buf
 */
function textDataToBuf(textData) {
    if (Buffer.isBuffer(textData))
        return textData;
    if (!fs.existsSync(textData)) {
        throw new Error(`${ARG_ERR}textData is not a valid file-path.${_errLoc(Function)}`);
    }
    return fs.readFileSync(textData);
}
exports.textDataToBuf = textDataToBuf;
/**
 * Detects the character encoding of a Buffer or a file-path. A binary file would be detected as UTF32. See {@link https://github.com/runk/node-chardet#supported-encodings|chardet Supported Encodings}. If chardet detect windows-1252, Re-detect with {@link https://github.com/polygonplanet/encoding.js#available-encodings|encoding.js}.
 *
 * @memberof API
 * @param {(Buffer|string)} textData - A Buffer or a file-path
 * @returns {string} - A name of character encoding. A binary file would be detected as UTF32.
 * @example
const { detectTextEncoding } = require('@tuckn/fs-hospitality');

const encoding = detectTextEncoding('D:\\Test\\SjisNote.txt');
// Returns: 'SJIS'

const encoding2 = detectTextEncoding('D:\\Test\\Utf16LeNote.doc');
// Returns: 'UTF-16LE'

const encoding3 = detectTextEncoding('D:\\Test\\image.png');
// Returns: 'UTF32'
 */
function detectTextEncoding(textData) {
    const buf = textDataToBuf(textData);
    const chardetVal = chardet.detect(buf);
    if (!chardetVal) {
        throw new Error(`TypeError [ERR_INVALID_CONTENT]: encoding is empty.${_errLoc(Function)}`);
    }
    if (chardetVal === 'windows-1252')
        return EncodingJp.detect(buf);
    return chardetVal;
}
exports.detectTextEncoding = detectTextEncoding;
/**
 * Decodes a Buffer of text with automatically detecting encoding
 *
 * @memberof API
 * @param {Buffer} textBuf - A Buffer of text
 * @param {string} [encoding=''] - A specifying encoding. falsy to auto
 * @returns {string} - A encoded string
 * @example
const { decodeTextBuffer } = require('@tuckn/fs-hospitality');

const textBuf = fs.readFileSync('D:\\Test\\SjisCRLF.txt');
const text = decodeTextBuffer(textBuf);
// Returns: 'これはshift-JISで書かれたファイルです。'
 */
function decodeTextBuffer(textBuf, encoding = '') {
    let enc = encoding;
    if (!encoding)
        enc = detectTextEncoding(textBuf);
    return iconv.decode(textBuf, enc);
}
exports.decodeTextBuffer = decodeTextBuffer;
/**
 * Detects the EOL (End of Line) character of a Buffer or a file-path.
 *
 * @memberof API
 * @param {(Buffer|string)} textData - Buffer of file-path
 * @returns {string} - "crlf" | "cr" | "lf" | ""
 * @example
const { detectTextEol } = require('@tuckn/fs-hospitality');

const eol = detectTextEol('D:\\Test\\SjisCRLF.txt'); // file-path
// Returns: 'crlf'

const buf = 'D:\\Test\\Utf8.doc'
const eol2 = detectTextEol(buf); // Buffer
// Returns: 'lf'
 */
function detectTextEol(textData) {
    const buf = textDataToBuf(textData);
    const text = decodeTextBuffer(buf);
    if (!text) {
        throw new Error(`${ARG_ERR}textData is empty.${_errLoc(Function)}`);
    }
    if (_.isString(text)) {
        if (/\r\n/.test(text))
            return 'crlf';
        if (/\r/.test(text))
            return 'cr';
        if (/\n/.test(text))
            return 'lf';
    }
    return '';
}
exports.detectTextEol = detectTextEol;
/**
 * fs.readFile Promisification
 *
 * @memberof API
 * @param {string} filePath - A Filename
 * @param {object} [options] - See {@link https://nodejs.org/api/fs.html#fs_fs_readfile_path_options_callback|Node.js fs.readFile}
 * @returns {Promise<Buffer|string>} -
 * @example
const { readFilePromise } = require('@tuckn/fs-hospitality');

// All arguments are same with fs.readFile
const data = await readFilePromise('D:\\Test\\myData.dat');
console.log(data);
 */
function readFilePromise(filePath, options = {}) {
    return new Promise((resolve, reject) => {
        fs.readFile(filePath, options, (err, data) => {
            if (err)
                return reject(err);
            return resolve(data);
        });
    });
}
exports.readFilePromise = readFilePromise;
/**
 * Reads a Buffer or a file-path as text and encodes it into a String.
 *
 * @memberof API
 * @param {(Buffer|string)} textFile - Buffer or file-path
 * @param {string} [encoding=''] - If empty, auto-detecting
 * @returns {Promise<string>} -
 * @example
const { readAsText } = require('@tuckn/fs-hospitality');

// Ex.1 From a file-path
const fileSjis = 'D:\\Test\\MyNoteSJIS.txt'

readAsText(fileSjis).then((textString) => {
  console.log(textString);
  // Returns String parsed with Shift_JIS
});

// Ex.2 From a Buffer
const fileUtf16LE = 'D:\\Test\\Utf16LE.log'

fs.readFile(fileUtf16LE, async (err, data) => {
  const textString = await readAsText(data);
  console.log(textString);
  // Returns String parsed with UTF-16LE
});
 */
async function readAsText(textFile, encoding = '') {
    if (!textFile) {
        return Promise.reject(new Error(`${ARG_ERR}textFile is empty.${_errLoc(Function)}`));
    }
    // Buffer
    if (Buffer.isBuffer(textFile)) {
        return decodeTextBuffer(textFile, encoding);
    }
    // String (A file-path)
    const filePath = path.resolve(textFile);
    const data = (await readFilePromise(filePath));
    // if (encoding === 'binary') return resolve(data);
    // @todo When enc is 'binary'
    return decodeTextBuffer(data, encoding);
}
exports.readAsText = readAsText;
/**
 * The synchronous version of this API: readAsText().
 *
 * @memberof API
 * @param {(Buffer|string)} textFile - Buffer or file-path
 * @param {string} [encoding=''] - If empty, auto-detecting
 * @returns {string} -
 * @example
const { readAsTextSync } = require('@tuckn/fs-hospitality');

// Ex.1 From the file-path
const textString = readAsTextSync('D:\\Test\\MyNoteSJIS.txt');
// Returns String parsed with Shift_JIS

// Ex.2 From the Buffer
const buf = fs.readFile('D:\\Test\\Utf16LE.log');
const textString2 = readAsTextSync(buf);
// Returns String parsed with UTF-16LE
 */
function readAsTextSync(textFile, encoding = '') {
    if (!textFile) {
        throw new Error(`${ARG_ERR}textFile is empty.${_errLoc(Function)}`);
    }
    // Buffer
    if (Buffer.isBuffer(textFile))
        return decodeTextBuffer(textFile, encoding);
    // String (A file-path)
    const filePath = path.resolve(textFile);
    const data = fs.readFileSync(filePath);
    // if (encoding === 'binary') return data;
    // @todo When enc is 'binary'
    return decodeTextBuffer(data, encoding);
}
exports.readAsTextSync = readAsTextSync;
/**
 * Replaces the EOL (End of Line) character of a String.
 *
 * @memberof API
 * @param {string} strData - A string to be replaced
 * @param {string} eol - "(lf|unix|\n)" | "(cr|mac|\r)" | "(crlf|dos|\r\n)"
 * @returns {string} - A replaced string
 * @example
const { convertEOL } = require('@tuckn/fs-hospitality');

const textCrLf = 'foo\r\n'
  + 'bar\r\n'
  + '\r\n'
  + 'baz';

const textLf = convertEOL(textCrLf, 'lf');
// Returns:
// 'foo\n'
//   + 'bar\n
//   + '\n
//   + 'baz'
 */
function convertEOL(strData, eol = '') {
    let eolCode;
    if (/^(crlf|dos)$/i.test(eol))
        eolCode = '\r\n';
    else if (/^(lf|unix)$/i.test(eol))
        eolCode = '\n';
    else if (/^(cr|mac)$/i.test(eol))
        eolCode = '\r';
    else
        eolCode = eol;
    return strData.replace(/\r?\n/g, eolCode);
}
exports.convertEOL = convertEOL;
/**
 * Create a temporary path on the {@link https://nodejs.org/api/os.html#os_os_tmpdir|Node.js os.tmpdir}
 *
 * @memberof API
 * @param {string} [baseDir] - The default is os.tmpdir
 * @param {string} [prefix]
 * @param {string} [postfix]
 * @returns {string} - A temporary path
 * @example
const { makeTmpPath } = require('@tuckn/fs-hospitality');

const tmpPath1 = makeTmpPath();
// Returns: 'C:\Users\YourName\AppData\Local\Temp\7c70ceef-28f6-4ae8-b4ef-5e5d459ef007'

// If necessary, make sure that the file does not exist.
const fs = require('fs');
if (fs.existsSync(tmpPath1)) throw new Error('Oops!');

// Makes on the current working directory
const tmpPath2 = makeTmpPath('.');
// Returns: 'D:\test\2a5d35c8-7214-4ec7-a41d-a371b19273e7'

// Make on SMB path
const tmpPath3 = makeTmpPath('\\\\server\\public');
// Returns: '\\server\public\01fa6ce7-e6d3-4b50-bdcd-19679c49bef2'

// with the prefix name
const tmpPath2 = makeTmpPath('', 'MyTemp_');
// Returns: 'C:\Users\YourName\AppData\Local\Temp\MyTemp_42dc1759-b744-4f2a-840f-e6fa27191cff'

const tmpPath4 = makeTmpPath('R:', 'tmp_', '.log');
// Returns: 'R:\tmp_14493643-792d-4b0d-b2af-c74531db625e.log'
 */
function makeTmpPath(baseDir = '', prefix = '', postfix = '') {
    let basePath = baseDir;
    if (!basePath)
        basePath = os.tmpdir();
    return path.resolve(basePath, prefix + (0, uuid_1.v4)() + postfix);
}
exports.makeTmpPath = makeTmpPath;
/**
 * Write the data to a new temporary path, and Return the path.
 *
 * @memberof API
 * @param {string | NodeJS.ArrayBufferView} data - A data to write
 * @param {object} [options] - See {@link https://nodejs.org/api/fs.html#fs_fs_writefilesync_file_data_options|Node.js fs.writeFileSync}
 * @returns {string} - A temporary file path
 * @example
const { writeTmpFileSync } = require('@tuckn/fs-hospitality');

const tmpStr = 'The Temporary Message';
const tmpPath = writeTmpFileSync(tmpStr);
// Returns: 'C:\Users\YourName\AppData\Local\Temp\7c70ceef-28f6-4ae8-b4ef-5e5d459ef007'

const fs = require('fs');
const readData = fs.readFileSync(tmpPath, { encoding: 'utf8' });
console.log(tmpStr === readData); // true
 */
function writeTmpFileSync(data, options = {}) {
    const tmpPath = makeTmpPath();
    fs.writeFileSync(tmpPath, data, options);
    return tmpPath;
}
exports.writeTmpFileSync = writeTmpFileSync;
/**
 * @private
 * @param {string} matched
 * @returns {string} - A replaced string
 */
function _trimAllLinesReplacer(matched) {
    return matched.replace(/[^\r\n]/g, '');
}
/**
 * Trims a string at every each line
 *
 * @memberof API
 * @param {string} strLines - A string to be trimmed
 * @param {string} [option='all'] - 'all' | 'start' | 'end';
 * @returns {string} - A trimmed string
 * @example
const { trimAllLines } = require('@tuckn/fs-hospitality');

const str = '  foo  \n'
  + '  bar  \n'
  + ' baz  ';

const trimmedStr1 = trimAllLines(str);
// Returns: 'foo\n'
//   + 'bar\n'
//   + 'baz';

const trimmedStr2 = trimAllLines(str, 'end');
// Returns: '  foo\n'
//   + '  bar\n'
//   + ' baz';
 */
function trimAllLines(strLines, option = 'all') {
    let trimmed = strLines;
    if (option === 'start' || option === 'all') {
        trimmed = trimmed.replace(/^\s+/gm, _trimAllLinesReplacer);
    }
    if (option === 'end' || option === 'all') {
        trimmed = trimmed.replace(/\s+$/gm, _trimAllLinesReplacer);
    }
    return trimmed;
}
exports.trimAllLines = trimAllLines;
/**
 * @private
 * @param {string} [strData='']
 * @param {PrewriteAsTextOptions} [options] - Optional parameters
 * @returns {string} - A formatted text
 */
function _prewriteAsText(strData = '', options = {}) {
    let writtenData = strData;
    const trimOpt = _.get(options, 'trim', undefined);
    if (trimOpt) {
        writtenData = trimAllLines(writtenData, trimOpt);
    }
    const eol = _.get(options, 'eol', null);
    if (eol)
        writtenData = convertEOL(writtenData, eol);
    return writtenData;
}
/**
 * Write a String to the file as text. Also can specify an encoding, an EOL, BOM and trimming every line.
 *
 * @memberof API
 * @param {string} destPath - A destination file-path
 * @param {string} [strData=''] - A string of data to write
 * @param {PrewriteAsTextOptions} [options] - Optional parameters
 * @returns {Promise<void>} - { resolve:undefined, reject: Error }
 * @example
const { writeAsText } = require('@tuckn/fs-hospitality');
const vbsFile = 'D:\\Test\\utf8bom.vbs';
const strData = 'Dim str As String  \n  str = "foo"\n  WScript.Echo str';
const options = {
  trim: 'all',
  eol: 'crlf',
  bom: true,
  encoding: 'UTF-8',
};

writeAsText(vbsFile, strData, options).then(() => {
  console.log('Writing successful');
});
 */
function writeAsText(destPath, strData = '', options = {}) {
    if (!destPath) {
        return Promise.reject(new Error(`${ARG_ERR}destPath is empty.${_errLoc(Function)}`));
    }
    const filePath = path.resolve(destPath);
    const writtenData = _prewriteAsText(strData, options);
    let addBOM = { addBOM: false };
    if (_.get(options, 'bom', false))
        addBOM = { addBOM: true };
    const encoding = _.get(options, 'encoding', 'utf8');
    return new Promise((resolve, reject) => {
        const dirPath = path.dirname(filePath);
        fs.mkdir(dirPath, { recursive: true }, (errMkDir) => {
            if (errMkDir)
                return reject(errMkDir);
            return fs.writeFile(filePath, iconv.encode(writtenData, encoding, addBOM), (err) => {
                if (err) {
                    reject(err);
                }
                else {
                    resolve();
                }
            });
        });
    });
}
exports.writeAsText = writeAsText;
/**
 * The synchronous version of this API: writeAsText().
 *
 * @memberof API
 * @param {string} destPath - A destination file-path
 * @param {string} [strData=''] - A string of data to write
 * @param {object} options - See {@link API.writeAsText}
 * @returns {void}
 * @example
const { writeAsTextSync } = require('@tuckn/fs-hospitality');
const vbsFile = 'D:\\Test\\utf8bom.vbs';
const strData = 'Dim str As String  \n  str = "foo"\n  WScript.Echo str';
const options = {
  trim: 'all',
  eol: 'crlf',
  bom: true,
  encoding: 'UTF-8',
};

writeAsTextSync(vbsFile, strData, options);
console.log('Writing successful');
 */
function writeAsTextSync(destPath, strData = '', options = {}) {
    if (!destPath) {
        throw new Error(`${ARG_ERR}destPath is empty.${_errLoc(Function)}`);
    }
    const filePath = path.resolve(destPath);
    const writtenData = _prewriteAsText(strData, options);
    let addBOM = { addBOM: false };
    if (_.get(options, 'bom', false))
        addBOM = { addBOM: true };
    const encoding = _.get(options, 'encoding', 'utf8');
    const dirPath = path.dirname(filePath);
    if (!fs.existsSync(dirPath))
        fs.mkdirSync(dirPath, { recursive: true });
    return fs.writeFileSync(filePath, iconv.encode(writtenData, encoding, addBOM));
}
exports.writeAsTextSync = writeAsTextSync;
/**
 *  Creates a new link (also known as Symbolic Link) to an existing file. Similar to {@link https://nodejs.org/api/fs.html#fs_fs_linksync_existingpath_newpath|Node.js-Path}. But on Windows, use mklink of command in Command-Prompt. so requires admin rights.
 *
 * @memberof API
 * @param {string} existingPath - A source file or direcotry
 * @param {string} newPath - A destination path
 * @returns {Promise<string>} - Returns mklink stdout
 * @example
const { mklink } = require('@tuckn/fs-hospitality');

// on Windows, use mklink of command in Command-Prompt and requires admin rights
mklink('D:\\MySrc\\TestDir', 'C:\\Test').then((stdout) => {
  console.log(stdout);
  // Created the symbolic link on "C:\"
});
 */
async function mklink(existingPath, newPath) {
    if (os.platform() !== 'win32') {
        return new Promise((resolve, reject) => {
            fs.link(existingPath, newPath, (err) => {
                if (err)
                    return reject(err);
                return resolve();
            });
        });
    }
    if (!existingPath) {
        throw new Error(`${ARG_ERR}existingPath is empty.${_errLoc(Function)}`);
    }
    if (!newPath) {
        throw new Error(`${ARG_ERR}newPath is empty.${_errLoc(Function)}`);
    }
    const mainCmd = 'mklink';
    let argsStr = '';
    return new Promise((resolve, reject) => {
        fs.stat(existingPath, (fsErr, statSrc) => {
            if (fsErr)
                return reject(fsErr);
            if (statSrc.isFile()) {
                argsStr = `"${newPath}" "${existingPath}"`;
            }
            else if (statSrc.isDirectory()) {
                argsStr = `/D "${newPath}" "${existingPath}"`;
            }
            else {
                return reject(new Error(`${ARG_ERR}${newPath} is not the file or directory.${_errLoc(Function)}`));
            }
            return (0, child_process_1.exec)(`${mainCmd} ${argsStr}`, { encoding: 'buffer' }, (err, stdout, stderr) => {
                if (err)
                    return reject(decodeTextBuffer(stderr));
                return resolve(decodeTextBuffer(stdout));
            });
        });
    });
}
exports.mklink = mklink;
/**
 * The synchronous version of this API: mklink().
 *
 * @memberof API
 * @param {string} existingPath - A source file or direcotry
 * @param {string} newPath - A destination path
 * @returns {string} - Returns mklink stdout
 * @example
const { mklinkSync } = require('@tuckn/fs-hospitality');

// on Windows, use mklink of command in Command-Prompt and requires admin rights
const stdout = mklinkSync('D:\\MySrc\\TestDir', 'C:\\Test');
// Created the symbolic link on "C:\"
 */
function mklinkSync(existingPath, newPath) {
    if (os.platform() !== 'win32') {
        return fs.linkSync(existingPath, newPath);
    }
    if (!existingPath) {
        throw new Error(`${ARG_ERR}existingPath is empty.${_errLoc(Function)}`);
    }
    if (!newPath) {
        throw new Error(`${ARG_ERR}newPath is empty.${_errLoc(Function)}`);
    }
    const mainCmd = 'mklink';
    let argsStr = '';
    const statSrc = fs.statSync(existingPath);
    if (statSrc.isFile()) {
        argsStr = `"${newPath}" "${existingPath}"`;
    }
    else if (statSrc.isDirectory()) {
        argsStr = `/D "${newPath}" "${existingPath}"`;
    }
    else {
        throw new Error(`${ARG_ERR}${newPath} is not the file or direcotry.${_errLoc(Function)}`);
    }
    return decodeTextBuffer((0, child_process_1.execSync)(`${mainCmd} ${argsStr}`));
}
exports.mklinkSync = mklinkSync;
/**
 * fs.readdir Promisification. {@link https://nodejs.org/api/fs.html#fs_fs_readdir_path_options_callback|Node.js fs.readdir}
 *
 * @memberof API
 * @param {string} dirPath - A directory path
 * @param {object} [options] - See {@link https://nodejs.org/api/fs.html#fs_fs_readdir_path_options_callback|Node.js fs.readdir}
 * @returns {Promise<string[]|Buffer[]|fs.Dirent[]>} - Returns array of fs.Dirent
 */
function readdirPromise(dirPath, options = {}) {
    return new Promise((resolve, reject) => {
        fs.readdir(dirPath, options, (err, files) => {
            if (err)
                return reject(err);
            return resolve(files);
        });
    });
}
exports.readdirPromise = readdirPromise;
/**
 * Sort-Function for FileInfo objects of Array. Sorts with name and file-type.
 *
 * @private
 * @param {FileInfo} a -
 * @param {FileInfo} b -
 * @returns {number} - 0|1|-1
 */
function sortFileInfo(a, b) {
    let comparison = 0;
    if (a.isDirectory && !b.isDirectory) {
        comparison = 1;
    }
    else if (!a.isDirectory && b.isDirectory) {
        comparison = -1;
    }
    else if (a.relPath > b.relPath) {
        comparison = 1;
    }
    else if (a.relPath < b.relPath) {
        comparison = -1;
    }
    return comparison;
}
/**
 * Recursively list all file paths in a directory.
 *
 * @memberof API
 * @param {string} dirPath - A directory path
 * @param {object} options - Optional parameters
 * @param {boolean} [options.isOnlyDir=false] - Exacting directories only
 * @param {boolean} [options.isOnlyFile=false] - Exacting files only
 * @param {boolean} [options.excludesSymlink=false] - Excluding symbolic-links
 * @param {string|RegExp} [options.matchedRegExp] - Ex. "\\d+\\.txt$"
 * @param {string|RegExp} [options.ignoredRegExp] - Ex. "[_\\-.]cache\\d+"
 * @param {boolean} [options.withFileTypes=false] - If true, return fs.Dirent[]
 * @param {string} [options._prefixDirName] - @private The internal option
 * @returns {Promise<string[]|FileInfo[]>} - { resolve:string, reject:Error }
 * @example
const { readdirRecursively } = require('@tuckn/fs-hospitality');

// D:\Test\
// │  FILE_ROOT1.TXT
// │  fileRoot2-Symlink.log
// │  fileRoot2.log
// │
// ├─DirBar
// │  │  fileBar1.txt
// │  │
// │  └─DirQuux
// │          fileQuux1-Symlink.txt
// │          fileQuux1.txt
// │
// ├─DirFoo
// └─DirFoo-Symlink

readdirRecursively('D:\\Test').then((files) => {
  console.dir(files);
  // Returns [
  //   'DirFoo-Symlink',
  //   'fileRoot2-Symlink.log',
  //   'fileRoot2.log',
  //   'FILE_ROOT1.TXT',
  //   'DirFoo',
  //   'DirBar',
  //   'DirBar\\fileBar1.txt',
  //   'DirBar\\DirQuux',
  //   'DirBar\\DirQuux\\fileQuux1-Symlink.txt',
  //   'DirBar\\DirQuux\\fileQuux1.txt' ]

readdirRecursively('D:\\Test', { withFileTypes: true }).then((files) => {
  console.dir(files);
  // Returns [
  //   {
  //     name: 'DirFoo-Symlink',
  //     relPath: 'DirFoo-Symlink',
  //     path: 'D:\\Test\\DirFoo-Symlink',
  //     isDirectory: false,
  //     isFile: false,
  //     isSymbolicLink: true
  //   },
  //   {
  //     name: 'fileRoot2-Symlink.log',
  //     relPath: 'fileRoot2-Symlink.log',
  //     path: 'D:\\Test\\fileRoot2-Symlink.log',
  //     isDirectory: false,
  //     isFile: false,
  //     isSymbolicLink: true
  //   },
  //   {
  //     name: 'fileRoot2.log',
  //     relPath: 'fileRoot2.log',
  //     path: 'D:\\Test\\fileRoot2.log',
  //     isDirectory: false,
  //     isFile: true,
  //     isSymbolicLink: false
  //   },
  //   {
  //     name: 'FILE_ROOT1.TXT',
  //     relPath: 'FILE_ROOT1.TXT',
  //     path: 'D:\\Test\\FILE_ROOT1.TXT',
  //     isDirectory: false,
  //     isFile: true,
  //     isSymbolicLink: false
  //   },
  //   {
  //     name: 'DirFoo',
  //     relPath: 'DirFoo',
  //     path: 'D:\\Test\\DirFoo',
  //     isDirectory: true,
  //     isFile: false,
  //     isSymbolicLink: false
  //   },
  //   {
  //     name: 'DirBar',
  //     relPath: 'DirBar',
  //     path: 'D:\\Test\\DirBar',
  //     isDirectory: true,
  //     isFile: false,
  //     isSymbolicLink: false
  //   },
  //   {
  //     name: 'fileBar1.txt',
  //     relPath: 'DirBar\\fileBar1.txt',
  //     path: 'D:\\Test\\DirBar\\fileBar1.txt',
  //     isDirectory: false,
  //     isFile: true,
  //     isSymbolicLink: false
  //   },
  //   {
  //     name: 'DirQuux',
  //     relPath: 'DirBar\\DirQuux',
  //     path: 'D:\\Test\\DirBar\\DirQuux',
  //     isDirectory: true,
  //     isFile: false,
  //     isSymbolicLink: false
  //   },
  //   {
  //     name: 'fileQuux1-Symlink.txt',
  //     relPath: 'DirBar\\DirQuux\\fileQuux1-Symlink.txt',
  //     path: 'D:\\Test\\DirBar\\DirQuux\\fileQuux1-Symlink.txt',
  //     isDirectory: false,
  //     isFile: false,
  //     isSymbolicLink: true
  //   },
  //   {
  //     name: 'fileQuux1.txt',
  //     relPath: 'DirBar\\DirQuux\\fileQuux1.txt',
  //     path: 'D:\\Test\\DirBar\\DirQuux\\fileQuux1.txt',
  //     isDirectory: false,
  //     isFile: true,
  //     isSymbolicLink: false
  //   }
  // ]
});
 */
async function readdirRecursively(dirPath, options = {}) {
    // Get the all of top files
    const dirents = (await readdirPromise(dirPath, {
        withFileTypes: true,
    }));
    // Filtering Options
    const isOnlyFile = _.get(options, 'isOnlyFile', false);
    const isOnlyDir = _.get(options, 'isOnlyDir', false);
    const excludesSymlink = _.get(options, 'excludesSymlink', false);
    const matchedRegExp = _.get(options, 'matchedRegExp', null);
    let mtchRE = null;
    if (matchedRegExp) {
        if (_.isRegExp(matchedRegExp)) {
            mtchRE = matchedRegExp;
        }
        else {
            mtchRE = new RegExp(matchedRegExp, 'i');
        }
    }
    const ignoredRegExp = _.get(options, 'ignoredRegExp', null);
    let ignrRE = null;
    if (ignoredRegExp) {
        if (_.isRegExp(ignoredRegExp)) {
            ignrRE = ignoredRegExp;
        }
        else {
            ignrRE = new RegExp(ignoredRegExp, 'i');
        }
    }
    const _prefixDirName = _.get(options, '_prefixDirName', '');
    // let files: string[] | FileInfo[] = [];
    const files = [];
    const dirs = [];
    dirents.forEach((dirent) => {
        const relPath = path.join(_prefixDirName, dirent.name);
        if (!dirent.isDirectory()) {
            // Filtering
            if (isOnlyDir)
                return;
            if (excludesSymlink && dirent.isSymbolicLink())
                return;
            if (mtchRE && !mtchRE.test(relPath))
                return;
            if (ignrRE && ignrRE.test(relPath))
                return;
            files.push({
                name: dirent.name,
                relPath,
                path: path.resolve(dirPath, dirent.name),
                isDirectory: false,
                isFile: dirent.isFile(),
                isSymbolicLink: dirent.isSymbolicLink(),
            });
        }
        else {
            // @note
            // The dires will be filterd after getting subdirectory information
            dirs.push({
                name: dirent.name,
                relPath,
                path: path.resolve(dirPath, dirent.name),
                isDirectory: true,
                isFile: false,
                isSymbolicLink: dirent.isSymbolicLink(),
            });
        }
    });
    // Get the all of sub directoies files recursively
    // let dirsBranches: string[] | FileInfo[] = [];
    let dirsBranches = [];
    await Promise.all(dirs.map(async (dir) => {
        const subDir = (await readdirRecursively(dir.path, {
            ...options,
            withFileTypes: true,
            _prefixDirName: dir.relPath,
        }));
        // Filtering the top directory
        if (!isOnlyFile &&
            !(excludesSymlink && dir.isSymbolicLink) &&
            (!mtchRE || (mtchRE && mtchRE.test(dir.relPath))) &&
            !(ignrRE && ignrRE.test(dir.relPath))) {
            dirsBranches = dirsBranches.concat(dir);
        }
        if (subDir.length > 0)
            dirsBranches = dirsBranches.concat(subDir);
    }));
    // Join
    let rtnFilesInfo = [];
    if (files.length > 0)
        rtnFilesInfo = rtnFilesInfo.concat(files);
    if (dirsBranches.length > 0)
        rtnFilesInfo = rtnFilesInfo.concat(dirsBranches);
    // @todo
    // Sort
    // files.sort(sortFileInfo);
    const withFileTypes = _.get(options, 'withFileTypes', false);
    if (!withFileTypes) {
        return rtnFilesInfo.map((file) => file.relPath);
    }
    return rtnFilesInfo;
}
exports.readdirRecursively = readdirRecursively;
/**
 * The synchronous version of this API: readdirRecursivelySync().
 *
 * @memberof API
 * @param {string} dirPath - A directory path
 * @param {object} options - See {@link API.readdirRecursively}
 * @returns {string[]|FileInfo[]} -
 * @example
const { readdirRecursivelySync } = require('@tuckn/fs-hospitality');

// D:\Test\
// │  FILE_ROOT1.TXT
// │  fileRoot2-Symlink.log
// │  fileRoot2.log
// │
// ├─DirBar
// │  │  fileBar1.txt
// │  │
// │  └─DirQuux
// │          fileQuux1-Symlink.txt
// │          fileQuux1.txt
// │
// ├─DirFoo
// └─DirFoo-Symlink

const files = readdirRecursivelySync('D:\\Test');
console.dir(files);
// Returns [
//   'DirFoo-Symlink',
//   'fileRoot2-Symlink.log',
//   'fileRoot2.log',
//   'FILE_ROOT1.TXT',
//   'DirFoo',
//   'DirBar',
//   'DirBar\\fileBar1.txt',
//   'DirBar\\DirQuux',
//   'DirBar\\DirQuux\\fileQuux1-Symlink.txt',
//   'DirBar\\DirQuux\\fileQuux1.txt' ]
 */
function readdirRecursivelySync(dirPath, options = {}) {
    // Get the all of top files
    const dirents = fs.readdirSync(dirPath, {
        withFileTypes: true,
    });
    // Filtering Options
    const isOnlyFile = _.get(options, 'isOnlyFile', false);
    const isOnlyDir = _.get(options, 'isOnlyDir', false);
    const excludesSymlink = _.get(options, 'excludesSymlink', false);
    const matchedRegExp = _.get(options, 'matchedRegExp', null);
    let mtchRE = null;
    if (matchedRegExp) {
        if (_.isRegExp(matchedRegExp)) {
            mtchRE = matchedRegExp;
        }
        else {
            mtchRE = new RegExp(matchedRegExp, 'i');
        }
    }
    const ignoredRegExp = _.get(options, 'ignoredRegExp', null);
    let ignrRE = null;
    if (ignoredRegExp) {
        if (_.isRegExp(ignoredRegExp)) {
            ignrRE = ignoredRegExp;
        }
        else {
            ignrRE = new RegExp(ignoredRegExp, 'i');
        }
    }
    const _prefixDirName = _.get(options, '_prefixDirName', '');
    // let files: string[] | FileInfo[] = [];
    const files = [];
    const dirs = [];
    dirents.forEach((dirent) => {
        const relPath = path.join(_prefixDirName, dirent.name);
        if (!dirent.isDirectory()) {
            // Filtering
            if (isOnlyDir)
                return;
            if (excludesSymlink && dirent.isSymbolicLink())
                return;
            if (mtchRE && !mtchRE.test(relPath))
                return;
            if (ignrRE && ignrRE.test(relPath))
                return;
            files.push({
                name: dirent.name,
                relPath,
                path: path.resolve(dirPath, dirent.name),
                isDirectory: false,
                isFile: dirent.isFile(),
                isSymbolicLink: dirent.isSymbolicLink(),
            });
        }
        else {
            // @note
            // The dires will be filtered after getting subdirectory information
            dirs.push({
                name: dirent.name,
                relPath,
                path: path.resolve(dirPath, dirent.name),
                isDirectory: true,
                isFile: false,
                isSymbolicLink: dirent.isSymbolicLink(),
            });
        }
    });
    // Get the all of sub directories files recursively
    // let dirsBranches: string[] | FileInfo[] = [];
    let dirsBranches = [];
    dirs.forEach((dir) => {
        const subDir = readdirRecursivelySync(dir.path, {
            ...options,
            withFileTypes: true,
            _prefixDirName: dir.relPath,
        });
        // Filtering the top directory
        if (!isOnlyFile &&
            !(excludesSymlink && dir.isSymbolicLink) &&
            (!mtchRE || (mtchRE && mtchRE.test(dir.relPath))) &&
            !(ignrRE && ignrRE.test(dir.relPath))) {
            dirsBranches = dirsBranches.concat(dir);
        }
        if (subDir.length > 0)
            dirsBranches = dirsBranches.concat(subDir);
    });
    // Join
    let rtnFilesInfo = [];
    if (files.length > 0)
        rtnFilesInfo = rtnFilesInfo.concat(files);
    if (dirsBranches.length > 0)
        rtnFilesInfo = rtnFilesInfo.concat(dirsBranches);
    // @todo
    // Sort
    // files.sort(sortFileInfo);
    const withFileTypes = _.get(options, 'withFileTypes', false);
    if (!withFileTypes) {
        return rtnFilesInfo.map((file) => file.relPath);
    }
    return rtnFilesInfo;
}
exports.readdirRecursivelySync = readdirRecursivelySync;
//# sourceMappingURL=fs-hospitality.js.map