413 lines
9.7 KiB
JavaScript
413 lines
9.7 KiB
JavaScript
// @ts-check
|
|
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import process from "node:process";
|
|
import { BitArray, Ok, Error as GError, toList } from "./gleam.mjs";
|
|
import * as $simplifile from "./simplifile.mjs";
|
|
|
|
/**
|
|
* Read the contents of a file as a BitArray
|
|
*
|
|
* @param {string} filepath
|
|
* @returns {Ok | GError} A Result with the BitArray of the file contents
|
|
*/
|
|
export function readBits(filepath) {
|
|
return gleamResult(() => {
|
|
const contents = fs.readFileSync(path.normalize(filepath));
|
|
return new BitArray(new Uint8Array(contents));
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Write the given BitArray to a file
|
|
*
|
|
* @param {string} filepath
|
|
* @param {BitArray} contents
|
|
* @returns {Ok | GError}
|
|
*/
|
|
export function writeBits(filepath, contents) {
|
|
return gleamResult(() =>
|
|
fs.writeFileSync(path.normalize(filepath), toUint8Array(contents))
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Append the given BitArray to a file
|
|
*
|
|
* @param {string} filepath
|
|
* @param {BitArray} contents
|
|
* @returns {Ok | GError}
|
|
*/
|
|
export function appendBits(filepath, contents) {
|
|
return gleamResult(() =>
|
|
fs.appendFileSync(path.normalize(filepath), toUint8Array(contents))
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Returns a BitArray as an aligned Uint8Array. Avoids a copy when possible.
|
|
*
|
|
* @param {BitArray} contents
|
|
* @returns Uint8Array
|
|
*/
|
|
function toUint8Array(contents) {
|
|
if (contents.bitSize % 8 !== 0) {
|
|
throw new GError(new $simplifile.Einval());
|
|
}
|
|
|
|
let buffer = contents.rawBuffer;
|
|
if (contents.bitOffset !== 0) {
|
|
buffer = new Uint8Array(contents.byteSize);
|
|
for (let i = 0; i < buffer.length; i++) {
|
|
buffer[i] = contents.byteAt(i);
|
|
}
|
|
}
|
|
|
|
return buffer;
|
|
}
|
|
|
|
/**
|
|
* Check whether a file exists at the given path
|
|
*
|
|
* @param {string} filepath
|
|
* @returns {Ok | GError}
|
|
*/
|
|
export function isFile(filepath) {
|
|
try {
|
|
return new Ok(fs.statSync(path.normalize(filepath)).isFile());
|
|
} catch (e) {
|
|
if (e.code === "ENOENT") {
|
|
return new Ok(false);
|
|
} else {
|
|
return new GError(cast_error(e.code));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check whether a symbolic link exists at the given path
|
|
*
|
|
* @param {string} filepath
|
|
* @returns {Ok | GError}
|
|
*/
|
|
export function isSymlink(filepath) {
|
|
try {
|
|
return new Ok(fs.lstatSync(path.normalize(filepath)).isSymbolicLink());
|
|
} catch (e) {
|
|
if (e.code === "ENOENT") {
|
|
return new Ok(false);
|
|
} else {
|
|
return new GError(cast_error(e.code));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check whether a directory exists at the given path
|
|
*
|
|
* @param {string} filepath
|
|
* @returns {Ok | GError}
|
|
*/
|
|
export function isDirectory(filepath) {
|
|
try {
|
|
return new Ok(fs.statSync(path.normalize(filepath)).isDirectory());
|
|
} catch (e) {
|
|
if (e.code === "ENOENT") {
|
|
return new Ok(false);
|
|
} else {
|
|
return new GError(cast_error(e.code));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create the symbolic link called path pointing to target
|
|
*
|
|
* @param {string} target
|
|
* @param {string} path
|
|
* @returns {Ok | GError}
|
|
*/
|
|
export function createSymlink(target, path) {
|
|
return gleamResult(() => fs.symlinkSync(target, path));
|
|
}
|
|
|
|
/**
|
|
* Create the "hard link" called path pointing to the target
|
|
*
|
|
* @param {string} target
|
|
* @param {string} path
|
|
* returns {ok | GError}
|
|
*/
|
|
export function createLink(target, path) {
|
|
return gleamResult(() => fs.linkSync(target, path));
|
|
}
|
|
|
|
/**
|
|
* Create a directory at the given filepath
|
|
*
|
|
* @param {string} filepath
|
|
* @returns {Ok | GError}
|
|
*/
|
|
export function createDirectory(filepath) {
|
|
return gleamResult(() => fs.mkdirSync(path.normalize(filepath)));
|
|
}
|
|
|
|
/**
|
|
* Recursively create a directory structure from the given path.
|
|
*
|
|
* @param {string} filepath
|
|
* @returns {Ok | GError}
|
|
*/
|
|
export function createDirAll(filepath) {
|
|
return gleamResult(() => {
|
|
fs.mkdirSync(path.normalize(filepath), { recursive: true });
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Recursively delete a directory or delete a file at the given path.
|
|
*
|
|
* @param {string} fileOrDirPath
|
|
* @returns {Ok | GError}
|
|
*/
|
|
export function delete_(fileOrDirPath) {
|
|
return gleamResult(() => {
|
|
const isDir = isDirectory(fileOrDirPath);
|
|
if (isDir instanceof Ok && isDir[0] === true) {
|
|
fs.rmSync(path.normalize(fileOrDirPath), { recursive: true });
|
|
} else {
|
|
fs.unlinkSync(path.normalize(fileOrDirPath));
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Specifically delete a single file.
|
|
*
|
|
* @param {string} filePath
|
|
* @returns {Ok | GError}
|
|
*/
|
|
export function deleteFile(filePath) {
|
|
return gleamResult(() => {
|
|
fs.unlinkSync(path.normalize(filePath));
|
|
})
|
|
}
|
|
|
|
/**
|
|
* List the contents of a directory.
|
|
*
|
|
* @param {string} filepath
|
|
* @returns {Ok | GError}
|
|
*/
|
|
export function readDirectory(filepath) {
|
|
return gleamResult(() => toList(fs.readdirSync(path.normalize(filepath))));
|
|
}
|
|
|
|
/**
|
|
* Copy a file to a new path.
|
|
*
|
|
* @param {string} srcpath
|
|
* @param {string} destpath
|
|
* @returns {Ok | GError}
|
|
*/
|
|
export function copyFile(srcpath, destpath) {
|
|
return gleamResult(() =>
|
|
fs.copyFileSync(path.normalize(srcpath), path.normalize(destpath)),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Move a file to the new path.
|
|
*
|
|
* @param {string} srcpath
|
|
* @param {string} destpath
|
|
* @returns {Ok | GError}
|
|
*/
|
|
export function renameFile(srcpath, destpath) {
|
|
return gleamResult(() =>
|
|
fs.renameSync(path.normalize(srcpath), path.normalize(destpath)),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Set the file permissions. Octal number should be in base 8.
|
|
*
|
|
* @param {string} filepath
|
|
* @param {number} octalNumber
|
|
* @returns {Ok | GError}
|
|
*/
|
|
export function setPermissionsOctal(filepath, octalNumber) {
|
|
return gleamResult(() => fs.chmodSync(path.normalize(filepath), octalNumber));
|
|
}
|
|
|
|
/**
|
|
* Return the current directory.
|
|
*
|
|
* @returns {Ok | GError} The current directory
|
|
*/
|
|
export function currentDirectory() {
|
|
return gleamResult(() => process.cwd());
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {string} filepath
|
|
* @returns {Ok | GError}
|
|
*/
|
|
export function fileInfo(filepath) {
|
|
return gleamResult(() => {
|
|
const stat = fs.statSync(path.normalize(filepath));
|
|
return new FileInfo(stat);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @param {string} filepath
|
|
* @returns {Ok | GError}
|
|
*/
|
|
export function linkInfo(filepath) {
|
|
return gleamResult(() => {
|
|
const stat = fs.lstatSync(path.normalize(filepath));
|
|
return new FileInfo(stat);
|
|
});
|
|
}
|
|
|
|
class FileInfo {
|
|
/**
|
|
* @param {fs.Stats} stat
|
|
*/
|
|
constructor(stat) {
|
|
this.size = stat.size;
|
|
this.mode = stat.mode;
|
|
this.nlinks = stat.nlink;
|
|
this.inode = stat.ino;
|
|
this.user_id = stat.uid;
|
|
this.group_id = stat.gid;
|
|
this.dev = stat.dev;
|
|
this.atime_seconds = Math.floor(stat.atimeMs / 1000);
|
|
this.mtime_seconds = Math.floor(stat.mtimeMs / 1000);
|
|
this.ctime_seconds = Math.floor(stat.ctimeMs / 1000);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Perform some operation and return a Gleam `Result(a, String)`
|
|
* where `a` is the type returned by the operation and the `String`
|
|
* is the error code.
|
|
*
|
|
* @param {function():any} op
|
|
* @returns {Ok | GError}
|
|
*/
|
|
function gleamResult(op) {
|
|
try {
|
|
const val = op();
|
|
return new Ok(val);
|
|
} catch (e) {
|
|
return new GError(cast_error(e.code));
|
|
}
|
|
}
|
|
|
|
function cast_error(error_code) {
|
|
switch (error_code) {
|
|
case "EACCES":
|
|
return new $simplifile.Eacces();
|
|
case "EAGAIN":
|
|
return new $simplifile.Eagain();
|
|
case "EBADF":
|
|
return new $simplifile.Ebadf();
|
|
case "EBADMSG":
|
|
return new $simplifile.Ebadmsg();
|
|
case "EBUSY":
|
|
return new $simplifile.Ebusy();
|
|
case "EDEADLK":
|
|
return new $simplifile.Edeadlk();
|
|
case "EDEADLOCK":
|
|
return new $simplifile.Edeadlock();
|
|
case "EDQUOT":
|
|
return new $simplifile.Edquot();
|
|
case "EEXIST":
|
|
return new $simplifile.Eexist();
|
|
case "EFAULT":
|
|
return new $simplifile.Efault();
|
|
case "EFBIG":
|
|
return new $simplifile.Efbig();
|
|
case "EFTYPE":
|
|
return new $simplifile.Eftype();
|
|
case "EINTR":
|
|
return new $simplifile.Eintr();
|
|
case "EINVAL":
|
|
return new $simplifile.Einval();
|
|
case "EIO":
|
|
return new $simplifile.Eio();
|
|
case "EISDIR":
|
|
return new $simplifile.Eisdir();
|
|
case "ELOOP":
|
|
return new $simplifile.Eloop();
|
|
case "EMFILE":
|
|
return new $simplifile.Emfile();
|
|
case "EMLINK":
|
|
return new $simplifile.Emlink();
|
|
case "EMULTIHOP":
|
|
return new $simplifile.Emultihop();
|
|
case "ENAMETOOLONG":
|
|
return new $simplifile.Enametoolong();
|
|
case "ENFILE":
|
|
return new $simplifile.Enfile();
|
|
case "ENOBUFS":
|
|
return new $simplifile.Enobufs();
|
|
case "ENODEV":
|
|
return new $simplifile.Enodev();
|
|
case "ENOLCK":
|
|
return new $simplifile.Enolck();
|
|
case "ENOLINK":
|
|
return new $simplifile.Enolink();
|
|
case "ENOENT":
|
|
return new $simplifile.Enoent();
|
|
case "ENOMEM":
|
|
return new $simplifile.Enomem();
|
|
case "ENOSPC":
|
|
return new $simplifile.Enospc();
|
|
case "ENOSR":
|
|
return new $simplifile.Enosr();
|
|
case "ENOSTR":
|
|
return new $simplifile.Enostr();
|
|
case "ENOSYS":
|
|
return new $simplifile.Enosys();
|
|
case "ENOBLK":
|
|
return new $simplifile.Enotblk();
|
|
case "ENOTDIR":
|
|
return new $simplifile.Enotdir();
|
|
case "ENOTSUP":
|
|
return new $simplifile.Enotsup();
|
|
case "ENXIO":
|
|
return new $simplifile.Enxio();
|
|
case "EOPNOTSUPP":
|
|
return new $simplifile.Eopnotsupp();
|
|
case "EOVERFLOW":
|
|
return new $simplifile.Eoverflow();
|
|
case "EPERM":
|
|
return new $simplifile.Eperm();
|
|
case "EPIPE":
|
|
return new $simplifile.Epipe();
|
|
case "ERANGE":
|
|
return new $simplifile.Erange();
|
|
case "EROFS":
|
|
return new $simplifile.Erofs();
|
|
case "ESPIPE":
|
|
return new $simplifile.Espipe();
|
|
case "ESRCH":
|
|
return new $simplifile.Esrch();
|
|
case "ESTALE":
|
|
return new $simplifile.Estale();
|
|
case "ETXTBSY":
|
|
return new $simplifile.Etxtbsy();
|
|
case "EXDEV":
|
|
return new $simplifile.Exdev();
|
|
case "NOTUTF8":
|
|
return new $simplifile.NotUtf8();
|
|
default:
|
|
return new $simplifile.Unknown(error_code);
|
|
}
|
|
}
|