2025-05-12 05:38:44 +09:00

256 lines
7.5 KiB
JavaScript

"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const path_1 = __importDefault(require("path"));
const immutable_1 = __importDefault(require("immutable"));
const stream_1 = __importDefault(require("stream"));
const file_1 = __importDefault(require("./file"));
const promise_1 = __importDefault(require("../utils/promise"));
const error_1 = __importDefault(require("../utils/error"));
const path_2 = __importDefault(require("../utils/path"));
class FS extends immutable_1.default.Record({
root: String(),
fsExists: Function(),
fsReadFile: Function(),
fsStatFile: Function(),
fsReadDir: Function(),
fsLoadObject: null,
fsReadAsStream: null
}) {
/**
Return path to the root
@return {string}
*/
getRoot() {
return this.get("root");
}
/**
Verify that a file is in the fs scope
@param {string} filename
@return {boolean}
*/
isInScope(filename) {
const rootPath = this.getRoot();
filename = path_1.default.join(rootPath, filename);
return path_2.default.isInRoot(rootPath, filename);
}
/**
Resolve a file in this FS
@return {string}
*/
resolve(...args) {
const rootPath = this.getRoot();
let filename = path_1.default.join.apply(path_1.default, [rootPath].concat(args));
filename = path_1.default.normalize(filename);
if (!this.isInScope(filename)) {
throw error_1.default.FileOutOfScopeError({
filename: filename,
root: rootPath
});
}
return filename;
}
/**
Check if a file exists, run a Promise(true) if that's the case, Promise(false) otherwise
@param {string} filename
@return {Promise<Boolean>}
*/
exists(filename) {
const that = this;
return (0, promise_1.default)().then(() => {
filename = that.resolve(filename);
const exists = that.get("fsExists");
return exists(filename);
});
}
/**
Read a file and returns a promise with the content as a buffer
@param {string} filename
@return {Promise<Buffer>}
*/
read(filename) {
const that = this;
return (0, promise_1.default)().then(() => {
filename = that.resolve(filename);
const read = that.get("fsReadFile");
return read(filename);
});
}
/**
Read a file as a string (utf-8)
@return {Promise<String>}
*/
readAsString(filename, encoding = "utf8") {
return this.read(filename).then((buf) => {
return buf.toString(encoding);
});
}
/**
Read file as a stream
@param {string} filename
@return {Promise<Stream>}
*/
readAsStream(filename) {
const that = this;
const filepath = that.resolve(filename);
const fsReadAsStream = this.get("fsReadAsStream");
if (fsReadAsStream) {
return (0, promise_1.default)(fsReadAsStream(filepath));
}
return this.read(filename).then((buf) => {
const bufferStream = new stream_1.default.PassThrough();
bufferStream.end(buf);
return bufferStream;
});
}
/**
Read stat infos about a file
@param {string} filename
@return {Promise<File>}
*/
statFile(filename) {
const that = this;
return (0, promise_1.default)()
.then(() => {
const filepath = that.resolve(filename);
const stat = that.get("fsStatFile");
return stat(filepath);
})
.then((stat) => {
return file_1.default.createFromStat(filename, stat);
});
}
/**
List files/directories in a directory.
Directories ends with '/'
@param {string} dirname
@return {Promise<List<String>>}
*/
readDir(dirname) {
const that = this;
return (0, promise_1.default)()
.then(() => {
const dirpath = that.resolve(dirname);
const readDir = that.get("fsReadDir");
return readDir(dirpath);
})
.then((files) => {
return immutable_1.default.List(files);
});
}
/**
List only files in a diretcory
Directories ends with '/'
@param {string} dirname
@return {Promise<List<String>>}
*/
listFiles(dirname) {
return this.readDir(dirname).then((files) => {
return files.filterNot(pathIsFolder);
});
}
/**
List all files in a directory
@param {string} dirName
@param {Function(dirName)} filterFn: call it for each file/directory to test if it should stop iterating
@return {Promise<List<String>>}
*/
listAllFiles(dirName, filterFn) {
const that = this;
dirName = dirName || ".";
return this.readDir(dirName).then((files) => {
return promise_1.default.reduce(files, (out, file) => {
const isDirectory = pathIsFolder(file);
const newDirName = path_1.default.join(dirName, file);
if (filterFn && filterFn(newDirName) === false) {
return out;
}
if (!isDirectory) {
return out.push(newDirName);
}
return that.listAllFiles(newDirName, filterFn).then((inner) => {
return out.concat(inner);
});
}, immutable_1.default.List());
});
}
/**
Find a file in a folder (case insensitive)
Return the found filename
@param {string} dirname
@param {string} filename
@return {Promise<String>}
*/
findFile(dirname, filename) {
return this.listFiles(dirname).then((files) => {
return files.find((file) => {
return file.toLowerCase() == filename.toLowerCase();
});
});
}
/**
Load a JSON file
By default, fs only supports JSON
@param {string} filename
@return {Promise<Object>}
*/
loadAsObject(filename) {
const that = this;
const fsLoadObject = this.get("fsLoadObject");
return this.exists(filename).then((exists) => {
if (!exists) {
const err = new Error("Module doesn't exist");
// @ts-expect-error ts-migrate(2339) FIXME: Property 'code' does not exist on type 'Error'.
err.code = "MODULE_NOT_FOUND";
throw err;
}
if (fsLoadObject) {
return fsLoadObject(that.resolve(filename));
}
else {
return that.readAsString(filename).then((str) => {
return JSON.parse(str);
});
}
});
}
/**
Create a FS instance
@param {Object} def
@return {FS}
*/
static create(def) {
return new FS(def);
}
/**
Create a new FS instance with a reduced scope
@param {FS} fs
@param {string} scope
@return {FS}
*/
static reduceScope(fs, scope) {
return fs.set("root", path_1.default.join(fs.getRoot(), scope));
}
}
// .readdir return files/folder as a list of string, folder ending with '/'
function pathIsFolder(filename) {
const lastChar = filename[filename.length - 1];
return lastChar == "/" || lastChar == "\\";
}
exports.default = FS;