aboutsummaryrefslogtreecommitdiff
path: root/aoc2023/build/packages/simplifile/src
diff options
context:
space:
mode:
Diffstat (limited to 'aoc2023/build/packages/simplifile/src')
-rw-r--r--aoc2023/build/packages/simplifile/src/simplifile.app.src8
-rw-r--r--aoc2023/build/packages/simplifile/src/simplifile.erl287
-rw-r--r--aoc2023/build/packages/simplifile/src/simplifile.gleam580
-rw-r--r--aoc2023/build/packages/simplifile/src/simplifile_erl.erl70
-rw-r--r--aoc2023/build/packages/simplifile/src/simplifile_js.mjs102
5 files changed, 1047 insertions, 0 deletions
diff --git a/aoc2023/build/packages/simplifile/src/simplifile.app.src b/aoc2023/build/packages/simplifile/src/simplifile.app.src
new file mode 100644
index 0000000..5b9d6ef
--- /dev/null
+++ b/aoc2023/build/packages/simplifile/src/simplifile.app.src
@@ -0,0 +1,8 @@
+{application, simplifile, [
+ {vsn, "1.0.0"},
+ {applications, [gleam_stdlib,
+ gleeunit]},
+ {description, "Basic file operations that work on all targets"},
+ {modules, [simplifile]},
+ {registered, []}
+]}.
diff --git a/aoc2023/build/packages/simplifile/src/simplifile.erl b/aoc2023/build/packages/simplifile/src/simplifile.erl
new file mode 100644
index 0000000..0d3818c
--- /dev/null
+++ b/aoc2023/build/packages/simplifile/src/simplifile.erl
@@ -0,0 +1,287 @@
+-module(simplifile).
+-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function]).
+
+-export([read/1, write/2, delete/1, delete_all/1, append/2, read_bits/1, write_bits/2, append_bits/2, is_directory/1, create_directory/1, read_directory/1, is_file/1, create_file/1, get_files/1, create_directory_all/1, copy_directory/2, rename_directory/2, copy_file/2, rename_file/2]).
+-export_type([file_error/0]).
+
+-type file_error() :: eacces |
+ eagain |
+ ebadf |
+ ebadmsg |
+ ebusy |
+ edeadlk |
+ edeadlock |
+ edquot |
+ eexist |
+ efault |
+ efbig |
+ eftype |
+ eintr |
+ einval |
+ eio |
+ eisdir |
+ eloop |
+ emfile |
+ emlink |
+ emultihop |
+ enametoolong |
+ enfile |
+ enobufs |
+ enodev |
+ enolck |
+ enolink |
+ enoent |
+ enomem |
+ enospc |
+ enosr |
+ enostr |
+ enosys |
+ enotblk |
+ enotdir |
+ enotsup |
+ enxio |
+ eopnotsupp |
+ eoverflow |
+ eperm |
+ epipe |
+ erange |
+ erofs |
+ espipe |
+ esrch |
+ estale |
+ etxtbsy |
+ exdev |
+ not_utf8 |
+ unknown.
+
+-spec do_append(binary(), binary()) -> {ok, nil} | {error, file_error()}.
+do_append(Content, Filepath) ->
+ _pipe = Content,
+ _pipe@1 = gleam_stdlib:identity(_pipe),
+ simplifile_erl:append_file(_pipe@1, Filepath).
+
+-spec do_write(binary(), binary()) -> {ok, nil} | {error, file_error()}.
+do_write(Content, Filepath) ->
+ _pipe = Content,
+ _pipe@1 = gleam_stdlib:identity(_pipe),
+ simplifile_erl:write_file(_pipe@1, Filepath).
+
+-spec do_read(binary()) -> {ok, binary()} | {error, file_error()}.
+do_read(Filepath) ->
+ case simplifile_erl:read_file(Filepath) of
+ {ok, Bits} ->
+ case gleam@bit_array:to_string(Bits) of
+ {ok, Str} ->
+ {ok, Str};
+
+ _ ->
+ {error, not_utf8}
+ end;
+
+ {error, E} ->
+ {error, E}
+ end.
+
+-spec cast_error({ok, FIL} | {error, file_error()}) -> {ok, FIL} |
+ {error, file_error()}.
+cast_error(Input) ->
+ Input.
+
+-spec read(binary()) -> {ok, binary()} | {error, file_error()}.
+read(Filepath) ->
+ _pipe = do_read(Filepath),
+ cast_error(_pipe).
+
+-spec write(binary(), binary()) -> {ok, nil} | {error, file_error()}.
+write(Filepath, Contents) ->
+ _pipe = do_write(Contents, Filepath),
+ cast_error(_pipe).
+
+-spec delete(binary()) -> {ok, nil} | {error, file_error()}.
+delete(Path) ->
+ _pipe = simplifile_erl:recursive_delete(Path),
+ cast_error(_pipe).
+
+-spec delete_all(list(binary())) -> {ok, nil} | {error, file_error()}.
+delete_all(Paths) ->
+ case Paths of
+ [] ->
+ {ok, nil};
+
+ [Path | Rest] ->
+ case delete(Path) of
+ {ok, nil} ->
+ delete_all(Rest);
+
+ {error, enoent} ->
+ delete_all(Rest);
+
+ E ->
+ E
+ end
+ end.
+
+-spec append(binary(), binary()) -> {ok, nil} | {error, file_error()}.
+append(Filepath, Contents) ->
+ _pipe = do_append(Contents, Filepath),
+ cast_error(_pipe).
+
+-spec read_bits(binary()) -> {ok, bitstring()} | {error, file_error()}.
+read_bits(Filepath) ->
+ _pipe = simplifile_erl:read_file(Filepath),
+ cast_error(_pipe).
+
+-spec write_bits(binary(), bitstring()) -> {ok, nil} | {error, file_error()}.
+write_bits(Filepath, Bits) ->
+ _pipe = simplifile_erl:write_file(Bits, Filepath),
+ cast_error(_pipe).
+
+-spec append_bits(binary(), bitstring()) -> {ok, nil} | {error, file_error()}.
+append_bits(Filepath, Bits) ->
+ _pipe = simplifile_erl:append_file(Bits, Filepath),
+ cast_error(_pipe).
+
+-spec is_directory(binary()) -> boolean().
+is_directory(Filepath) ->
+ filelib:is_dir(Filepath).
+
+-spec create_directory(binary()) -> {ok, nil} | {error, file_error()}.
+create_directory(Filepath) ->
+ _pipe = simplifile_erl:make_directory(Filepath),
+ cast_error(_pipe).
+
+-spec read_directory(binary()) -> {ok, list(binary())} | {error, file_error()}.
+read_directory(Path) ->
+ _pipe = simplifile_erl:list_directory(Path),
+ cast_error(_pipe).
+
+-spec is_file(binary()) -> boolean().
+is_file(Filepath) ->
+ simplifile_erl:is_file(Filepath).
+
+-spec create_file(binary()) -> {ok, nil} | {error, file_error()}.
+create_file(Filepath) ->
+ case begin
+ _pipe = Filepath,
+ is_file(_pipe)
+ end
+ orelse begin
+ _pipe@1 = Filepath,
+ is_directory(_pipe@1)
+ end of
+ true ->
+ {error, eexist};
+
+ false ->
+ write_bits(Filepath, <<>>)
+ end.
+
+-spec do_copy_directory(binary(), binary()) -> {ok, nil} | {error, file_error()}.
+do_copy_directory(Src, Dest) ->
+ gleam@result:'try'(
+ read_directory(Src),
+ fun(Segments) ->
+ _pipe = Segments,
+ gleam@list:each(
+ _pipe,
+ fun(Segment) ->
+ Src_path = <<<<Src/binary, "/"/utf8>>/binary,
+ Segment/binary>>,
+ Dest_path = <<<<Dest/binary, "/"/utf8>>/binary,
+ Segment/binary>>,
+ case {is_file(Src_path), is_directory(Src_path)} of
+ {true, false} ->
+ gleam@result:'try'(
+ read_bits(Src_path),
+ fun(Content) -> _pipe@1 = Content,
+ write_bits(Dest_path, _pipe@1) end
+ );
+
+ {false, true} ->
+ gleam@result:'try'(
+ create_directory(Dest_path),
+ fun(_) ->
+ do_copy_directory(Src_path, Dest_path)
+ end
+ );
+
+ {_, _} ->
+ erlang:error(#{gleam_error => panic,
+ message => <<"unreachable"/utf8>>,
+ module => <<"simplifile"/utf8>>,
+ function => <<"do_copy_directory"/utf8>>,
+ line => 341})
+ end
+ end
+ ),
+ {ok, nil}
+ end
+ ).
+
+-spec get_files(binary()) -> {ok, list(binary())} | {error, file_error()}.
+get_files(Directory) ->
+ gleam@result:'try'(
+ read_directory(Directory),
+ fun(Contents) ->
+ Paths = gleam@list:map(
+ Contents,
+ fun(Segment) ->
+ <<<<Directory/binary, "/"/utf8>>/binary, Segment/binary>>
+ end
+ ),
+ Files = gleam@list:filter(Paths, fun is_file/1),
+ case gleam@list:filter(Paths, fun is_directory/1) of
+ [] ->
+ {ok, Files};
+
+ Directories ->
+ gleam@result:'try'(
+ gleam@list:try_map(Directories, fun get_files/1),
+ fun(Nested_files) ->
+ {ok,
+ gleam@list:append(
+ Files,
+ gleam@list:flatten(Nested_files)
+ )}
+ end
+ )
+ end
+ end
+ ).
+
+-spec create_directory_all(binary()) -> {ok, nil} | {error, file_error()}.
+create_directory_all(Dirpath) ->
+ Path = case begin
+ _pipe = Dirpath,
+ gleam@string:ends_with(_pipe, <<"/"/utf8>>)
+ end of
+ true ->
+ Dirpath;
+
+ false ->
+ <<Dirpath/binary, "/"/utf8>>
+ end,
+ _pipe@1 = simplifile_erl:create_dir_all(Path),
+ cast_error(_pipe@1).
+
+-spec copy_directory(binary(), binary()) -> {ok, nil} | {error, file_error()}.
+copy_directory(Src, Dest) ->
+ gleam@result:'try'(
+ create_directory_all(Dest),
+ fun(_) -> do_copy_directory(Src, Dest) end
+ ).
+
+-spec rename_directory(binary(), binary()) -> {ok, nil} | {error, file_error()}.
+rename_directory(Src, Dest) ->
+ gleam@result:'try'(copy_directory(Src, Dest), fun(_) -> delete(Src) end).
+
+-spec copy_file(binary(), binary()) -> {ok, nil} | {error, file_error()}.
+copy_file(Src, Dest) ->
+ _pipe = file:copy(Src, Dest),
+ _pipe@1 = gleam@result:replace(_pipe, nil),
+ cast_error(_pipe@1).
+
+-spec rename_file(binary(), binary()) -> {ok, nil} | {error, file_error()}.
+rename_file(Src, Dest) ->
+ _pipe = simplifile_erl:rename_file(Src, Dest),
+ cast_error(_pipe).
diff --git a/aoc2023/build/packages/simplifile/src/simplifile.gleam b/aoc2023/build/packages/simplifile/src/simplifile.gleam
new file mode 100644
index 0000000..eff0306
--- /dev/null
+++ b/aoc2023/build/packages/simplifile/src/simplifile.gleam
@@ -0,0 +1,580 @@
+import gleam/bit_array
+import gleam/string
+import gleam/result
+import gleam/list
+
+/// This type represents all of the reasons for why a file system operation could fail.
+///
+/// Most of these reasons are POSIX errors, which come from the operating system
+/// and start with E. Others have been added to represent other issues that may
+/// arise specific to this library.
+///
+pub type FileError {
+ /// Permission denied.
+ Eacces
+ /// Resource temporarily unavailable.
+ Eagain
+ /// Bad file number
+ Ebadf
+ /// Bad message.
+ Ebadmsg
+ /// File busy.
+ Ebusy
+ /// Resource deadlock avoided.
+ Edeadlk
+ /// On most architectures, same as `Edeadlk`. On some architectures, it
+ /// means "File locking deadlock error."
+ Edeadlock
+ /// Disk quota exceeded.
+ Edquot
+ /// File already exists.
+ Eexist
+ /// Bad address in system call argument.
+ Efault
+ /// File too large.
+ Efbig
+ /// Inappropriate file type or format. Usually caused by trying to set the
+ /// "sticky bit" on a regular file (not a directory).
+ Eftype
+ /// Interrupted system call.
+ Eintr
+ /// Invalid argument.
+ Einval
+ /// I/O error.
+ Eio
+ /// Illegal operation on a directory.
+ Eisdir
+ /// Too many levels of symbolic links.
+ Eloop
+ /// Too many open files.
+ Emfile
+ /// Too many links.
+ Emlink
+ /// Multihop attempted.
+ Emultihop
+ /// Filename too long
+ Enametoolong
+ /// File table overflow
+ Enfile
+ /// No buffer space available.
+ Enobufs
+ /// No such device.
+ Enodev
+ /// No locks available.
+ Enolck
+ /// Link has been severed.
+ Enolink
+ /// No such file or directory.
+ Enoent
+ /// Not enough memory.
+ Enomem
+ /// No space left on device.
+ Enospc
+ /// No STREAM resources.
+ Enosr
+ /// Not a STREAM.
+ Enostr
+ /// Function not implemented.
+ Enosys
+ /// Block device required.
+ Enotblk
+ /// Not a directory.
+ Enotdir
+ /// Operation not supported.
+ Enotsup
+ /// No such device or address.
+ Enxio
+ /// Operation not supported on socket.
+ Eopnotsupp
+ /// Value too large to be stored in data type.
+ Eoverflow
+ /// Not owner.
+ Eperm
+ /// Broken pipe.
+ Epipe
+ /// Result too large.
+ Erange
+ /// Read-only file system.
+ Erofs
+ /// Invalid seek.
+ Espipe
+ /// No such process.
+ Esrch
+ /// Stale remote file handle.
+ Estale
+ /// Text file busy.
+ Etxtbsy
+ /// Cross-domain link.
+ Exdev
+ /// File was requested to be read as UTF-8, but is not UTF-8 encoded.
+ NotUtf8
+ /// Any error not accounted for by this type
+ Unknown
+}
+
+/// Read a files contents as a string
+/// ## Example
+/// ```gleam
+/// let assert Ok(records) = read(from: "./users.csv")
+/// ```
+///
+pub fn read(from filepath: String) -> Result(String, FileError) {
+ do_read(filepath)
+ |> cast_error
+}
+
+/// Write a string to a file at the given path
+/// ## Example
+/// ```gleam
+/// let assert Ok(Nil) = write("Hello, World!", to: "./hello_world.txt")
+/// ```
+///
+pub fn write(
+ to filepath: String,
+ contents contents: String,
+) -> Result(Nil, FileError) {
+ do_write(contents, to: filepath)
+ |> cast_error
+}
+
+/// Delete a file or directory at a given path. Performs a recursive
+/// delete on a directory.
+/// Throws an error if the path does not exist.
+/// ## Example
+/// ```gleam
+/// let assert Ok(Nil) = delete(file_at: "./delete_me.txt")
+/// ```
+///
+pub fn delete(file_or_dir_at path: String) -> Result(Nil, FileError) {
+ do_delete(path)
+ |> cast_error
+}
+
+/// Delete all files/directories specified in a list of paths.
+/// Recursively deletes provided directories.
+/// Does not return an error if one or more of the provided paths
+/// do not exist.
+///
+pub fn delete_all(paths paths: List(String)) -> Result(Nil, FileError) {
+ case paths {
+ [] -> Ok(Nil)
+ [path, ..rest] -> {
+ case delete(path) {
+ Ok(Nil) | Error(Enoent) -> delete_all(rest)
+ e -> e
+ }
+ }
+ }
+}
+
+/// Append a string to the contents of a file at the given path
+/// ## Example
+/// ```gleam
+/// let assert Ok(Nil) = append("more text", to: "./needs_more_text.txt")
+/// ```
+///
+pub fn append(
+ to filepath: String,
+ contents contents: String,
+) -> Result(Nil, FileError) {
+ do_append(contents, to: filepath)
+ |> cast_error
+}
+
+/// Read a files contents as a bitstring
+/// ## Example
+/// ```gleam
+/// let assert Ok(records) = read_bits(from: "./users.csv")
+/// ```
+///
+pub fn read_bits(from filepath: String) -> Result(BitArray, FileError) {
+ do_read_bits(filepath)
+ |> cast_error
+}
+
+/// Write a bitstring to a file at the given path
+/// ## Example
+/// ```gleam
+/// let assert Ok(Nil) = write_bits(<<"Hello, World!":utf8>>, to: "./hello_world.txt")
+/// ```
+///
+pub fn write_bits(
+ to filepath: String,
+ bits bits: BitArray,
+) -> Result(Nil, FileError) {
+ do_write_bits(bits, filepath)
+ |> cast_error
+}
+
+/// Append a bitstring to the contents of a file at the given path
+/// ## Example
+/// ```gleam
+/// let assert Ok(Nil) = append_bits(<<"more text":utf8>>, to: "./needs_more_text.txt")
+/// ```
+///
+pub fn append_bits(
+ to filepath: String,
+ bits bits: BitArray,
+) -> Result(Nil, FileError) {
+ do_append_bits(bits, filepath)
+ |> cast_error
+}
+
+/// Checks if the provided filepath is a directory
+/// ## Example
+/// ```gleam
+/// let assert True = is_directory("./test")
+/// ```
+pub fn is_directory(filepath: String) -> Bool {
+ do_is_directory(filepath)
+}
+
+/// Create a directory at the provided filepath. Returns an error if
+/// the directory already exists.
+///
+/// ## Example
+/// ```gleam
+/// create_directory("./test")
+/// ```
+pub fn create_directory(filepath: String) -> Result(Nil, FileError) {
+ do_make_directory(filepath)
+ |> cast_error
+}
+
+/// Lists the contents of a directory.
+/// The list contains directory and file names, and is not recursive.
+///
+/// ## Example
+/// ```gleam
+/// let assert Ok(files_and_folders) = read_directory(at: "./Folder1")
+/// ```
+///
+pub fn read_directory(at path: String) -> Result(List(String), FileError) {
+ do_read_directory(path)
+ |> cast_error
+}
+
+/// Returns `True` if there is a file at the given path, false otherwise.
+///
+pub fn is_file(filepath: String) -> Bool {
+ do_is_file(filepath)
+}
+
+/// Creates an empty file at the given filepath. Returns an `Error(Eexist)`
+/// if the file already exists.
+///
+pub fn create_file(at filepath: String) -> Result(Nil, FileError) {
+ case
+ filepath
+ |> is_file || filepath
+ |> is_directory
+ {
+ True -> Error(Eexist)
+ False -> write_bits(<<>>, to: filepath)
+ }
+}
+
+/// Recursively creates necessary directories for a given directory
+/// path. Note that if you pass a path that "looks like" a file, i.e.
+/// `./a/b.txt`, a folder named `b.txt` will be created, so be sure
+/// to pass only the path to the required directory.
+pub fn create_directory_all(dirpath: String) -> Result(Nil, FileError) {
+ let path = case
+ dirpath
+ |> string.ends_with("/")
+ {
+ True -> dirpath
+ False -> dirpath <> "/"
+ }
+ do_create_dir_all(path)
+ |> cast_error
+}
+
+/// Copy a file at a given path to another path.
+/// Note: destination should include the filename, not just the directory
+pub fn copy_file(at src: String, to dest: String) -> Result(Nil, FileError) {
+ do_copy_file(src, dest)
+ |> result.replace(Nil)
+ |> cast_error
+}
+
+/// Rename a file at a given path to another path.
+/// Note: destination should include the filename, not just the directory
+pub fn rename_file(at src: String, to dest: String) -> Result(Nil, FileError) {
+ do_rename_file(src, dest)
+ |> cast_error
+}
+
+/// Copy a directory recursively
+pub fn copy_directory(at src: String, to dest: String) -> Result(Nil, FileError) {
+ // Erlang does not provide a built in `copy_dir` function,
+ // and Deno doesn't support Node's `fs.cpSync`, so we'll just roll
+ // our own for now.
+ use _ <- result.try(create_directory_all(dest))
+ do_copy_directory(src, dest)
+}
+
+fn do_copy_directory(src: String, dest: String) -> Result(Nil, FileError) {
+ // Iterate over the segments of the file
+ use segments <- result.try(read_directory(src))
+ segments
+ |> list.each(fn(segment) {
+ let src_path = src <> "/" <> segment
+ let dest_path = dest <> "/" <> segment
+
+ case is_file(src_path), is_directory(src_path) {
+ True, False -> {
+ // For a file, create the file in the new directory
+ use content <- result.try(read_bits(src_path))
+ content
+ |> write_bits(to: dest_path)
+ }
+ False, True -> {
+ // Create the target directory and recurs
+ use _ <- result.try(create_directory(dest_path))
+ do_copy_directory(src_path, dest_path)
+ }
+ _, _ -> {
+ // This should be unreachable. The src_path can't be both a file
+ // and a directory, and it's definitely one of the two because it's
+ // coming from list_contents
+ panic as "unreachable"
+ }
+ }
+ })
+ Ok(Nil)
+}
+
+/// Copy a directory recursively and then delete the old one.
+pub fn rename_directory(
+ at src: String,
+ to dest: String,
+) -> Result(Nil, FileError) {
+ use _ <- result.try(copy_directory(src, dest))
+ delete(src)
+}
+
+/// Returns a list of filepaths for every file in the directory, including nested
+/// files.
+///
+pub fn get_files(in directory: String) -> Result(List(String), FileError) {
+ use contents <- result.try(read_directory(directory))
+ let paths = list.map(contents, fn(segment) { directory <> "/" <> segment })
+ let files = list.filter(paths, is_file)
+ case list.filter(paths, is_directory) {
+ [] -> Ok(files)
+ directories -> {
+ use nested_files <- result.try(list.try_map(directories, get_files))
+ Ok(list.append(files, list.flatten(nested_files)))
+ }
+ }
+}
+
+@target(javascript)
+fn do_read(from filepath: String) -> Result(String, String) {
+ case do_read_bits(filepath) {
+ Ok(bits) -> {
+ case bit_array.to_string(bits) {
+ Ok(str) -> Ok(str)
+ _ -> Error("NOTUTF8")
+ }
+ }
+ Error(e) -> Error(e)
+ }
+}
+
+@target(javascript)
+fn do_write(content: String, to filepath: String) -> Result(Nil, String) {
+ content
+ |> bit_array.from_string
+ |> do_write_bits(to: filepath)
+}
+
+@target(javascript)
+@external(javascript, "./simplifile_js.mjs", "deleteFileOrDirRecursive")
+fn do_delete(file_or_dir_at: String) -> Result(Nil, String)
+
+@target(javascript)
+fn do_append(content: String, to filepath: String) -> Result(Nil, String) {
+ content
+ |> bit_array.from_string
+ |> do_append_bits(to: filepath)
+}
+
+@target(javascript)
+@external(javascript, "./simplifile_js.mjs", "readBits")
+fn do_read_bits(from: String) -> Result(BitArray, String)
+
+@target(javascript)
+@external(javascript, "./simplifile_js.mjs", "writeBits")
+fn do_write_bits(content: BitArray, to filepath: String) -> Result(Nil, String)
+
+@target(javascript)
+@external(javascript, "./simplifile_js.mjs", "appendBits")
+fn do_append_bits(content: BitArray, to filepath: String) -> Result(Nil, String)
+
+@target(javascript)
+@external(javascript, "./simplifile_js.mjs", "isDirectory")
+fn do_is_directory(filepath: String) -> Bool
+
+@target(javascript)
+@external(javascript, "./simplifile_js.mjs", "makeDirectory")
+fn do_make_directory(filepath: String) -> Result(Nil, String)
+
+@target(javascript)
+@external(javascript, "./simplifile_js.mjs", "createDirAll")
+fn do_create_dir_all(dirpath: String) -> Result(Nil, String)
+
+@target(javascript)
+@external(javascript, "./simplifile_js.mjs", "listContents")
+fn do_read_directory(directory_path: String) -> Result(List(String), String)
+
+@target(javascript)
+@external(javascript, "./simplifile_js.mjs", "copyFile")
+fn do_copy_file(at: String, to: String) -> Result(Nil, String)
+
+@target(javascript)
+@external(javascript, "./simplifile_js.mjs", "renameFile")
+fn do_rename_file(at: String, to: String) -> Result(Nil, String)
+
+@target(javascript)
+fn cast_error(input: Result(a, String)) -> Result(a, FileError) {
+ result.map_error(
+ input,
+ fn(e) {
+ case e {
+ "EACCES" -> Eacces
+ "EAGAIN" -> Eagain
+ "EBADF" -> Ebadf
+ "EBADMSG" -> Ebadmsg
+ "EBUSY" -> Ebusy
+ "EDEADLK" -> Edeadlk
+ "EDEADLOCK" -> Edeadlock
+ "EDQUOT" -> Edquot
+ "EEXIST" -> Eexist
+ "EFAULT" -> Efault
+ "EFBIG" -> Efbig
+ "EFTYPE" -> Eftype
+ "EINTR" -> Eintr
+ "EINVAL" -> Einval
+ "EIO" -> Eio
+ "EISDIR" -> Eisdir
+ "ELOOP" -> Eloop
+ "EMFILE" -> Emfile
+ "EMLINK" -> Emlink
+ "EMULTIHOP" -> Emultihop
+ "ENAMETOOLONG" -> Enametoolong
+ "ENFILE" -> Enfile
+ "ENOBUFS" -> Enobufs
+ "ENODEV" -> Enodev
+ "ENOLCK" -> Enolck
+ "ENOLINK" -> Enolink
+ "ENOENT" -> Enoent
+ "ENOMEM" -> Enomem
+ "ENOSPC" -> Enospc
+ "ENOSR" -> Enosr
+ "ENOSTR" -> Enostr
+ "ENOSYS" -> Enosys
+ "ENOBLK" -> Enotblk
+ "ENODIR" -> Enotdir
+ "ENOTSUP" -> Enotsup
+ "ENXIO" -> Enxio
+ "EOPNOTSUPP" -> Eopnotsupp
+ "EOVERFLOW" -> Eoverflow
+ "EPERM" -> Eperm
+ "EPIPE" -> Epipe
+ "ERANGE" -> Erange
+ "EROFS" -> Erofs
+ "ESPIPE" -> Espipe
+ "ESRCH" -> Esrch
+ "ESTALE" -> Estale
+ "ETXTBSY" -> Etxtbsy
+ "EXDEV" -> Exdev
+ "NOTUTF8" -> NotUtf8
+ _ -> Unknown
+ }
+ },
+ )
+}
+
+@target(erlang)
+@external(erlang, "simplifile_erl", "append_file")
+fn do_append_bits(
+ content: BitArray,
+ to filepath: String,
+) -> Result(Nil, FileError)
+
+@target(erlang)
+@external(erlang, "simplifile_erl", "write_file")
+fn do_write_bits(
+ content: BitArray,
+ to filepath: String,
+) -> Result(Nil, FileError)
+
+@target(erlang)
+@external(erlang, "simplifile_erl", "read_file")
+fn do_read_bits(from: String) -> Result(BitArray, FileError)
+
+@target(erlang)
+@external(erlang, "simplifile_erl", "recursive_delete")
+fn do_delete(file_or_dir_at: String) -> Result(Nil, FileError)
+
+@target(erlang)
+fn do_append(content: String, to filepath: String) -> Result(Nil, FileError) {
+ content
+ |> bit_array.from_string
+ |> do_append_bits(filepath)
+}
+
+@target(erlang)
+fn do_write(content: String, to filepath: String) -> Result(Nil, FileError) {
+ content
+ |> bit_array.from_string
+ |> do_write_bits(filepath)
+}
+
+@target(erlang)
+fn do_read(from filepath: String) -> Result(String, FileError) {
+ case do_read_bits(filepath) {
+ Ok(bits) -> {
+ case bit_array.to_string(bits) {
+ Ok(str) -> Ok(str)
+ _ -> Error(NotUtf8)
+ }
+ }
+ Error(e) -> Error(e)
+ }
+}
+
+@target(erlang)
+fn cast_error(input: Result(a, FileError)) -> Result(a, FileError) {
+ input
+}
+
+@target(erlang)
+@external(erlang, "filelib", "is_dir")
+fn do_is_directory(path: String) -> Bool
+
+@target(erlang)
+@external(erlang, "simplifile_erl", "make_directory")
+fn do_make_directory(directory: String) -> Result(Nil, FileError)
+
+@target(erlang)
+@external(erlang, "simplifile_erl", "list_directory")
+fn do_read_directory(directory: String) -> Result(List(String), FileError)
+
+@external(erlang, "simplifile_erl", "is_file")
+@external(javascript, "./simplifile_js.mjs", "isFile")
+fn do_is_file(filepath: String) -> Bool
+
+@target(erlang)
+@external(erlang, "simplifile_erl", "create_dir_all")
+fn do_create_dir_all(dirpath: String) -> Result(Nil, FileError)
+
+@target(erlang)
+@external(erlang, "file", "copy")
+fn do_copy_file(src: String, dest: String) -> Result(Int, FileError)
+
+@target(erlang)
+@external(erlang, "simplifile_erl", "rename_file")
+fn do_rename_file(src: String, dest: String) -> Result(Nil, FileError)
diff --git a/aoc2023/build/packages/simplifile/src/simplifile_erl.erl b/aoc2023/build/packages/simplifile/src/simplifile_erl.erl
new file mode 100644
index 0000000..dac135a
--- /dev/null
+++ b/aoc2023/build/packages/simplifile/src/simplifile_erl.erl
@@ -0,0 +1,70 @@
+-module(simplifile_erl).
+-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
+]).
+
+-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
+).
+
+posix_result(Result) ->
+ case Result of
+ ok -> {ok, nil};
+ {ok, Value} -> {ok, Value};
+ {error, Reason} when ?is_posix_error(Reason) -> {error, Reason}
+ end.
+
+read_file(Filename) ->
+ posix_result(file:read_file(Filename)).
+
+write_file(Contents, Filename) ->
+ posix_result(file:write_file(Filename, Contents)).
+
+append_file(Contents, Filename) ->
+ posix_result(file:write_file(Filename, Contents, [append])).
+
+delete_file(Filename) ->
+ posix_result(file:delete(Filename)).
+
+make_directory(Dir) ->
+ posix_result(file:make_dir(Dir)).
+
+list_directory(Dir) ->
+ case file:list_dir(Dir) of
+ {ok, Filenames} ->
+ {ok, [list_to_binary(Filename) || Filename <- Filenames]};
+ {error, Reason} when ?is_posix_error(Reason) ->
+ {error, Reason}
+ end.
+
+delete_directory(Dir) ->
+ posix_result(file:del_dir(Dir)).
+
+recursive_delete(Dir) ->
+ posix_result(file:del_dir_r(Dir)).
+
+is_file(Filename) ->
+ not (file:read_file_info(Filename) == {error, enoent}) and not filelib: is_dir(Filename).
+
+create_dir_all(Filename) ->
+ posix_result(filelib:ensure_dir(Filename)).
+
+rename_file(Source, Destination) ->
+ posix_result(file:rename(Source, Destination)).
diff --git a/aoc2023/build/packages/simplifile/src/simplifile_js.mjs b/aoc2023/build/packages/simplifile/src/simplifile_js.mjs
new file mode 100644
index 0000000..faf4109
--- /dev/null
+++ b/aoc2023/build/packages/simplifile/src/simplifile_js.mjs
@@ -0,0 +1,102 @@
+import fs from "node:fs"
+import path from "node:path"
+import { BitArray, Ok, Error as GError, toList} from "./gleam.mjs";
+
+export function readBits(filepath) {
+ try {
+ const contents = fs.readFileSync(path.normalize(filepath))
+ return new Ok(new BitArray(new Uint8Array(contents)))
+ } catch(e) {
+ return new GError(stringifyError(e))
+ }
+}
+
+export function writeBits(contents, filepath) {
+ try {
+ fs.writeFileSync(path.normalize(filepath), contents.buffer)
+ return new Ok(undefined)
+ } catch (e) {
+ return new GError(stringifyError(e))
+ }
+}
+
+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
+}
+
+export function isFile(filepath) {
+ let fp = path.normalize(filepath)
+ return fs.existsSync(fp) && fs.lstatSync(fp).isFile();
+}
+
+export function isDirectory(filepath) {
+ let fp = path.normalize(filepath)
+ return fs.existsSync(fp) && fs.lstatSync(fp).isDirectory();
+}
+
+export function makeDirectory(filepath) {
+ try {
+ fs.mkdirSync(path.normalize(filepath))
+ return new Ok(undefined)
+ } catch (e) {
+ return new GError(stringifyError(e))
+ }
+}
+
+export function createDirAll(filepath) {
+ try {
+ fs.mkdirSync(filepath, { recursive: true })
+ return new Ok(undefined)
+ } catch (e) {
+ return new GError(stringifyError(e))
+ }
+}
+
+export function deleteFileOrDirRecursive(fileOrDirPath) {
+ try {
+ 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))
+ }
+}
+
+export function listContents(filepath) {
+ try {
+ const stuff = toList(fs.readdirSync(path.normalize(filepath)))
+ return new Ok(stuff)
+ } catch(e) {
+ return new GError(stringifyError(e))
+ }
+}
+
+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))
+ }
+}
+
+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))
+ }
+} \ No newline at end of file