aboutsummaryrefslogtreecommitdiff
path: root/aoc2023/build/packages/simplifile/src/simplifile.gleam
diff options
context:
space:
mode:
Diffstat (limited to 'aoc2023/build/packages/simplifile/src/simplifile.gleam')
-rw-r--r--aoc2023/build/packages/simplifile/src/simplifile.gleam580
1 files changed, 580 insertions, 0 deletions
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)