UNPKG

@ayonli/jsext

Version:

A JavaScript extension package for building strong and modern applications.

574 lines (570 loc) 19.5 kB
'use strict'; var async = require('../async.js'); var filetype = require('../filetype.js'); var object = require('../object.js'); var path = require('../path.js'); var reader = require('../reader.js'); var string = require('../string.js'); var result = require('../result.js'); var fs_util = require('./util.js'); require('../error.js'); var fs_errors = require('./errors.js'); var path_util = require('../path/util.js'); var reader_util = require('../reader/util.js'); var error_common = require('../error/common.js'); /** * A slim version of the `fs` module for the browser. * * Normally, we should just use the `fs` module, however, if we don't want * to include other parts that are not needed in the browser, we can use this * module instead. * @module */ const EOL = "\n"; /** * Obtains the directory handle of the given path. * * NOTE: This function is only available in the browser. * * NOTE: If the `path` is not provided or is empty, the root directory handle * will be returned. * * @example * ```ts * // with the default storage * import { getDirHandle } from "@ayonli/jsext/fs"; * * const dir = await getDirHandle("/path/to/dir"); * ``` * * @example * ```ts * // with a user-selected directory as root (Chromium only) * import { getDirHandle } from "@ayonli/jsext/fs"; * * const root = await window.showDirectoryPicker(); * const dir = await getDirHandle("/path/to/dir", { root }); * ``` * * @example * ```ts * // create the directory if not exist * import { getDirHandle } from "@ayonli/jsext/fs"; * * const dir = await getDirHandle("/path/to/dir", { create: true, recursive: true }); * ``` * * @example * ```ts * // return the root directory handle * import { getDirHandle } from "@ayonli/jsext/fs"; * * const root = await getDirHandle(); * ``` */ async function getDirHandle(path = "", options = {}) { var _a; if (typeof location === "object" && typeof location.origin === "string") { path = string.stripStart(path, location.origin); } const { create = false, recursive = false } = options; const paths = path_util.split(string.stripStart(path, "/")).filter(p => p !== "."); const root = (_a = options.root) !== null && _a !== void 0 ? _a : await fs_util.rawOp(navigator.storage.getDirectory(), "directory"); let dir = root; for (let i = 0; i < paths.length; i++) { const _path = paths[i]; dir = await fs_util.rawOp(dir.getDirectoryHandle(_path, { create: create && (recursive || (i === paths.length - 1)), }), "directory"); } return dir; } /** * Obtains the file handle of the given path. * * NOTE: This function is only available in the browser. * * @example * ```ts * // with the default storage * import { getFileHandle } from "@ayonli/jsext/fs"; * * const file = await getFileHandle("/path/to/file.txt"); * ``` * * @example * ```ts * // with a user-selected directory as root (Chromium only) * import { getFileHandle } from "@ayonli/jsext/fs"; * * const root = await window.showDirectoryPicker(); * const file = await getFileHandle("/path/to/file.txt", { root }); * ``` * * @example * ```ts * // create the file if not exist * import { getFileHandle } from "@ayonli/jsext/fs"; * * const file = await getFileHandle("/path/to/file.txt", { create: true }); * ``` */ async function getFileHandle(path$1, options = {}) { var _a; const dirPath = path.dirname(path$1); const name = path.basename(path$1); const dir = await getDirHandle(dirPath, { root: options.root }); return await fs_util.rawOp(dir.getFileHandle(name, { create: (_a = options.create) !== null && _a !== void 0 ? _a : false, }), "file"); } async function exists(path, options = {}) { try { await stat(path, options); return true; } catch (err) { if (err instanceof Exception) { if (err.name === "NotFoundError") { return false; } } throw err; } } async function stat(target, options = {}) { var _a, _b, _c, _d, _e; if (typeof target === "object") { if (target.kind === "file") { const info = await fs_util.rawOp(target.getFile(), "file"); return { name: target.name, kind: "file", size: info.size, type: (_b = (_a = info.type) !== null && _a !== void 0 ? _a : filetype.getMIME(path.extname(target.name))) !== null && _b !== void 0 ? _b : "", mtime: new Date(info.lastModified), atime: null, birthtime: null, mode: 0, uid: 0, gid: 0, isBlockDevice: false, isCharDevice: false, isFIFO: false, isSocket: false, }; } else { return { name: target.name, kind: "directory", size: 0, type: "", mtime: null, atime: null, birthtime: null, mode: 0, uid: 0, gid: 0, isBlockDevice: false, isCharDevice: false, isFIFO: false, isSocket: false, }; } } else { const { value: file, error: err, } = await result.try_(getFileHandle(target, options)); if (file) { const info = await fs_util.rawOp(file.getFile(), "file"); return { name: info.name, kind: "file", size: info.size, type: (_d = (_c = info.type) !== null && _c !== void 0 ? _c : filetype.getMIME(path.extname(info.name))) !== null && _d !== void 0 ? _d : "", mtime: new Date(info.lastModified), atime: null, birthtime: null, mode: 0, uid: 0, gid: 0, isBlockDevice: false, isCharDevice: false, isFIFO: false, isSocket: false, }; } else if (((_e = object.as(err, Exception)) === null || _e === void 0 ? void 0 : _e.name) === "IsDirectoryError") { return { name: path.basename(target), kind: "directory", size: 0, type: "", mtime: null, atime: null, birthtime: null, mode: 0, uid: 0, gid: 0, isBlockDevice: false, isCharDevice: false, isFIFO: false, isSocket: false, }; } else { throw err; } } } async function mkdir(path, options = {}) { if (await exists(path, { root: options.root })) { throw new error_common.AlreadyExistsError(`File or folder already exists, mkdir '${path}'`); } await getDirHandle(path, { ...options, create: true }); } async function ensureDir(path, options = {}) { var _a; if (await exists(path, options)) { return; } try { await mkdir(path, { ...options, recursive: true }); } catch (err) { if (((_a = object.as(err, Exception)) === null || _a === void 0 ? void 0 : _a.name) === "AlreadyExistsError") { return; } else { throw err; } } } async function* readDir(target, options = {}) { const handle = typeof target === "object" ? target : await getDirHandle(target, options); yield* readDirHandle(handle, options); } async function* readDirHandle(dir, options = {}) { const { base = "", recursive = false } = options; const entries = dir.entries(); for await (const [_, entry] of entries) { const _entry = fs_util.fixDirEntry({ name: entry.name, kind: entry.kind, relativePath: path.join(base, entry.name), handle: entry, }); yield _entry; if (recursive && entry.kind === "directory") { yield* readDirHandle(entry, { base: _entry.relativePath, recursive, }); } } } async function readTree(target, options = {}) { const entries = (await reader.readAsArray(readDir(target, { ...options, recursive: true }))); const tree = fs_util.makeTree(target, entries, true); if (!tree.handle && options.root) { tree.handle = options.root; } return tree; } async function readFile(target, options = {}) { const handle = typeof target === "object" ? target : await getFileHandle(target, { root: options.root }); const file = await fs_util.rawOp(handle.getFile(), "file"); const arr = new Uint8Array(file.size); let offset = 0; let reader = reader_util.toAsyncIterable(file.stream()); if (options.signal) { reader = async.abortable(reader, options.signal); } for await (const chunk of reader) { arr.set(chunk, offset); offset += chunk.length; } return arr; } async function readFileAsText(target, options = {}) { const { encoding, ...rest } = options; const file = await readFile(target, rest); return await reader.readAsText(file, encoding); } async function readFileAsFile(target, options = {}) { const handle = typeof target === "object" ? target : await getFileHandle(target, { root: options.root }); const file = await fs_util.rawOp(handle.getFile(), "file"); return fs_util.fixFileType(file); } async function writeFile(target, data, options = {}) { const handle = typeof target === "object" ? target : await getFileHandle(target, { root: options.root, create: true }); const writer = await createFileHandleWritableStream(handle, options); if (options.signal) { const { signal } = options; if (signal.aborted) { await writer.abort(signal.reason); throw fs_util.wrapFsError(signal.reason, "file"); } else { signal.addEventListener("abort", () => { writer.abort(signal.reason); }); } } try { if (data instanceof Blob) { await data.stream().pipeTo(writer); } else if (data instanceof ReadableStream) { await data.pipeTo(writer); } else { await writer.write(data); await writer.close(); } } catch (err) { throw fs_util.wrapFsError(err, "file"); } } async function writeLines(target, lines, options = {}) { const current = await readFileAsText(target, options).catch(err => { var _a; if (((_a = object.as(err, Exception)) === null || _a === void 0 ? void 0 : _a.name) !== "NotFoundError") { throw err; } else { return ""; } }); const lineEndings = current.match(/\r?\n/g); let eol = EOL; if (lineEndings) { const crlf = lineEndings.filter(e => e === "\r\n").length; const lf = lineEndings.length - crlf; eol = crlf > lf ? "\r\n" : "\n"; } let content = lines.join(eol); if (!content.endsWith(eol)) { if (eol === "\r\n" && content.endsWith("\r")) { content += "\n"; } else { content += eol; } } if (options.append && !current.endsWith(eol) && !content.startsWith(eol)) { if (eol === "\r\n" && current.endsWith("\r")) { if (!content.startsWith("\n")) { content = "\n" + content; } } else { content = eol + content; } } await writeFile(target, content, options); } async function truncate(target, size = 0, options = {}) { const handle = typeof target === "object" ? target : await getFileHandle(target, { root: options.root }); try { const writer = await handle.createWritable({ keepExistingData: true }); await writer.truncate(size); await writer.close(); } catch (err) { throw fs_util.wrapFsError(err, "file"); } } async function remove(path$1, options = {}) { const parent = path.dirname(path$1); const name = path.basename(path$1); const dir = await getDirHandle(parent, { root: options.root }); await fs_util.rawOp(dir.removeEntry(name, options), "directory"); } async function rename(oldPath, newPath, options = {}) { return await copyInBrowser(oldPath, newPath, { root: options.root, recursive: true, move: true, }); } async function copy(src, dest, options = {}) { return copyInBrowser(src, dest, options); } async function copyInBrowser(src, dest, options = {}) { var _a, _b; if (typeof src === "object" && typeof dest !== "object") { throw new TypeError("The destination must be a FileSystemHandle."); } else if (typeof dest === "object" && typeof src !== "object") { throw new TypeError("The source must be a FileSystemHandle."); } else if (typeof src === "object" && typeof dest === "object") { if (src.kind === "file") { if (dest.kind === "file") { return await copyFileHandleToFileHandle(src, dest); } else { return await copyFileHandleToDirHandle(src, dest); } } else if (dest.kind === "directory") { if (!options.recursive) { throw new fs_errors.InvalidOperationError("Cannot copy a directory without the 'recursive' option"); } return await copyDirHandleToDirHandle(src, dest); } else { throw new fs_errors.NotDirectoryError("The destination location is not a directory"); } } const oldParent = path.dirname(src); const oldName = path.basename(src); let oldDir = await getDirHandle(oldParent, { root: options.root }); const { value: oldFile, error: oldErr, } = await result.try_(fs_util.rawOp(oldDir.getFileHandle(oldName), "file")); if (oldFile) { const newParent = path.dirname(dest); const newName = path.basename(dest); let newDir = await getDirHandle(newParent, { root: options.root }); const { error: newErr, value: newFile, } = await result.try_(fs_util.rawOp(newDir.getFileHandle(newName, { create: true, }), "file")); if (newFile) { await copyFileHandleToFileHandle(oldFile, newFile); if (options.move) { await fs_util.rawOp(oldDir.removeEntry(oldName), "directory"); } } else if (((_a = object.as(newErr, Exception)) === null || _a === void 0 ? void 0 : _a.name) === "IsDirectoryError" && !options.move) { // The destination is a directory, copy the file into the new path // with the old name. newDir = await fs_util.rawOp(newDir.getDirectoryHandle(newName), "directory"); await copyFileHandleToDirHandle(oldFile, newDir); } else { throw newErr; } } else if (((_b = object.as(oldErr, Exception)) === null || _b === void 0 ? void 0 : _b.name) === "IsDirectoryError") { if (!options.recursive) { throw new fs_errors.InvalidOperationError("Cannot copy a directory without the 'recursive' option"); } const parent = oldDir; oldDir = await fs_util.rawOp(oldDir.getDirectoryHandle(oldName), "directory"); const newDir = await getDirHandle(dest, { root: options.root, create: true }); await copyDirHandleToDirHandle(oldDir, newDir); if (options.move) { await fs_util.rawOp(parent.removeEntry(oldName, { recursive: true }), "directory"); } } else { throw oldErr; } } async function copyFileHandleToFileHandle(src, dest) { try { const srcFile = await src.getFile(); const destFile = await dest.createWritable(); await srcFile.stream().pipeTo(destFile); } catch (err) { throw fs_util.wrapFsError(err, "file"); } } async function copyFileHandleToDirHandle(src, dest) { try { const srcFile = await src.getFile(); const newFile = await dest.getFileHandle(src.name, { create: true }); const destFile = await newFile.createWritable(); await srcFile.stream().pipeTo(destFile); } catch (err) { throw fs_util.wrapFsError(err, "file"); } } async function copyDirHandleToDirHandle(src, dest) { const entries = src.entries(); for await (const [_, entry] of entries) { if (entry.kind === "file") { try { const oldFile = await entry.getFile(); const newFile = await dest.getFileHandle(entry.name, { create: true, }); const reader = oldFile.stream(); const writer = await newFile.createWritable(); await reader.pipeTo(writer); } catch (err) { throw fs_util.wrapFsError(err, "file"); } } else { const newSubDir = await fs_util.rawOp(dest.getDirectoryHandle(entry.name, { create: true, }), "directory"); await copyDirHandleToDirHandle(entry, newSubDir); } } } function createReadableStream(target, options = {}) { return reader_util.resolveByteStream((async () => { const handle = typeof target === "object" ? target : await getFileHandle(target, { root: options.root }); const file = await fs_util.rawOp(handle.getFile(), "file"); return file.stream(); })()); } function createWritableStream(target, options = {}) { const { readable, writable } = new TransformStream(); const getHandle = typeof target === "object" ? Promise.resolve(target) : getFileHandle(target, { root: options.root, create: true }); getHandle.then(handle => createFileHandleWritableStream(handle, options)) .then(stream => readable.pipeTo(stream)); return writable; } async function createFileHandleWritableStream(handle, options) { var _a; const stream = await fs_util.rawOp(handle.createWritable({ keepExistingData: (_a = options === null || options === void 0 ? void 0 : options.append) !== null && _a !== void 0 ? _a : false, }), "file"); if (options.append) { const file = await fs_util.rawOp(handle.getFile(), "file"); file.size && stream.seek(file.size); } return stream; } exports.EOL = EOL; exports.copy = copy; exports.createReadableStream = createReadableStream; exports.createWritableStream = createWritableStream; exports.ensureDir = ensureDir; exports.exists = exists; exports.getDirHandle = getDirHandle; exports.getFileHandle = getFileHandle; exports.mkdir = mkdir; exports.readDir = readDir; exports.readFile = readFile; exports.readFileAsFile = readFileAsFile; exports.readFileAsText = readFileAsText; exports.readTree = readTree; exports.remove = remove; exports.rename = rename; exports.stat = stat; exports.truncate = truncate; exports.writeFile = writeFile; exports.writeLines = writeLines; //# sourceMappingURL=web.js.map