init
This commit is contained in:
@@ -0,0 +1,412 @@
|
||||
// @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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user