Current section
Files
Jump to
Current section
Files
src/simplifile_js.mjs
// @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);
}
}
/**
* Resolves a relative path to an absolute path based on the current working directory.
*
* @param {string} filepath The file path to resolve
* @returns {string} The resolved file path
*/
export function resolve(filepath) {
return path.resolve(filepath);
}