//// Working with files on the filesystem. //// //// The functions included in this module are for high-level concepts such as //// reading and writing. import gleam/bit_array import gleam/result /// Reason represents all of the reasons that Erlang surfaces of 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. pub type Reason { /// 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 } /// The type of file found by `file_info` or `link_info`. /// pub type FileType { Device Directory Other Regular Symlink } /// The read/write permissions a user can have for a file. /// pub type Access { NoAccess Read ReadWrite Write } /// Meta information for a file. /// /// Timestamps are in seconds before or after the Unix time epoch, /// `1970-01-01 00:00:00 UTC`. /// pub type FileInfo { FileInfo( /// File size in bytes. /// size: Int, /// `Regular`, `Directory`, `Symlink`, `Device`, or `Other`. /// file_type: FileType, /// `ReadWrite`, `Read`, `Write`, or `NoAccess`. /// access: Access, /// Timestamp of most recent access. /// atime: Int, /// Timestamp of most recent modification. /// mtime: Int, /// Timestamp of most recent change (or file creation, depending on /// operating system). /// ctime: Int, /// File permissions encoded as a sum of bit values, including but not /// limited to: /// /// Owner read, write, execute. /// /// `0o400`, `0o200`, `0o100` /// /// Group read, write, execute. /// /// `0o40`, `0o20`, `0o10` /// /// Other read, write, execute. /// /// `0o4`, `0o2`, `0o1` /// /// Set user ID, group ID on execution. /// /// `0x800`, `0x400` /// mode: Int, /// Total links to a file (always `1` for file systems without links). /// links: Int, /// The file system where a file is located (`0` for drive `A:` on Windows, /// `1` for `B:`, etc.). /// major_device: Int, /// Character device (or `0` on non-Unix systems). /// minor_device: Int, /// The `inode` number for a file (always `0` on non-Unix file systems). /// inode: Int, /// The owner of a file (always `0` on non-Unix file systems). /// user_id: Int, /// The group id of a file (always `0` on non-Unix file systems). /// group_id: Int, ) } /// Results in `FileInfo` about the given `path` on success, otherwise a /// `Reason` for failure. /// /// When `path` refers to a symlink, the result pertains to the link's target. /// To get `FileInfo` about a symlink itself, use `link_info`. /// /// ## Examples /// /// ```gleam /// > file_info("gleam.toml") /// Ok(FileInfo( /// size: 430, /// file_type: Regular, /// access: ReadWrite, /// atime: 1680580321, /// mtime: 1680580272, /// ctime: 1680580272, /// mode: 33188, /// links: 1, /// major_device: 64, /// minor_device: 0, /// inode: 469028, /// user_id: 1000, /// group_id: 1000, /// )) /// /// > file_info("/root") /// Ok(FileInfo( /// size: 16, /// file_type: Directory, /// access: Read, /// atime: 1677789967, /// mtime: 1664561240, /// ctime: 1664561240, /// mode: 16877, /// links: 11, /// major_device: 54, /// minor_device: 0, /// inode: 34, /// user_id: 0, /// group_id: 0, /// )) /// /// > file_info("./build/dev/erlang/rad/priv") /// Ok(FileInfo( /// size: 140, /// file_type: Directory, /// access: ReadWrite, /// atime: 1680580321, /// mtime: 1680580272, /// ctime: 1680580272, /// mode: 33188, /// links: 1, /// major_device: 64, /// minor_device: 0, /// inode: 469028, /// user_id: 1000, /// group_id: 1000, /// )) /// /// > file_info("/does_not_exist") /// Error(Enoent) /// /// > file_info("/root/.local/maybe_exists") /// Error(Eacces) /// ``` /// @deprecated("Use the simplifile package instead") pub fn file_info(a: String) -> Result(FileInfo, Reason) { do_file_info(a) } @external(erlang, "gleam_erlang_ffi", "file_info") fn do_file_info(a: String) -> Result(FileInfo, Reason) /// Results in `FileInfo` about the given `path` on success, otherwise a /// `Reason` for failure. /// /// When `path` refers to a symlink, the result pertains to the link itself. /// To get `FileInfo` about a symlink's target, use `file_info`. /// /// ## Examples /// /// ```gleam /// > link_info("gleam.toml") /// Ok(FileInfo( /// size: 430, /// file_type: Regular, /// access: ReadWrite, /// atime: 1680580321, /// mtime: 1680580272, /// ctime: 1680580272, /// mode: 33188, /// links: 1, /// major_device: 64, /// minor_device: 0, /// inode: 469028, /// user_id: 1000, /// group_id: 1000, /// )) /// /// > link_info("/root") /// Ok(FileInfo( /// size: 16, /// file_type: Directory, /// access: Read, /// atime: 1677789967, /// mtime: 1664561240, /// ctime: 1664561240, /// mode: 16877, /// links: 11, /// major_device: 54, /// minor_device: 0, /// inode: 34, /// user_id: 0, /// group_id: 0, /// )) /// /// > link_info("./build/dev/erlang/rad/priv") /// Ok(FileInfo( /// size: 41, /// file_type: Symlink, /// access: ReadWrite, /// atime: 1680581150, /// mtime: 1680581150, /// ctime: 1680581150, /// mode: 41471, /// links: 1, /// major_device: 64, /// minor_device: 0, /// inode: 471587, /// user_id: 1000, /// group_id: 1000, /// )) /// /// > link_info("/does_not_exist") /// Error(Enoent) /// /// > link_info("/root/.local/maybe_exists") /// Error(Eacces) /// ``` /// @deprecated("Use the simplifile package instead") pub fn link_info(a: String) -> Result(FileInfo, Reason) { do_link_info(a) } @external(erlang, "gleam_erlang_ffi", "link_info") fn do_link_info(a: String) -> Result(FileInfo, Reason) /// Results in a `Bool` on success that indicates whether the given `path` has /// a `Directory` `FileType`, otherwise a `Reason` for failure. /// /// When `path` refers to a symlink, the result pertains to the link's target. /// /// ## Examples /// /// ```gleam /// > is_directory("/tmp") /// Ok(True) /// /// > is_directory("resume.pdf") /// Ok(False) /// /// > is_directory("/does_not_exist") /// Error(Enoent) /// ``` /// @deprecated("Use the simplifile package instead") pub fn is_directory(path: String) -> Result(Bool, Reason) { use FileInfo(file_type: file_type, ..) <- result.map(over: do_file_info(path)) file_type == Directory } /// Results in a `Bool` on success that indicates whether the given `path` has /// a `Regular` `FileType`, otherwise a `Reason` for failure. /// /// When `path` refers to a symlink, the result pertains to the link's target. /// /// ## Examples /// /// ```gleam /// > is_regular("resume.pdf") /// Ok(True) /// /// > is_regular("/tmp") /// Ok(False) /// /// > is_regular("/does_not_exist.txt") /// Error(Enoent) /// ``` /// @deprecated("Use the simplifile package instead") pub fn is_regular(path: String) -> Result(Bool, Reason) { use FileInfo(file_type: file_type, ..) <- result.map(over: do_file_info(path)) file_type == Regular } /// Results in a `Bool` on success that indicates whether the given `path` /// exists, otherwise a `Reason` for failure. /// /// When `path` refers to a symlink, the result pertains to the link's target. /// To find whether a symlink itself exists, use `link_exists`. /// /// ## Examples /// /// ```gleam /// > file_exists("resume.pdf") /// Ok(True) /// /// > file_exists("/tmp") /// Ok(True) /// /// > file_exists("/does_not_exist") /// Ok(False) /// /// > file_exists("/root/.local/maybe_exists") /// Error(Eacces) /// ``` /// @deprecated("Use the simplifile package instead") pub fn file_exists(path: String) -> Result(Bool, Reason) { let result = path |> do_file_info |> result.replace(True) case result { Error(Enoent) -> Ok(False) _ -> result } } /// Results in a `Bool` on success that indicates whether the given `path` /// exists, otherwise a `Reason` for failure. /// /// When `path` refers to a symlink, the result pertains to the link itself. /// To find whether a symlink's target exists, use `file_exists`. /// /// ## Examples /// /// ```gleam /// > link_exists("resume.pdf") /// Ok(True) /// /// > link_exists("/tmp") /// Ok(True) /// /// > link_exists("/does_not_exist") /// Ok(False) /// /// > link_exists("/root/.local/maybe_exists") /// Error(Eacces) /// ``` /// @deprecated("Use the simplifile package instead") pub fn link_exists(path: String) -> Result(Bool, Reason) { let result = path |> do_link_info |> result.replace(True) case result { Error(Enoent) -> Ok(False) _ -> result } } /// Tries to create a directory. Missing parent directories are not created. /// /// Returns a Result of nil if the directory is created or Reason if the /// operation failed. /// /// ## Examples /// /// ```gleam /// > make_directory("/tmp/foo") /// Ok(Nil) /// /// > make_directory("relative_directory") /// Ok(Nil) /// /// > make_directory("/tmp/missing_intermediate_directory/foo") /// Error(Enoent) /// ``` /// @deprecated("Use the simplifile package instead") @external(erlang, "gleam_erlang_ffi", "make_directory") pub fn make_directory(a: String) -> Result(Nil, Reason) /// Lists all files in a directory, except files with /// [raw filenames](https://www.erlang.org/doc/apps/stdlib/unicode_usage.html#notes-about-raw-filenames). /// /// Returns a Result containing the list of filenames in the directory, or Reason /// if the operation failed. /// /// ## Examples /// /// ```gleam /// > list_directory("/tmp") /// Ok(["FB01293B-8597-4359-80D5-130140A0C0DE","AlTest2.out"]) /// /// > list_directory("resume.docx") /// Error(Enotdir) /// ``` /// @deprecated("Use the simplifile package instead") @external(erlang, "gleam_erlang_ffi", "list_directory") pub fn list_directory(a: String) -> Result(List(String), Reason) /// Deletes a directory. /// /// The directory must be empty before it can be deleted. Returns a nil Success /// or Reason if the operation failed. /// /// ## Examples /// /// ```gleam /// > delete_directory("foo") /// Ok(Nil) /// /// > delete_directory("does_not_exist/") /// Error(Enoent) /// ``` /// @deprecated("Use the simplifile package instead") @external(erlang, "gleam_erlang_ffi", "delete_directory") pub fn delete_directory(a: String) -> Result(Nil, Reason) /// Deletes a file or directory recursively. /// /// Returns a nil Success or Reason if the operation failed. /// /// ## Examples /// /// ```gleam /// > recursive_delete("foo") /// Ok(Nil) /// /// > recursive_delete("/bar") /// Ok(Nil) /// /// > recursive_delete("does_not_exist/") /// Error(Enoent) /// ``` /// @deprecated("Use the simplifile package instead") @external(erlang, "gleam_erlang_ffi", "recursive_delete") pub fn recursive_delete(a: String) -> Result(Nil, Reason) /// Read the contents of the given file as a String /// /// Assumes the file is UTF-8 encoded. Returns a Result containing the file's /// contents as a String if the operation was successful, or Reason if the file /// operation failed. If the file is not UTF-8 encoded, the `NotUTF8` variant /// will be returned. /// /// ## Examples /// /// ```gleam /// > read("example.txt") /// Ok("Hello, World!") /// /// > read(from: "example.txt") /// Ok("Hello, World!") /// /// > read("does_not_exist.txt") /// Error(Enoent) /// /// > read("cat.gif") /// Error(NotUTF8) /// ``` /// @deprecated("Use the simplifile package instead?") pub fn read(from path: String) -> Result(String, Reason) { path |> do_read_bits() |> result.then(fn(content) { case bit_array.to_string(content) { Ok(string) -> Ok(string) Error(Nil) -> Error(NotUtf8) } }) } /// Read the contents of the given file as a BitString /// /// Returns a Result containing the file's contents as a BitString if the /// operation was successful, or Reason if the operation failed. /// /// ## Examples /// /// ```gleam /// > read_bits("example.txt") /// Ok(<<"Hello, World!">>) /// /// > read_bits(from: "cat.gif") /// Ok(<<71,73,70,56,57,97,1,0,1,0,0,0,0,59>>) /// /// > read_bits("does_not_exist.txt") /// Error(Enoent) /// ``` /// @deprecated("Use the simplifile package instead") pub fn read_bits(from path: String) -> Result(BitArray, Reason) { do_read_bits(path) } @external(erlang, "gleam_erlang_ffi", "read_file") fn do_read_bits(a: path) -> Result(BitArray, Reason) /// Write the given String contents to a file of the given name. /// /// Returns a Result with Nil if the operation was successful or a Reason /// otherwise. /// /// ## Examples /// /// ```gleam /// > write("Hello, World!", "file.txt") /// Ok(Nil) /// /// > write(to: "file.txt", contents: "Hello, World!") /// Ok(Nil) /// /// > write("Hello, World!", "does_not_exist/file.txt") /// Error(Enoent) /// ``` /// @deprecated("Use the simplifile package instead") pub fn write(contents contents: String, to path: String) -> Result(Nil, Reason) { contents |> bit_array.from_string |> do_write_bits(path) } /// Write the given BitString contents to a file of the given name. /// /// Returns a Result with Nil if the operation was successful or a Reason /// otherwise. /// /// ## Examples /// /// ```gleam /// > write_bits(<<71,73,70,56,57,97,1,0,1,0,0,0,0,59>>, "cat.gif") /// Ok(Nil) /// /// > write_bits(to: "cat.gif", contents: <<71,73,70,56,57,97,1,0,1,0,0,0,0,59>>) /// Ok(Nil) /// /// > write_bits(<<71,73,70,56,57,97,1,0,1,0,0,0,0,59>>, "does_not_exist/cat.gif") /// Error(Enoent) /// ``` /// @deprecated("Use the simplifile package instead") pub fn write_bits( contents contents: BitArray, to path: String, ) -> Result(Nil, Reason) { do_write_bits(contents, path) } @external(erlang, "gleam_erlang_ffi", "write_file") fn do_write_bits(a: BitArray, b: String) -> Result(Nil, Reason) /// Append the given String contents to a file of the given name. /// /// Returns a Result with Nil if the operation was successful or a Reason /// otherwise. /// /// ## Examples /// /// ```gleam /// > append("Hello, World!", "file.txt") /// Ok(Nil) /// /// > append(to: "file.txt", contents: "Hello, World!") /// Ok(Nil) /// /// > append("Hello, World!", "does_not_exist/file.txt") /// Error(Enoent) /// ``` /// @deprecated("Use the simplifile package instead") pub fn append(contents contents: String, to path: String) -> Result(Nil, Reason) { contents |> bit_array.from_string |> do_append_bits(path) } /// Append the given BitString contents to a file of the given name. /// /// Returns a Result with Nil if the operation was successful or a Reason /// otherwise. /// /// ## Examples /// /// ```gleam /// > append_bits(<<71,73,70,56,57,97,1,0,1,0,0,0,0,59>>, "cat.gif") /// Ok(Nil) /// /// > append_bits(to: "cat.gif", contents: <<71,73,70,56,57,97,1,0,1,0,0,0,0,59>>) /// Ok(Nil) /// /// > append_bits(<<71,73,70,56,57,97,1,0,1,0,0,0,0,59>>, "does_not_exist/cat.gif") /// Error(Enoent) /// ``` /// pub fn append_bits( contents contents: BitArray, to path: String, ) -> Result(Nil, Reason) { do_append_bits(contents, path) } @external(erlang, "gleam_erlang_ffi", "append_file") fn do_append_bits( contents contents: BitArray, path path: String, ) -> Result(Nil, Reason) /// Delete the given file. /// /// Returns a Result with Nil if the operation was successful or a Reason /// otherwise. /// /// ## Examples /// /// ```gleam /// > delete("file.txt") /// Ok(Nil) /// /// > delete("does_not_exist.txt") /// Error(Enoent) /// ``` /// @deprecated("Use the simplifile package instead") @external(erlang, "gleam_erlang_ffi", "delete_file") pub fn delete(a: String) -> Result(Nil, Reason)