Skip to content

Commit

Permalink
refactor ffi code
Browse files Browse the repository at this point in the history
  • Loading branch information
bcpeinhardt committed Dec 26, 2023
1 parent 10afa9e commit 7048e95
Show file tree
Hide file tree
Showing 3 changed files with 165 additions and 78 deletions.
6 changes: 6 additions & 0 deletions jsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"compilerOptions": {
"checkJs": true,
"rootDirs": ["src", "build/dev/javascript/simplifile"],
}
}
68 changes: 50 additions & 18 deletions src/simplifile_erl.erl
Original file line number Diff line number Diff line change
@@ -1,51 +1,77 @@
%%% --------------------------------------------------
%%% @author Benjamin Peinhardt <[email protected]>
%%% @doc Erlang ffi glue code for the simplifile Gleam package.
%%% @end
%%% --------------------------------------------------

-module(simplifile_erl).

%% API
-export([
read_file/1,
append_file/2, write_file/2, delete_file/1, delete_directory/1, recursive_delete/1,
list_directory/1, make_directory/1, is_file/1, create_dir_all/1, rename_file/2, set_permissions/2
append_file/2,
write_file/2,
delete_file/1,
delete_directory/1,
recursive_delete/1,
list_directory/1,
make_directory/1,
is_file/1,
create_dir_all/1,
rename_file/2,
set_permissions/2
]).

%% A macro for checking whether the error returned is one of the atoms for a posixe error.
-define(is_posix_error(Error),
Error =:= eacces orelse Error =:= eagain orelse Error =:= ebadf orelse
Error =:= ebadmsg orelse Error =:= ebusy orelse Error =:= edeadlk orelse
Error =:= edeadlock orelse Error =:= edquot orelse Error =:= eexist orelse
Error =:= efault orelse Error =:= efbig orelse Error =:= eftype orelse
Error =:= eintr orelse Error =:= einval orelse Error =:= eio orelse
Error =:= eisdir orelse Error =:= eloop orelse Error =:= emfile orelse
Error =:= emlink orelse Error =:= emultihop orelse Error =:= enametoolong orelse
Error =:= enfile orelse Error =:= enobufs orelse Error =:= enodev orelse
Error =:= enolck orelse Error =:= enolink orelse Error =:= enoent orelse
Error =:= enomem orelse Error =:= enospc orelse Error =:= enosr orelse
Error =:= enostr orelse Error =:= enosys orelse Error =:= enotblk orelse
Error =:= enotdir orelse Error =:= enotsup orelse Error =:= enxio orelse
Error =:= eopnotsupp orelse Error =:= eoverflow orelse Error =:= eperm orelse
Error =:= epipe orelse Error =:= erange orelse Error =:= erofs orelse
Error =:= espipe orelse Error =:= esrch orelse Error =:= estale orelse
Error =:= etxtbsy orelse Error =:= exdev
Error =:= ebadmsg orelse Error =:= ebusy orelse Error =:= edeadlk orelse
Error =:= edeadlock orelse Error =:= edquot orelse Error =:= eexist orelse
Error =:= efault orelse Error =:= efbig orelse Error =:= eftype orelse
Error =:= eintr orelse Error =:= einval orelse Error =:= eio orelse
Error =:= eisdir orelse Error =:= eloop orelse Error =:= emfile orelse
Error =:= emlink orelse Error =:= emultihop orelse Error =:= enametoolong orelse
Error =:= enfile orelse Error =:= enobufs orelse Error =:= enodev orelse
Error =:= enolck orelse Error =:= enolink orelse Error =:= enoent orelse
Error =:= enomem orelse Error =:= enospc orelse Error =:= enosr orelse
Error =:= enostr orelse Error =:= enosys orelse Error =:= enotblk orelse
Error =:= enotdir orelse Error =:= enotsup orelse Error =:= enxio orelse
Error =:= eopnotsupp orelse Error =:= eoverflow orelse Error =:= eperm orelse
Error =:= epipe orelse Error =:= erange orelse Error =:= erofs orelse
Error =:= espipe orelse Error =:= esrch orelse Error =:= estale orelse
Error =:= etxtbsy orelse Error =:= exdev
).

%% Only return the error if it's a posix error. Also converts ok to {ok, nil},
%% as gleam doesn't have atoms.
posix_result(Result) ->
case Result of
ok -> {ok, nil};
{ok, Value} -> {ok, Value};
{error, Reason} when ?is_posix_error(Reason) -> {error, Reason}
end.

%% Read the binary contents of a file
read_file(Filename) ->
posix_result(file:read_file(Filename)).

%% Write bytes to a file
write_file(Contents, Filename) ->
posix_result(file:write_file(Filename, Contents)).

%% Append bytes to a file
append_file(Contents, Filename) ->
posix_result(file:write_file(Filename, Contents, [append])).

%% Delete the file at the given path
delete_file(Filename) ->
posix_result(file:delete(Filename)).

%% Create a directory at the given path. Missing parent directories are not created.
make_directory(Dir) ->
posix_result(file:make_dir(Dir)).

%% List the contents of a directory
list_directory(Dir) ->
case file:list_dir(Dir) of
{ok, Filenames} ->
Expand All @@ -54,20 +80,26 @@ list_directory(Dir) ->
{error, Reason}
end.

%% Delete a directory at the given path. Directory must be empty.
delete_directory(Dir) ->
posix_result(file:del_dir(Dir)).

%% Deletes a file/directory and everything in it.
recursive_delete(Dir) ->
posix_result(file:del_dir_r(Dir)).

%% Checks whether a given file exists and is a file (as opposed to a directory)
is_file(Filename) ->
not (file:read_file_info(Filename) == {error, enoent}) and not filelib: is_dir(Filename).
not (file:read_file_info(Filename) == {error, enoent}) and not filelib:is_dir(Filename).

%% Creates the entire path for a given directory to exist
create_dir_all(Filename) ->
posix_result(filelib:ensure_dir(Filename)).

%% Move file from one path to another
rename_file(Source, Destination) ->
posix_result(file:rename(Source, Destination)).

%% Set the permissions for the given file.
set_permissions(Filename, Permissions) ->
posix_result(file:change_mode(Filename, Permissions)).
169 changes: 109 additions & 60 deletions src/simplifile_js.mjs
Original file line number Diff line number Diff line change
@@ -1,111 +1,160 @@
// @ts-check

import fs from "node:fs"
import path from "node:path"
import { BitArray, Ok, Error as GError, toList} from "./gleam.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) {
try {
return gleamResult(() => {
const contents = fs.readFileSync(path.normalize(filepath))
return new Ok(new BitArray(new Uint8Array(contents)))
} catch(e) {
return new GError(stringifyError(e))
}
return new BitArray(new Uint8Array(contents))
})
}

/**
* Write the given BitArray to a file
*
* @param {BitArray} contents
* @param {string} filepath
* @returns {Ok | GError}
*/
export function writeBits(contents, filepath) {
try {
fs.writeFileSync(path.normalize(filepath), contents.buffer)
return new Ok(undefined)
} catch (e) {
return new GError(stringifyError(e))
}
return gleamResult(() => fs.writeFileSync(path.normalize(filepath), contents.buffer))
}

/**
* Append the given BitArray to a file
*
* @param {BitArray} contents
* @param {string} filepath
* @returns {Ok | GError}
*/
export function appendBits(contents, filepath) {
try {
fs.appendFileSync(path.normalize(filepath), contents.buffer)
return new Ok(undefined)
} catch (e) {
return new GError(stringifyError(e))
}
}

function stringifyError(e) {
return e.code
return gleamResult(() => fs.appendFileSync(path.normalize(filepath), contents.buffer))
}

/**
* Check whether a file exists at the given path
*
* @param {string} filepath
* @returns {boolean}
*/
export function isFile(filepath) {
let fp = path.normalize(filepath)
return fs.existsSync(fp) && fs.lstatSync(fp).isFile();
}

/**
* Check whether a directory exists at the given path
*
* @param {string} filepath
* @returns {boolean}
*/
export function isDirectory(filepath) {
let fp = path.normalize(filepath)
return fs.existsSync(fp) && fs.lstatSync(fp).isDirectory();
}

/**
* Create a directory at the given filepath
*
* @param {string} filepath
* @returns {Ok | GError}
*/
export function makeDirectory(filepath) {
try {
fs.mkdirSync(path.normalize(filepath))
return new Ok(undefined)
} catch (e) {
return new GError(stringifyError(e))
}
return gleamResult(() => fs.mkdirSync(path.normalize(filepath)))
}

/**
* Recursively create a directory structure from the given path.
*
* @param {string} filepath
* @returns
*/
export function createDirAll(filepath) {
try {
fs.mkdirSync(filepath, { recursive: true })
return new Ok(undefined)
} catch (e) {
return new GError(stringifyError(e))
}
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 deleteFileOrDirRecursive(fileOrDirPath) {
try {
return gleamResult(() => {
if (isDirectory(fileOrDirPath)) {
fs.rmSync(path.normalize(fileOrDirPath), { recursive: true })
} else {
fs.unlinkSync(path.normalize(fileOrDirPath))
}
return new Ok(undefined)
} catch (e) {
return new GError(stringifyError(e))
}
})
}

/**
* List the contents of a directory.
*
* @param {string} filepath
* @returns {Ok | GError}
*/
export function listContents(filepath) {
try {
const stuff = toList(fs.readdirSync(path.normalize(filepath)))
return new Ok(stuff)
} catch(e) {
return new GError(stringifyError(e))
}
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) {
try {
fs.copyFileSync(path.normalize(srcpath), path.normalize(destpath))
return new Ok(undefined)
} catch (e) {
return new GError(stringifyError(e))
}
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) {
try {
fs.renameSync(path.normalize(srcpath), path.normalize(destpath))
return new Ok(undefined)
} catch (e) {
return new GError(stringifyError(e))
}
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 setPermissions(filepath, octalNumber) {
return gleamResult(() => fs.chmodSync(path.normalize(filepath), octalNumber))
}

/**
* 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 {
fs.chmodSync(path.normalize(filepath), octalNumber)
return new Ok(undefined)
} catch (e) {
return new GError(stringifyError(e))
const val = op()
return new Ok(val)
} catch(e) {
return new GError(e.code)
}
}

0 comments on commit 7048e95

Please sign in to comment.