# LLM Docs ## Compact Docs Copy just this section if you want to keep your context small. If you want all docs, check the section `Full Docs per Module` below. Path.write! : val, Path, fmt => Result {} [FileWriteErr Path IOErr] where val implements Encoding, fmt implements EncoderFormatting Path.write_bytes! : List U8, Path => Result {} [FileWriteErr Path IOErr] Path.write_utf8! : Str, Path => Result {} [FileWriteErr Path IOErr] Path.from_str : Str -> Path Path.from_bytes : List U8 -> Path Path.display : Path -> Str Path.is_dir! : Path => Result Bool [PathErr IOErr] Path.is_file! : Path => Result Bool [PathErr IOErr] Path.is_sym_link! : Path => Result Bool [PathErr IOErr] Path.exists! : Path => Result Bool [PathErr IOErr] Path.type! : Path => Result [ IsFile, IsDir, IsSymLink ] [PathErr IOErr] Path.with_extension : Path, Str -> Path Path.delete! : Path => Result {} [FileWriteErr Path IOErr] Path.read_utf8! : Path => Result Str [ FileReadErr Path IOErr, FileReadUtf8Err Path ] Path.read_bytes! : Path => Result (List U8) [FileReadErr Path IOErr] Path.list_dir! : Path => Result (List Path) [DirErr IOErr] Path.delete_empty! : Path => Result {} [DirErr IOErr] Path.delete_all! : Path => Result {} [DirErr IOErr] Path.create_dir! : Path => Result {} [DirErr IOErr] Path.create_all! : Path => Result {} [DirErr IOErr] Path.hard_link! : Path, Path => Result {} [LinkErr IOErr] Path.rename! : Path, Path => Result {} [PathErr IOErr] Arg.to_os_raw : Arg -> [ Unix (List U8), Windows (List U16) ] Arg.from_os_raw : [ Unix (List U8), Windows (List U16) ] -> Arg Arg.display : Arg -> Str Dir.list! : Str => Result (List Path) [DirErr IOErr] Dir.delete_empty! : Str => Result {} [DirErr IOErr] Dir.delete_all! : Str => Result {} [DirErr IOErr] Dir.create! : Str => Result {} [DirErr IOErr] Dir.create_all! : Str => Result {} [DirErr IOErr] Env.cwd! : {} => Result Path [CwdUnavailable] Env.set_cwd! : Path => Result {} [InvalidCwd] Env.exe_path! : {} => Result Path [ExePathUnavailable] Env.var! : Str => Result Str [VarNotFound Str] Env.decode! : Str => Result val [ VarNotFound Str, DecodeErr DecodeError ] where val implements Decoding Env.dict! : {} => Dict Str Str Env.platform! : {} => { arch : ARCH, os : OS } Env.temp_dir! : {} => Path File.write! : val, Str, fmt => Result {} [FileWriteErr Path IOErr] where val implements Encoding, fmt implements EncoderFormatting File.write_bytes! : List U8, Str => Result {} [FileWriteErr Path IOErr] File.write_utf8! : Str, Str => Result {} [FileWriteErr Path IOErr] File.delete! : Str => Result {} [FileWriteErr Path IOErr] File.read_bytes! : Str => Result (List U8) [FileReadErr Path IOErr] File.read_utf8! : Str => Result Str [ FileReadErr Path IOErr, FileReadUtf8Err Path ] File.hard_link! : Str, Str => Result {} [LinkErr IOErr] File.is_dir! : Str => Result Bool [PathErr IOErr] File.is_file! : Str => Result Bool [PathErr IOErr] File.is_sym_link! : Str => Result Bool [PathErr IOErr] File.exists! : Str => Result Bool [PathErr IOErr] File.is_executable! : Str => Result Bool [PathErr IOErr] File.is_readable! : Str => Result Bool [PathErr IOErr] File.is_writable! : Str => Result Bool [PathErr IOErr] File.time_accessed! : Str => Result Utc [PathErr IOErr] File.time_modified! : Str => Result Utc [PathErr IOErr] File.time_created! : Str => Result Utc [PathErr IOErr] File.rename! : Str, Str => Result {} [PathErr IOErr] File.type! : Str => Result [ IsFile, IsDir, IsSymLink ] [PathErr IOErr] File.open_reader! : Str => Result Reader [GetFileReadErr Path IOErr] File.open_reader_with_capacity! : Str, U64 => Result Reader [GetFileReadErr Path IOErr] File.read_line! : Reader => Result (List U8) [FileReadErr Path IOErr] File.size_in_bytes! : Str => Result U64 [PathErr IOErr] Http.default_request : Request Http.header : ( Str, Str ) -> Header Http.send! : Request => Result Response [ HttpErr [ Timeout, NetworkError, BadBody, Other (List U8) ] ] Http.get! : Str, fmt => Result body [ HttpDecodingFailed, HttpErr ] where body implements Decoding, fmt implements DecoderFormatting Http.get_utf8! : Str => Result Str [ BadBody Str, HttpErr ] Stderr.line! : Str => Result {} [StderrErr IOErr] Stderr.write! : Str => Result {} [StderrErr IOErr] Stderr.write_bytes! : List U8 => Result {} [StderrErr IOErr] Stdin.line! : {} => Result Str [ EndOfFile, StdinErr IOErr ] Stdin.bytes! : {} => Result (List U8) [ EndOfFile, StdinErr IOErr ] Stdin.read_to_end! : {} => Result (List U8) [StdinErr IOErr] Stdout.line! : Str => Result {} [StdoutErr IOErr] Stdout.write! : Str => Result {} [StdoutErr IOErr] Stdout.write_bytes! : List U8 => Result {} [StdoutErr IOErr] Tcp.connect! : Str, U16 => Result Stream (ConnectErr ) Tcp.read_up_to! : Stream, U64 => Result (List U8) [TcpReadErr StreamErr] Tcp.read_exactly! : Stream, U64 => Result (List U8) [ TcpReadErr StreamErr, TcpUnexpectedEOF ] Tcp.read_until! : Stream, U8 => Result (List U8) [TcpReadErr StreamErr] Tcp.read_line! : Stream => Result Str [ TcpReadErr StreamErr, TcpReadBadUtf8 ] Tcp.write! : Stream, List U8 => Result {} [TcpWriteErr StreamErr] Tcp.write_utf8! : Stream, Str => Result {} [TcpWriteErr StreamErr] Tcp.connect_err_to_str : ConnectErr -> Str Tcp.stream_err_to_str : StreamErr -> Str Url.reserve : Url, U64 -> Url Url.from_str : Str -> Url Url.to_str : Url -> Str Url.append : Url, Str -> Url Url.append_param : Url, Str, Str -> Url Url.with_query : Url, Str -> Url Url.query : Url -> Str Url.has_query : Url -> Bool Url.fragment : Url -> Str Url.with_fragment : Url, Str -> Url Url.has_fragment : Url -> Bool Url.query_params : Url -> Dict Str Str Url.path : Url -> Str Utc.now! : {} => Utc Utc.to_millis_since_epoch : Utc -> I128 Utc.from_millis_since_epoch : I128 -> Utc Utc.to_nanos_since_epoch : Utc -> I128 Utc.from_nanos_since_epoch : I128 -> Utc Utc.delta_as_millis : Utc, Utc -> U128 Utc.delta_as_nanos : Utc, Utc -> U128 Utc.to_iso_8601 : Utc -> Str Sleep.millis! : U64 => {} Cmd.exec! : Str, List Str => Result {} [ ExecFailed { command : Str, exit_code : I32 }, FailedToGetExitCode { command : Str, err : IOErr } ] Cmd.exec_cmd! : Cmd => Result {} [ ExecCmdFailed { command : Str, exit_code : I32 }, FailedToGetExitCode { command : Str, err : IOErr } ] Cmd.exec_output! : Cmd => Result { stdout_utf8 : Str, stderr_utf8_lossy : Str } [ StdoutContainsInvalidUtf8 { cmd_str : Str, err : [ BadUtf8 { index : U64, problem : Str.Utf8Problem } ] }, NonZeroExitCode { command : Str, exit_code : I32, stdout_utf8_lossy : Str, stderr_utf8_lossy : Str }, FailedToGetExitCode { command : Str, err : IOErr } ] Cmd.exec_output_bytes! : Cmd => Result { stderr_bytes : List U8, stdout_bytes : List U8 } [ FailedToGetExitCodeB InternalIOErr.IOErr, NonZeroExitCodeB { exit_code : I32, stderr_bytes : List U8, stdout_bytes : List U8 } ] Cmd.exec_exit_code! : Cmd => Result I32 [ FailedToGetExitCode { command : Str, err : IOErr } ] Cmd.env : Cmd, Str, Str -> Cmd Cmd.envs : Cmd, List ( Str, Str ) -> Cmd Cmd.clear_envs : Cmd -> Cmd Cmd.new : Str -> Cmd Cmd.arg : Cmd, Str -> Cmd Cmd.args : Cmd, List Str -> Cmd Tty.enable_raw_mode! : {} => {} Tty.disable_raw_mode! : {} => {} Locale.get! : {} => Result Str [NotAvailable] Locale.all! : {} => List Str Sqlite.prepare! : { path : Str, query : Str } => Result Stmt [SqliteErr ErrCode Str] Sqlite.execute! : { path : Str, query : Str, bindings : List Binding } => Result {} [ SqliteErr ErrCode Str, RowsReturnedUseQueryInstead ] Sqlite.execute_prepared! : { stmt : Stmt, bindings : List Binding } => Result {} [ SqliteErr ErrCode Str, RowsReturnedUseQueryInstead ] Sqlite.query! : { path : Str, query : Str, bindings : List Binding, row : SqlDecode a (RowCountErr err) } => Result a (SqlDecodeErr (RowCountErr err)) Sqlite.query_prepared! : { stmt : Stmt, bindings : List Binding, row : SqlDecode a (RowCountErr err) } => Result a (SqlDecodeErr (RowCountErr err)) Sqlite.query_many! : { path : Str, query : Str, bindings : List Binding, rows : SqlDecode a err } => Result (List a) (SqlDecodeErr err) Sqlite.query_many_prepared! : { stmt : Stmt, bindings : List Binding, rows : SqlDecode a err } => Result (List a) (SqlDecodeErr err) Sqlite.decode_record : SqlDecode a err, SqlDecode b err, (a, b -> c) -> SqlDecode c err Sqlite.map_value : SqlDecode a err, (a -> b) -> SqlDecode b err Sqlite.map_value_result : SqlDecode a err, (a -> Result c (SqlDecodeErr err)) -> SqlDecode c err Sqlite.tagged_value : Str -> SqlDecode Value [] Sqlite.str : Str -> SqlDecode Str UnexpectedTypeErr Sqlite.bytes : Str -> SqlDecode (List U8) UnexpectedTypeErr Sqlite.i64 : Str -> SqlDecode I64 [FailedToDecodeInteger []]UnexpectedTypeErr Sqlite.i32 : Str -> SqlDecode I32 [FailedToDecodeInteger [OutOfBounds]]UnexpectedTypeErr Sqlite.i16 : Str -> SqlDecode I16 [FailedToDecodeInteger [OutOfBounds]]UnexpectedTypeErr Sqlite.i8 : Str -> SqlDecode I8 [FailedToDecodeInteger [OutOfBounds]]UnexpectedTypeErr Sqlite.u64 : Str -> SqlDecode U64 [FailedToDecodeInteger [OutOfBounds]]UnexpectedTypeErr Sqlite.u32 : Str -> SqlDecode U32 [FailedToDecodeInteger [OutOfBounds]]UnexpectedTypeErr Sqlite.u16 : Str -> SqlDecode U16 [FailedToDecodeInteger [OutOfBounds]]UnexpectedTypeErr Sqlite.u8 : Str -> SqlDecode U8 [FailedToDecodeInteger [OutOfBounds]]UnexpectedTypeErr Sqlite.f64 : Str -> SqlDecode F64 [FailedToDecodeReal []]UnexpectedTypeErr Sqlite.f32 : Str -> SqlDecode F32 [FailedToDecodeReal []]UnexpectedTypeErr Sqlite.Nullable : [ NotNull a, Null ] Sqlite.nullable_str : Str -> SqlDecode (Nullable Str) UnexpectedTypeErr Sqlite.nullable_bytes : Str -> SqlDecode (Nullable (List U8)) UnexpectedTypeErr Sqlite.nullable_i64 : Str -> SqlDecode (Nullable I64) [FailedToDecodeInteger []]UnexpectedTypeErr Sqlite.nullable_i32 : Str -> SqlDecode (Nullable I32) [FailedToDecodeInteger [OutOfBounds]]UnexpectedTypeErr Sqlite.nullable_i16 : Str -> SqlDecode (Nullable I16) [FailedToDecodeInteger [OutOfBounds]]UnexpectedTypeErr Sqlite.nullable_i8 : Str -> SqlDecode (Nullable I8) [FailedToDecodeInteger [OutOfBounds]]UnexpectedTypeErr Sqlite.nullable_u64 : Str -> SqlDecode (Nullable U64) [FailedToDecodeInteger [OutOfBounds]]UnexpectedTypeErr Sqlite.nullable_u32 : Str -> SqlDecode (Nullable U32) [FailedToDecodeInteger [OutOfBounds]]UnexpectedTypeErr Sqlite.nullable_u16 : Str -> SqlDecode (Nullable U16) [FailedToDecodeInteger [OutOfBounds]]UnexpectedTypeErr Sqlite.nullable_u8 : Str -> SqlDecode (Nullable U8) [FailedToDecodeInteger [OutOfBounds]]UnexpectedTypeErr Sqlite.nullable_f64 : Str -> SqlDecode (Nullable F64) [FailedToDecodeReal []]UnexpectedTypeErr Sqlite.nullable_f32 : Str -> SqlDecode (Nullable F32) [FailedToDecodeReal []]UnexpectedTypeErr Sqlite.errcode_to_str : ErrCode -> Str ## Full Docs per Module ### Path Description: Represents a path to a file or directory on the filesystem. Description: Tag union of possible errors when reading and writing a file or directory. > This is the same as [`File.Err`](File#Err). write! : val, Path, fmt => Result {} [FileWriteErr Path IOErr] where val implements Encoding, fmt implements EncoderFormatting Description: Write data to a file. First encode a `val` using a given `fmt` which implements the ability [Encode.EncoderFormatting](https://www.roc-lang.org/builtins/Encode#EncoderFormatting). For example, suppose you have a `Json.utf8` which implements [Encode.EncoderFormatting](https://www.roc-lang.org/builtins/Encode#EncoderFormatting). You can use this to write [JSON](https://en.wikipedia.org/wiki/JSON) data to a file like this: ``` # Writes `{"some":"json stuff"}` to the file `output.json`: Path.write!( { some: "json stuff" }, Path.from_str("output.json"), Json.utf8, )? ``` This opens the file first and closes it after writing to it. If writing to the file fails, for example because of a file permissions issue, the task fails with [WriteErr]. > To write unformatted bytes to a file, you can use [Path.write_bytes!] instead. write_bytes! : List U8, Path => Result {} [FileWriteErr Path IOErr] Description: Writes bytes to a file. ``` # Writes the bytes 1, 2, 3 to the file `myfile.dat`. Path.write_bytes!([1, 2, 3], Path.from_str("myfile.dat"))? ``` This opens the file first and closes it after writing to it. > To format data before writing it to a file, you can use [Path.write!] instead. write_utf8! : Str, Path => Result {} [FileWriteErr Path IOErr] Description: Writes a [Str] to a file, encoded as [UTF-8](https://en.wikipedia.org/wiki/UTF-8). ``` # Writes "Hello!" encoded as UTF-8 to the file `myfile.txt`. Path.write_utf8!("Hello!", Path.from_str("myfile.txt"))? ``` This opens the file first and closes it after writing to it. > To write unformatted bytes to a file, you can use [Path.write_bytes!] instead. from_str : Str -> Path Description: Note that the path may not be valid depending on the filesystem where it is used. For example, paths containing `:` are valid on ext4 and NTFS filesystems, but not on FAT ones. So if you have multiple disks on the same machine, but they have different filesystems, then this path could be valid on one but invalid on another! It's safest to assume paths are invalid (even syntactically) until given to an operation which uses them to open a file. If that operation succeeds, then the path was valid (at the time). Otherwise, error handling can happen for that operation rather than validating up front for a false sense of security (given symlinks, parts of a path being renamed, etc.). from_bytes : List U8 -> Path Description: Not all filesystems use Unicode paths. This function can be used to create a path which is not valid Unicode (like a [Str] is), but which is valid for a particular filesystem. Note that if the list contains any `0` bytes, sending this path to any file operations (e.g. `Path.read_bytes` or `WriteStream.open_path`) will fail. display : Path -> Str Description: Unfortunately, operating system paths do not include information about which charset they were originally encoded with. It's most common (but not guaranteed) that they will have been encoded with the same charset as the operating system's curent locale (which typically does not change after it is set during installation of the OS), so this should convert a [Path] to a valid string as long as the path was created with the given `Charset`. (Use `Env.charset` to get the current system charset.) For a conversion to [Str] that is lossy but does not return a [Result], see [display]. to_inner : Path -> [Str Str, Bytes (List U8)] Assumes a path is encoded as [UTF-8](https://en.wikipedia.org/wiki/UTF-8), and converts it to a string using `Str.display`. This conversion is lossy because the path may contain invalid UTF-8 bytes. If that happens, any invalid bytes will be replaced with the [Unicode replacement character](https://unicode.org/glossary/#replacement_character) instead of returning an error. As such, it's rarely a good idea to use the [Str] returned by this function for any purpose other than displaying it to a user. When you don't know for sure what a path's encoding is, UTF-8 is a popular guess because it's the default on UNIX and also is the encoding used in Roc strings. This platform also automatically runs applications under the [UTF-8 code page](https://docs.microsoft.com/en-us/windows/apps/design/globalizing/use-utf8-code-page) on Windows. Converting paths to strings can be an unreliable operation, because operating systems don't record the paths' encodings. This means it's possible for the path to have been encoded with a different character set than UTF-8 even if UTF-8 is the system default, which means when [display] converts them to a string, the string may include gibberish. [Here is an example.](https://unix.stackexchange.com/questions/667652/can-a-file-path-be-invalid-utf-8/667863#667863) If you happen to know the `Charset` that was used to encode the path, you can use `to_str_using_charset` instead of [display]. is_dir! : Path => Result Bool [PathErr IOErr] Description: Returns true if the path exists on disk and is pointing at a directory. Returns `Ok false` if the path exists and it is not a directory. If the path does not exist, this function will return `Err (PathErr PathDoesNotExist)`. This uses [rust's std::path::is_dir](https://doc.rust-lang.org/std/path/struct.Path.html#method.is_dir). > [`File.is_dir`](File#is_dir!) does the same thing, except it takes a [Str] instead of a [Path]. is_file! : Path => Result Bool [PathErr IOErr] Description: Returns true if the path exists on disk and is pointing at a regular file. Returns `Ok false` if the path exists and it is not a file. If the path does not exist, this function will return `Err (PathErr PathDoesNotExist)`. This uses [rust's std::path::is_file](https://doc.rust-lang.org/std/path/struct.Path.html#method.is_file). > [`File.is_file`](File#is_file!) does the same thing, except it takes a [Str] instead of a [Path]. is_sym_link! : Path => Result Bool [PathErr IOErr] Description: Returns true if the path exists on disk and is pointing at a symbolic link. Returns `Ok false` if the path exists and it is not a symbolic link. If the path does not exist, this function will return `Err (PathErr PathDoesNotExist)`. This uses [rust's std::path::is_symlink](https://doc.rust-lang.org/std/path/struct.Path.html#method.is_symlink). > [`File.is_sym_link`](File#is_sym_link!) does the same thing, except it takes a [Str] instead of a [Path]. exists! : Path => Result Bool [PathErr IOErr] Description: Returns true if the path exists on disk. This uses [rust's std::path::try_exists](https://doc.rust-lang.org/std/path/struct.Path.html#method.try_exists). > [`File.exists!`](File#exists!) does the same thing, except it takes a [Str] instead of a [Path]. type! : Path => Result [ IsFile, IsDir, IsSymLink ] [PathErr IOErr] Description: Return the type of the path if the path exists on disk. > [`File.type`](File#type!) does the same thing, except it takes a [Str] instead of a [Path]. with_extension : Path, Str -> Path Description: If the last component of this path has no `.`, appends `.` followed by the given string. Otherwise, replaces everything after the last `.` with the given string. ``` # Each of these gives "foo/bar/baz.txt" Path.from_str("foo/bar/baz") |> Path.with_extension("txt") Path.from_str("foo/bar/baz.") |> Path.with_extension("txt") Path.from_str("foo/bar/baz.xz") |> Path.with_extension("txt") ``` delete! : Path => Result {} [FileWriteErr Path IOErr] Description: Deletes a file from the filesystem. Performs a [`DeleteFile`](https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-deletefile) on Windows and [`unlink`](https://en.wikipedia.org/wiki/Unlink_(Unix)) on UNIX systems. On Windows, this will fail when attempting to delete a readonly file; the file's readonly permission must be disabled before it can be successfully deleted. ``` # Deletes the file named `myfile.dat` Path.delete!(Path.from_str("myfile.dat"))? ``` > This does not securely erase the file's contents from disk; instead, the operating system marks the space it was occupying as safe to write over in the future. Also, the operating system may not immediately mark the space as free; for example, on Windows it will wait until the last file handle to it is closed, and on UNIX, it will not remove it until the last [hard link](https://en.wikipedia.org/wiki/Hard_link) to it has been deleted. > [`File.delete!`](File#delete!) does the same thing, except it takes a [Str] instead of a [Path]. read_utf8! : Path => Result Str [ FileReadErr Path IOErr, FileReadUtf8Err Path ] Description: Reads a [Str] from a file containing [UTF-8](https://en.wikipedia.org/wiki/UTF-8)-encoded text. ``` # Reads UTF-8 encoded text into a Str from the file "myfile.txt" contents_str = Path.read_utf8!(Path.from_str("myfile.txt"))? ``` This opens the file first and closes it after reading its contents. The task will fail with `FileReadUtf8Err` if the given file contains invalid UTF-8. > To read unformatted bytes from a file, you can use [Path.read_bytes!] instead. > > [`File.read_utf8!`](File#read_utf8!) does the same thing, except it takes a [Str] instead of a [Path]. read_bytes! : Path => Result (List U8) [FileReadErr Path IOErr] Description: Reads all the bytes in a file. ``` # Read all the bytes in `myfile.txt`. contents_bytes = Path.read_bytes!(Path.from_str("myfile.txt"))? ``` This opens the file first and closes it after reading its contents. > To read and decode data from a file into a [Str], you can use [Path.read_utf8!] instead. > > [`File.read_bytes`](File#read_bytes!) does the same thing, except it takes a [Str] instead of a [Path]. list_dir! : Path => Result (List Path) [DirErr IOErr] Description: Lists the files and directories inside the directory. > [`Dir.list`](Dir#list!) does the same thing, except it takes a [Str] instead of a [Path]. delete_empty! : Path => Result {} [DirErr IOErr] Description: Deletes a directory if it's empty This may fail if: - the path doesn't exist - the path is not a directory - the directory is not empty - the user lacks permission to remove the directory. > [`Dir.delete_empty`](Dir#delete_empty!) does the same thing, except it takes a [Str] instead of a [Path]. delete_all! : Path => Result {} [DirErr IOErr] Description: Recursively deletes a directory as well as all files and directories inside it. This may fail if: - the path doesn't exist - the path is not a directory - the user lacks permission to remove the directory. > [`Dir.delete_all`](Dir#delete_all!) does the same thing, except it takes a [Str] instead of a [Path]. create_dir! : Path => Result {} [DirErr IOErr] Description: Creates a directory This may fail if: - a parent directory does not exist - the user lacks permission to create a directory there - the path already exists. > [`Dir.create`](Dir#create!) does the same thing, except it takes a [Str] instead of a [Path]. create_all! : Path => Result {} [DirErr IOErr] Description: Creates a directory recursively adding any missing parent directories. This may fail if: - the user lacks permission to create a directory there - the path already exists > [`Dir.create_all`](Dir#create_all!) does the same thing, except it takes a [Str] instead of a [Path]. hard_link! : Path, Path => Result {} [LinkErr IOErr] Description: Creates a new [hard link](https://en.wikipedia.org/wiki/Hard_link) on the filesystem. The link path will be a link pointing to the original path. Note that systems often require these two paths to both be located on the same filesystem. This uses [rust's std::fs::hard_link](https://doc.rust-lang.org/std/fs/fn.hard_link.html). > [File.hard_link!] does the same thing, except it takes a [Str] instead of a [Path]. rename! : Path, Path => Result {} [PathErr IOErr] Description: Renames a file or directory. This uses [rust's std::fs::rename](https://doc.rust-lang.org/std/fs/fn.rename.html). ### Arg Description: An OS-aware (see below) representation of a command-line argument. Though we tend to think of args as Unicode strings, **most operating systems represent command-line arguments as lists of bytes** that aren't necessarily UTF-8 encoded. Windows doesn't even use bytes, but U16s. Most of the time, you will pass these to packages and they will handle the encoding for you, but for quick-and-dirty code you can use [display] to convert these to [Str] in a lossy way. to_os_raw : Arg -> [ Unix (List U8), Windows (List U16) ] Description: Unwrap an [Arg] into a raw, OS-aware numeric list. This is a good way to pass [Arg]s to Roc packages. from_os_raw : [ Unix (List U8), Windows (List U16) ] -> Arg Description: Wrap a raw, OS-aware numeric list into an [Arg]. display : Arg -> Str Description: Convert an Arg to a `Str` for display purposes. NB: this will currently crash if there is invalid utf8 bytes, in future this will be lossy and replace any invalid bytes with the [Unicode Replacement Character U+FFFD �](https://en.wikipedia.org/wiki/Specials_(Unicode_block)) ### Dir Description: Tag union of possible errors when reading and writing a file or directory. > This is the same as [`File.IOErr`](File#IOErr). list! : Str => Result (List Path) [DirErr IOErr] Description: Lists the files and directories inside the directory. > [Path.list_dir!] does the same thing, except it takes a [Path] instead of a [Str]. delete_empty! : Str => Result {} [DirErr IOErr] Description: Deletes a directory if it's empty This may fail if: - the path doesn't exist - the path is not a directory - the directory is not empty - the user lacks permission to remove the directory. > [Path.delete_empty!] does the same thing, except it takes a [Path] instead of a [Str]. delete_all! : Str => Result {} [DirErr IOErr] Description: Recursively deletes the directory as well as all files and directories inside it. This may fail if: - the path doesn't exist - the path is not a directory - the user lacks permission to remove the directory. > [Path.delete_all!] does the same thing, except it takes a [Path] instead of a [Str]. create! : Str => Result {} [DirErr IOErr] Description: Creates a directory This may fail if: - a parent directory does not exist - the user lacks permission to create a directory there - the path already exists. > [Path.create_dir!] does the same thing, except it takes a [Path] instead of a [Str]. create_all! : Str => Result {} [DirErr IOErr] Description: Creates a directory recursively adding any missing parent directories. This may fail if: - the user lacks permission to create a directory there - the path already exists > [Path.create_all!] does the same thing, except it takes a [Path] instead of a [Str]. ### Env cwd! : {} => Result Path [CwdUnavailable] Description: Reads the [current working directory](https://en.wikipedia.org/wiki/Working_directory) from the environment. File operations on relative [Path]s are relative to this directory. set_cwd! : Path => Result {} [InvalidCwd] Description: Sets the [current working directory](https://en.wikipedia.org/wiki/Working_directory) in the environment. After changing it, file operations on relative [Path]s will be relative to this directory. exe_path! : {} => Result Path [ExePathUnavailable] Description: Gets the path to the currently-running executable. var! : Str => Result Str [VarNotFound Str] Description: Reads the given environment variable. If the value is invalid Unicode, the invalid parts will be replaced with the [Unicode replacement character](https://unicode.org/glossary/#replacement_character) ('�'). decode! : Str => Result val [ VarNotFound Str, DecodeErr DecodeError ] where val implements Decoding Description: Reads the given environment variable and attempts to decode it into the correct type. The type being decoded into will be determined by type inference. For example, if this ends up being used like a `Result U16 _` then the environment variable will be decoded as a string representation of a `U16`. Trying to decode into any other type will fail with a `DecodeErr`. Supported types include; - Strings, - Numbers, as long as they contain only numeric digits, up to one `.`, and an optional `-` at the front for negative numbers, and - Comma-separated lists (of either strings or numbers), as long as there are no spaces after the commas. For example, consider we want to decode the environment variable `NUM_THINGS`; ``` # Reads "NUM_THINGS" and decodes into a U16 get_u16_var! : Str => Result U16 [VarNotFound, DecodeErr DecodeError] [Read [Env]] get_u16_var! = |var| Env.decode!(var) ``` If `NUM_THINGS=123` then `get_u16_var` succeeds with the value of `123u16`. However if `NUM_THINGS=123456789`, then `get_u16_var` will fail with [DecodeErr](https://www.roc-lang.org/builtins/Decode#DecodeError) because `123456789` is too large to fit in a [U16](https://www.roc-lang.org/builtins/Num#U16). dict! : {} => Dict Str Str Description: Reads all the process's environment variables into a [Dict]. If any key or value contains invalid Unicode, the [Unicode replacement character](https://unicode.org/glossary/#replacement_character) will be used in place of any parts of keys or values that are invalid Unicode. platform! : {} => { arch : ARCH, os : OS } Description: Returns the current Achitecture and Operating System. `ARCH : [X86, X64, ARM, AARCH64, OTHER Str]` `OS : [LINUX, MACOS, WINDOWS, OTHER Str]` Note these values are constants from when the platform is built. temp_dir! : {} => Path Description: This uses rust's [`std::env::temp_dir()`](https://doc.rust-lang.org/std/env/fn.temp_dir.html) !! From the Rust documentation: The temporary directory may be shared among users, or between processes with different privileges; thus, the creation of any files or directories in the temporary directory must use a secure method to create a uniquely named file. Creating a file or directory with a fixed or predictable name may result in “insecure temporary file” security vulnerabilities. ### File Description: Tag union of possible errors when reading and writing a file or directory. **NotFound** - An entity was not found, often a file. **PermissionDenied** - The operation lacked the necessary privileges to complete. **BrokenPipe** - The operation failed because a pipe was closed. **AlreadyExists** - An entity already exists, often a file. **Interrupted** - This operation was interrupted. Interrupted operations can typically be retried. **Unsupported** - This operation is unsupported on this platform. This means that the operation can never succeed. **OutOfMemory** - An operation could not be completed, because it failed to allocate enough memory. **Other** - A custom error that does not fall under any other I/O error kind. write! : val, Str, fmt => Result {} [FileWriteErr Path IOErr] where val implements Encoding, fmt implements EncoderFormatting Description: Write data to a file. First encode a `val` using a given `fmt` which implements the ability [Encode.EncoderFormatting](https://www.roc-lang.org/builtins/Encode#EncoderFormatting). For example, suppose you have a `Json.utf8` which implements [Encode.EncoderFormatting](https://www.roc-lang.org/builtins/Encode#EncoderFormatting). You can use this to write [JSON](https://en.wikipedia.org/wiki/JSON) data to a file like this: ``` # Writes `{"some":"json stuff"}` to the file `output.json`: File.write!( { some: "json stuff" }, "output.json", Json.utf8, )? ``` This opens the file first and closes it after writing to it. If writing to the file fails, for example because of a file permissions issue, the task fails with [WriteErr]. > To write unformatted bytes to a file, you can use [File.write_bytes!] instead. > > [Path.write!] does the same thing, except it takes a [Path] instead of a [Str]. write_bytes! : List U8, Str => Result {} [FileWriteErr Path IOErr] Description: Writes bytes to a file. ``` # Writes the bytes 1, 2, 3 to the file `myfile.dat`. File.write_bytes!([1, 2, 3], "myfile.dat")? ``` This opens the file first and closes it after writing to it. > To format data before writing it to a file, you can use [File.write!] instead. > > [Path.write_bytes!] does the same thing, except it takes a [Path] instead of a [Str]. write_utf8! : Str, Str => Result {} [FileWriteErr Path IOErr] Description: Writes a [Str] to a file, encoded as [UTF-8](https://en.wikipedia.org/wiki/UTF-8). ``` # Writes "Hello!" encoded as UTF-8 to the file `myfile.txt`. File.write_utf8!("Hello!", "myfile.txt")? ``` This opens the file first and closes it after writing to it. > To write unformatted bytes to a file, you can use [File.write_bytes!] instead. > > [Path.write_utf8!] does the same thing, except it takes a [Path] instead of a [Str]. delete! : Str => Result {} [FileWriteErr Path IOErr] Description: Deletes a file from the filesystem. Performs a [`DeleteFile`](https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-deletefile) on Windows and [`unlink`](https://en.wikipedia.org/wiki/Unlink_(Unix)) on UNIX systems. On Windows, this will fail when attempting to delete a readonly file; the file's readonly permission must be disabled before it can be successfully deleted. ``` # Deletes the file named `myfile.dat` File.delete!("myfile.dat")? ``` > This does not securely erase the file's contents from disk; instead, the operating system marks the space it was occupying as safe to write over in the future. Also, the operating system may not immediately mark the space as free; for example, on Windows it will wait until the last file handle to it is closed, and on UNIX, it will not remove it until the last [hard link](https://en.wikipedia.org/wiki/Hard_link) to it has been deleted. > > [Path.delete!] does the same thing, except it takes a [Path] instead of a [Str]. read_bytes! : Str => Result (List U8) [FileReadErr Path IOErr] Description: Reads all the bytes in a file. ``` # Read all the bytes in `myfile.txt`. bytes = File.read_bytes!("myfile.txt")? ``` This opens the file first and closes it after reading its contents. > To read and decode data from a file into a [Str], you can use [File.read_utf8!] instead. > > [Path.read_bytes!] does the same thing, except it takes a [Path] instead of a [Str]. read_utf8! : Str => Result Str [ FileReadErr Path IOErr, FileReadUtf8Err Path ] Description: Reads a [Str] from a file containing [UTF-8](https://en.wikipedia.org/wiki/UTF-8)-encoded text. ``` # Reads UTF-8 encoded text into a Str from the file "myfile.txt" str = File.read_utf8!("myfile.txt")? ``` This opens the file first and closes it after reading its contents. The task will fail with `FileReadUtf8Err` if the given file contains invalid UTF-8. > To read unformatted bytes from a file, you can use [File.read_bytes!] instead. > [Path.read_utf8!] does the same thing, except it takes a [Path] instead of a [Str]. hard_link! : Str, Str => Result {} [LinkErr IOErr] Description: Creates a new [hard link](https://en.wikipedia.org/wiki/Hard_link) on the filesystem. The link path will be a link pointing to the original path. Note that systems often require these two paths to both be located on the same filesystem. This uses [rust's std::fs::hard_link](https://doc.rust-lang.org/std/fs/fn.hard_link.html). > [Path.hard_link!] does the same thing, except it takes a [Path] instead of a [Str]. is_dir! : Str => Result Bool [PathErr IOErr] Description: Returns True if the path exists on disk and is pointing at a directory. Returns False if the path exists and it is not a directory. If the path does not exist, this function will return `Err (PathErr PathDoesNotExist)`. This uses [rust's std::path::is_dir](https://doc.rust-lang.org/std/path/struct.Path.html#method.is_dir). > [Path.is_dir!] does the same thing, except it takes a [Path] instead of a [Str]. is_file! : Str => Result Bool [PathErr IOErr] Description: Returns True if the path exists on disk and is pointing at a regular file. Returns False if the path exists and it is not a file. If the path does not exist, this function will return `Err (PathErr PathDoesNotExist)`. This uses [rust's std::path::is_file](https://doc.rust-lang.org/std/path/struct.Path.html#method.is_file). > [Path.is_file!] does the same thing, except it takes a [Path] instead of a [Str]. is_sym_link! : Str => Result Bool [PathErr IOErr] Description: Returns True if the path exists on disk and is pointing at a symbolic link. Returns False if the path exists and it is not a symbolic link. If the path does not exist, this function will return `Err (PathErr PathDoesNotExist)`. This uses [rust's std::path::is_symlink](https://doc.rust-lang.org/std/path/struct.Path.html#method.is_symlink). > [Path.is_sym_link!] does the same thing, except it takes a [Path] instead of a [Str]. exists! : Str => Result Bool [PathErr IOErr] Description: Returns true if the path exists on disk. This uses [rust's std::path::try_exists](https://doc.rust-lang.org/std/path/struct.Path.html#method.try_exists). is_executable! : Str => Result Bool [PathErr IOErr] Description: Checks if the file has the execute permission for the current process. This uses rust [std::fs::Metadata](https://doc.rust-lang.org/std/fs/struct.Metadata.html). is_readable! : Str => Result Bool [PathErr IOErr] Description: Checks if the file has the readable permission for the current process. This uses rust [std::fs::Metadata](https://doc.rust-lang.org/std/fs/struct.Metadata.html). is_writable! : Str => Result Bool [PathErr IOErr] Description: Checks if the file has the writeable permission for the current process. This uses rust [std::fs::Metadata](https://doc.rust-lang.org/std/fs/struct.Metadata.html). time_accessed! : Str => Result Utc [PathErr IOErr] Description: Returns the time when the file was last accessed. This uses [rust's std::fs::Metadata::accessed](https://doc.rust-lang.org/std/fs/struct.Metadata.html#method.accessed). Note that this is [not guaranteed to be correct in all cases](https://doc.rust-lang.org/std/fs/struct.Metadata.html#method.accessed). NOTE: this function will not work on Linux if the platform was built with musl, which is the case for the normal tar.br URL release. See "Running Locally" in the README.md file to build without musl. time_modified! : Str => Result Utc [PathErr IOErr] Description: Returns the time when the file was last modified. This uses [rust's std::fs::Metadata::modified](https://doc.rust-lang.org/std/fs/struct.Metadata.html#method.modified). NOTE: this function will not work on Linux if the platform was built with musl, which is the case for the normal tar.br URL release. See "Running Locally" in the README.md file to build without musl. time_created! : Str => Result Utc [PathErr IOErr] Description: Returns the time when the file was created. This uses [rust's std::fs::Metadata::created](https://doc.rust-lang.org/std/fs/struct.Metadata.html#method.created). NOTE: this function will not work on Linux if the platform was built with musl, which is the case for the normal tar.br URL release. See "Running Locally" in the README.md file to build without musl. rename! : Str, Str => Result {} [PathErr IOErr] Description: Renames a file or directory. This uses [rust's std::fs::rename](https://doc.rust-lang.org/std/fs/fn.rename.html). type! : Str => Result [ IsFile, IsDir, IsSymLink ] [PathErr IOErr] Description: Return the type of the path if the path exists on disk. This uses [rust's std::path::is_symlink](https://doc.rust-lang.org/std/path/struct.Path.html#method.is_symlink). > [Path.type!] does the same thing, except it takes a [Path] instead of a [Str]. open_reader! : Str => Result Reader [GetFileReadErr Path IOErr] Description: Try to open a `File.Reader` for buffered (= part by part) reading given a path string. See [examples/file-read-buffered.roc](https://github.com/roc-lang/basic-cli/blob/main/examples/file-read-buffered.roc) for example usage. This uses [rust's std::io::BufReader](https://doc.rust-lang.org/std/io/struct.BufReader.html). Use [read_utf8!] if you want to get the entire file contents at once. open_reader_with_capacity! : Str, U64 => Result Reader [GetFileReadErr Path IOErr] Description: Try to open a `File.Reader` for buffered (= part by part) reading given a path string. The buffer will be created with the specified capacity. See [examples/file-read-buffered.roc](https://github.com/roc-lang/basic-cli/blob/main/examples/file-read-buffered.roc) for example usage. This uses [rust's std::io::BufReader](https://doc.rust-lang.org/std/io/struct.BufReader.html). Use [read_utf8!] if you want to get the entire file contents at once. read_line! : Reader => Result (List U8) [FileReadErr Path IOErr] Description: Try to read a line from a file given a Reader. The line will be provided as the list of bytes (`List U8`) until a newline (`0xA` byte). This list will be empty when we reached the end of the file. See [examples/file-read-buffered.roc](https://github.com/roc-lang/basic-cli/blob/main/examples/file-read-buffered.roc) for example usage. This uses [rust's `BufRead::read_line`](https://doc.rust-lang.org/std/io/trait.BufRead.html#method.read_line). Use [read_utf8!] if you want to get the entire file contents at once. size_in_bytes! : Str => Result U64 [PathErr IOErr] Description: Returns the size of a file in bytes. This uses [rust's std::fs::Metadata::len](https://doc.rust-lang.org/std/fs/struct.Metadata.html#method.len). ### Http Description: Represents an HTTP method: `[OPTIONS, GET, POST, PUT, DELETE, HEAD, TRACE, CONNECT, PATCH, EXTENSION Str]` Description: Represents an HTTP header e.g. `Content-Type: application/json`. Header is a `{ name : Str, value : Str }`. Description: Represents an HTTP request. Request is a record: ``` { method : Method, headers : List Header, uri : Str, body : List U8, timeout_ms : [TimeoutMilliseconds U64, NoTimeout], } ``` Description: Represents an HTTP response. Response is a record with the following fields: ``` { status : U16, headers : List Header, body : List U8 } ``` default_request : Request Description: A default [Request] value with the following values: ``` { method: GET headers: [] uri: "" body: [] timeout_ms: NoTimeout } ``` Example: ``` # GET "roc-lang.org" { Http.default_request & uri: "https://www.roc-lang.org", } ``` header : ( Str, Str ) -> Header Description: An HTTP header for configuring requests. See common headers [here](https://en.wikipedia.org/wiki/List_of_HTTP_header_fields). Example: `header(("Content-Type", "application/json"))` send! : Request => Result Response [ HttpErr [ Timeout, NetworkError, BadBody, Other (List U8) ] ] Description: Send an HTTP request, succeeds with a [Response] or fails with a [HttpErr _]. ``` # Prints out the HTML of the Roc-lang website. response : Response response = Http.send!({ Http.default_request & uri: "https://www.roc-lang.org" })? Stdout.line!(Str.from_utf8(response.body))? ``` get! : Str, fmt => Result body [ HttpDecodingFailed, HttpErr ] where body implements Decoding, fmt implements DecoderFormatting Description: Try to perform an HTTP get request and convert (decode) the received bytes into a Roc type. Very useful for working with Json. ``` import json.Json # On the server side we send `Encode.to_bytes({foo: "Hello Json!"}, Json.utf8)` { foo } = Http.get!("http://localhost:8000", Json.utf8)? ``` get_utf8! : Str => Result Str [ BadBody Str, HttpErr ] Description: Try to perform an HTTP get request and convert the received bytes (in the body) into a UTF-8 string. ``` # On the server side we, send `Str.to_utf8("Hello utf8")` hello_str : Str hello_str = Http.get_utf8!("http://localhost:8000")? ``` ### Stderr Description: **NotFound** - An entity was not found, often a file. **PermissionDenied** - The operation lacked the necessary privileges to complete. **BrokenPipe** - The operation failed because a pipe was closed. **AlreadyExists** - An entity already exists, often a file. **Interrupted** - This operation was interrupted. Interrupted operations can typically be retried. **Unsupported** - This operation is unsupported on this platform. This means that the operation can never succeed. **OutOfMemory** - An operation could not be completed, because it failed to allocate enough memory. **Other** - A custom error that does not fall under any other I/O error kind. line! : Str => Result {} [StderrErr IOErr] Description: Write the given string to [standard error](https://en.wikipedia.org/wiki/Standard_streams#Standard_error_(stderr)), followed by a newline. > To write to `stderr` without the newline, see [Stderr.write!]. write! : Str => Result {} [StderrErr IOErr] Description: Write the given string to [standard error](https://en.wikipedia.org/wiki/Standard_streams#Standard_error_(stderr)). Most terminals will not actually display strings that are written to them until they receive a newline, so this may appear to do nothing until you write a newline! > To write to `stderr` with a newline at the end, see [Stderr.line!]. write_bytes! : List U8 => Result {} [StderrErr IOErr] ### Stdin Description: **NotFound** - An entity was not found, often a file. **PermissionDenied** - The operation lacked the necessary privileges to complete. **BrokenPipe** - The operation failed because a pipe was closed. **AlreadyExists** - An entity already exists, often a file. **Interrupted** - This operation was interrupted. Interrupted operations can typically be retried. **Unsupported** - This operation is unsupported on this platform. This means that the operation can never succeed. **OutOfMemory** - An operation could not be completed, because it failed to allocate enough memory. **Other** - A custom error that does not fall under any other I/O error kind. line! : {} => Result Str [ EndOfFile, StdinErr IOErr ] Description: Read a line from [standard input](https://en.wikipedia.org/wiki/Standard_streams#Standard_input_(stdin)). > This task will block the program from continuing until `stdin` receives a newline character (e.g. because the user pressed Enter in the terminal), so using it can result in the appearance of the programming having gotten stuck. It's often helpful to print a prompt first, so the user knows it's necessary to enter something before the program will continue. bytes! : {} => Result (List U8) [ EndOfFile, StdinErr IOErr ] Description: Read bytes from [standard input](https://en.wikipedia.org/wiki/Standard_streams#Standard_input_(stdin)). ‼️ This function can read no more than 16,384 bytes at a time. Use [read_to_end!] if you need more. > This is typically used in combintation with [Tty.enable_raw_mode!], which disables defaults terminal bevahiour and allows reading input without buffering until Enter key is pressed. read_to_end! : {} => Result (List U8) [StdinErr IOErr] Description: Read all bytes from [standard input](https://en.wikipedia.org/wiki/Standard_streams#Standard_input_(stdin)) until [EOF](https://en.wikipedia.org/wiki/End-of-file) in this source. ### Stdout Description: **NotFound** - An entity was not found, often a file. **PermissionDenied** - The operation lacked the necessary privileges to complete. **BrokenPipe** - The operation failed because a pipe was closed. **AlreadyExists** - An entity already exists, often a file. **Interrupted** - This operation was interrupted. Interrupted operations can typically be retried. **Unsupported** - This operation is unsupported on this platform. This means that the operation can never succeed. **OutOfMemory** - An operation could not be completed, because it failed to allocate enough memory. **Other** - A custom error that does not fall under any other I/O error kind. line! : Str => Result {} [StdoutErr IOErr] Description: Write the given string to [standard output](https://en.wikipedia.org/wiki/Standard_streams#Standard_output_(stdout)), followed by a newline. > To write to `stdout` without the newline, see [Stdout.write!]. write! : Str => Result {} [StdoutErr IOErr] Description: Write the given string to [standard output](https://en.wikipedia.org/wiki/Standard_streams#Standard_output_(stdout)). Note that many terminals will not actually display strings that are written to them until they receive a newline, so this may appear to do nothing until you write a newline! > To write to `stdout` with a newline at the end, see [Stdout.line!]. write_bytes! : List U8 => Result {} [StdoutErr IOErr] ### Tcp Description: Represents a TCP stream. Description: Represents errors that can occur when connecting to a remote host. Description: Represents errors that can occur when performing an effect with a [Stream]. connect! : Str, U16 => Result Stream (ConnectErr ) Description: Opens a TCP connection to a remote host. ``` # Connect to localhost:8080 stream = Tcp.connect!("localhost", 8080)? ``` The connection is automatically closed when the last reference to the stream is dropped. Examples of valid hostnames: - `127.0.0.1` - `::1` - `localhost` - `roc-lang.org` read_up_to! : Stream, U64 => Result (List U8) [TcpReadErr StreamErr] Description: Read up to a number of bytes from the TCP stream. ``` # Read up to 64 bytes from the stream received_bytes = Tcp.read_up_to!(stream, 64)? ``` > To read an exact number of bytes or fail, you can use [Tcp.read_exactly!] instead. read_exactly! : Stream, U64 => Result (List U8) [ TcpReadErr StreamErr, TcpUnexpectedEOF ] Description: Read an exact number of bytes or fail. ``` bytes = Tcp.read_exactly!(stream, 64)? ``` `TcpUnexpectedEOF` is returned if the stream ends before the specfied number of bytes is reached. read_until! : Stream, U8 => Result (List U8) [TcpReadErr StreamErr] Description: Read until a delimiter or EOF is reached. ``` # Read until null terminator bytes = Tcp.read_until!(stream, 0)? ``` If found, the delimiter is included as the last byte. > To read until a newline is found, you can use [Tcp.read_line!] which conveniently decodes to a [Str]. read_line! : Stream => Result Str [ TcpReadErr StreamErr, TcpReadBadUtf8 ] Description: Read until a newline or EOF is reached. ``` # Read a line and then print it to `stdout` line_str = Tcp.read_line!(stream)? Stdout.line(line_str)? ``` If found, the newline is included as the last character in the [Str]. write! : Stream, List U8 => Result {} [TcpWriteErr StreamErr] Description: Writes bytes to a TCP stream. ``` # Writes the bytes 1, 2, 3 Tcp.write!(stream, [1, 2, 3])? ``` > To write a [Str], you can use [Tcp.write_utf8!] instead. write_utf8! : Stream, Str => Result {} [TcpWriteErr StreamErr] Description: Writes a [Str] to a TCP stream, encoded as [UTF-8](https://en.wikipedia.org/wiki/UTF-8). ``` # Write "Hi from Roc!" encoded as UTF-8 Tcp.write_utf8!(stream, "Hi from Roc!")? ``` > To write unformatted bytes, you can use [Tcp.write!] instead. connect_err_to_str : ConnectErr -> Str Description: Convert a [ConnectErr] to a [Str] you can print. ``` when err is TcpPerfomErr(TcpConnectErr(connect_err)) -> Stderr.line!(Tcp.connect_err_to_str(connect_err)) ``` stream_err_to_str : StreamErr -> Str Description: Convert a [StreamErr] to a [Str] you can print. ``` when err is TcpPerformErr(TcpReadErr(err)) -> err_str = Tcp.stream_err_to_str(err) Stderr.line!("Error while reading: ${err_str}") TcpPerformErr(TcpWriteErr(err)) -> err_str = Tcp.stream_err_to_str(err) Stderr.line!("Error while writing: ${err_str}") ``` ### Url Description: A [Uniform Resource Locator](https://en.wikipedia.org/wiki/URL). It could be an absolute address, such as `https://roc-lang.org/authors` or a relative address, such as `/authors`. You can create one using [Url.from_str]. reserve : Url, U64 -> Url Description: Reserve the given number of bytes as extra capacity. This can avoid reallocation when calling multiple functions that increase the length of the URL. The following example reserves 50 bytes, then builds the url `https://example.com/stuff?caf%C3%A9=du%20Monde&email=hi%40example.com`; ``` Url.from_str("https://example.com") |> Url.reserve(50) |> Url.append("stuff") |> Url.append_param("café", "du Monde") |> Url.append_param("email", "hi@example.com") ``` The [Str.count_utf8_bytes](https://www.roc-lang.org/builtins/Str#count_utf8_bytes) function can be helpful in finding out how many bytes to reserve. There is no `Url.with_capacity` because it's better to reserve extra capacity on a [Str] first, and then pass that string to [Url.from_str]. This function will make use of the extra capacity. from_str : Str -> Url Description: Create a [Url] without validating or [percent-encoding](https://en.wikipedia.org/wiki/Percent-encoding) anything. ``` Url.from_str("https://example.com#stuff") ``` URLs can be absolute, like `https://example.com`, or they can be relative, like `/blah`. ``` Url.from_str("/this/is#relative") ``` Since nothing is validated, this can return invalid URLs. ``` Url.from_str("https://this is not a valid URL, not at all!") ``` Naturally, passing invalid URLs to functions that need valid ones will tend to result in errors. to_str : Url -> Str Description: Return a [Str] representation of this URL. ``` # Gives "https://example.com/two%20words" Url.from_str("https://example.com") |> Url.append("two words") |> Url.to_str ``` append : Url, Str -> Url Description: [Percent-encodes](https://en.wikipedia.org/wiki/Percent-encoding) a [path component](https://en.wikipedia.org/wiki/Uniform_Resource_Identifier#Syntax) and appends to the end of the URL's path. This will be appended before any queries and fragments. If the given path string begins with `/` and the URL already ends with `/`, one will be ignored. This avoids turning a single slash into a double slash. If either the given URL or the given string is empty, no `/` will be added. ``` # Gives https://example.com/some%20stuff Url.from_str("https://example.com") |> Url.append("some stuff") # Gives https://example.com/stuff?search=blah#fragment Url.from_str("https://example.com?search=blah#fragment") |> Url.append("stuff") # Gives https://example.com/things/stuff/more/etc/" Url.from_str("https://example.com/things/") |> Url.append("/stuff/") |> Url.append("/more/etc/") # Gives https://example.com/things Url.from_str("https://example.com/things") |> Url.append("") ``` append_param : Url, Str, Str -> Url Description: Adds a [Str] query parameter to the end of the [Url]. The key and value both get [percent-encoded](https://en.wikipedia.org/wiki/Percent-encoding). ``` # Gives https://example.com?email=someone%40example.com Url.from_str("https://example.com") |> Url.append_param("email", "someone@example.com") ``` This can be called multiple times on the same URL. ``` # Gives https://example.com?caf%C3%A9=du%20Monde&email=hi%40example.com Url.from_str("https://example.com") |> Url.append_param("café", "du Monde") |> Url.append_param("email", "hi@example.com") ``` with_query : Url, Str -> Url Description: Replaces the URL's [query](https://en.wikipedia.org/wiki/URL#Syntax)—the part after the `?`, if it has one, but before any `#` it might have. Passing `""` removes the `?` (if there was one). ``` # Gives https://example.com?newQuery=thisRightHere#stuff Url.from_str("https://example.com?key1=val1&key2=val2#stuff") |> Url.with_query("newQuery=thisRightHere") # Gives https://example.com#stuff Url.from_str("https://example.com?key1=val1&key2=val2#stuff") |> Url.with_query("") ``` query : Url -> Str Description: Returns the URL's [query](https://en.wikipedia.org/wiki/URL#Syntax)—the part after the `?`, if it has one, but before any `#` it might have. Returns `""` if the URL has no query. ``` # Gives "key1=val1&key2=val2&key3=val3" Url.from_str("https://example.com?key1=val1&key2=val2&key3=val3#stuff") |> Url.query # Gives "" Url.from_str("https://example.com#stuff") |> Url.query ``` has_query : Url -> Bool Description: Returns [Bool.true] if the URL has a `?` in it. ``` # Gives Bool.true Url.from_str("https://example.com?key=value#stuff") |> Url.has_query # Gives Bool.false Url.from_str("https://example.com#stuff") |> Url.has_query ``` fragment : Url -> Str Description: Returns the URL's [fragment](https://en.wikipedia.org/wiki/URL#Syntax)—the part after the `#`, if it has one. Returns `""` if the URL has no fragment. ``` # Gives "stuff" Url.from_str("https://example.com#stuff") |> Url.fragment # Gives "" Url.from_str("https://example.com") |> Url.fragment ``` with_fragment : Url, Str -> Url Description: Replaces the URL's [fragment](https://en.wikipedia.org/wiki/URL#Syntax). If the URL didn't have a fragment, adds one. Passing `""` removes the fragment. ``` # Gives https://example.com#things Url.from_str("https://example.com#stuff") |> Url.with_fragment("things") # Gives https://example.com#things Url.from_str("https://example.com") |> Url.with_fragment("things") # Gives https://example.com Url.from_str("https://example.com#stuff") |> Url.with_fragment "" ``` has_fragment : Url -> Bool Description: Returns [Bool.true] if the URL has a `#` in it. ``` # Gives Bool.true Url.from_str("https://example.com?key=value#stuff") |> Url.has_fragment # Gives Bool.false Url.from_str("https://example.com?key=value") |> Url.has_fragment ``` query_params : Url -> Dict Str Str path : Url -> Str Description: Returns the URL's [path](https://en.wikipedia.org/wiki/URL#Syntax)—the part after the scheme and authority (e.g. `https://`) but before any `?` or `#` it might have. Returns `""` if the URL has no path. ``` # Gives "example.com/" Url.from_str("https://example.com/?key1=val1&key2=val2&key3=val3#stuff") |> Url.path ``` ``` # Gives "/foo/" Url.from_str("/foo/?key1=val1&key2=val2&key3=val3#stuff") |> Url.path ``` ### Utc Description: Stores a timestamp as nanoseconds since UNIX EPOCH now! : {} => Utc Description: Duration since UNIX EPOCH to_millis_since_epoch : Utc -> I128 Description: Convert Utc timestamp to milliseconds from_millis_since_epoch : I128 -> Utc Description: Convert milliseconds to Utc timestamp to_nanos_since_epoch : Utc -> I128 Description: Convert Utc timestamp to nanoseconds from_nanos_since_epoch : I128 -> Utc Description: Convert nanoseconds to Utc timestamp delta_as_millis : Utc, Utc -> U128 Description: Calculate milliseconds between two Utc timestamps delta_as_nanos : Utc, Utc -> U128 Description: Calculate nanoseconds between two Utc timestamps to_iso_8601 : Utc -> Str Description: Convert Utc timestamp to ISO 8601 string. For example: 2023-11-14T23:39:39Z ### Sleep millis! : U64 => {} Description: Sleep for at least the given number of milliseconds. This uses [rust's std::thread::sleep](https://doc.rust-lang.org/std/thread/fn.sleep.html). ### Cmd exec! : Str, List Str => Result {} [ ExecFailed { command : Str, exit_code : I32 }, FailedToGetExitCode { command : Str, err : IOErr } ] Description: Simplest way to execute a command while inheriting stdin, stdout and stderr from parent. If you want to capture the output, use [exec_output!] instead. ``` # Call echo to print "hello world" Cmd.exec!("echo", ["hello world"])? ``` exec_cmd! : Cmd => Result {} [ ExecCmdFailed { command : Str, exit_code : I32 }, FailedToGetExitCode { command : Str, err : IOErr } ] Description: Execute a Cmd while inheriting stdin, stdout and stderr from parent. You should prefer using [exec!] instead, only use this if you want to use [env], [envs] or [clear_envs]. If you want to capture the output, use [exec_output!] instead. ``` # Execute `cargo build` with env var. Cmd.new("cargo") |> Cmd.arg("build") |> Cmd.env("RUST_BACKTRACE", "1") |> Cmd.exec_cmd!()? ``` exec_output! : Cmd => Result { stdout_utf8 : Str, stderr_utf8_lossy : Str } [ StdoutContainsInvalidUtf8 { cmd_str : Str, err : [ BadUtf8 { index : U64, problem : Str.Utf8Problem } ] }, NonZeroExitCode { command : Str, exit_code : I32, stdout_utf8_lossy : Str, stderr_utf8_lossy : Str }, FailedToGetExitCode { command : Str, err : IOErr } ] Description: Execute command and capture stdout and stderr. > Stdin is not inherited from the parent and any attempt by the child process > to read from the stdin stream will result in the stream immediately closing. Use [exec_output_bytes!] instead if you want to capture the output in the original form as bytes. [exec_output_bytes!] may also be used for maximum performance, because you may be able to avoid unnecessary UTF-8 conversions. ``` cmd_output = Cmd.new("echo") |> Cmd.args(["Hi"]) |> Cmd.exec_output!()? Stdout.line!("Echo output: ${cmd_output.stdout_utf8}")? ``` exec_output_bytes! : Cmd => Result { stderr_bytes : List U8, stdout_bytes : List U8 } [ FailedToGetExitCodeB InternalIOErr.IOErr, NonZeroExitCodeB { exit_code : I32, stderr_bytes : List U8, stdout_bytes : List U8 } ] Description: Execute command and capture stdout and stderr in the original form as bytes. > Stdin is not inherited from the parent and any attempt by the child process > to read from the stdin stream will result in the stream immediately closing. Use [exec_output!] instead if you want to get the output as UTF-8 strings. ``` cmd_output_bytes = Cmd.new("echo") |> Cmd.args(["Hi"]) |> Cmd.exec_output_bytes!()? Stdout.line!("${Inspect.to_str(cmd_output_bytes)}")? # {stderr_bytes: [], stdout_bytes: [72, 105, 10]} ``` exec_exit_code! : Cmd => Result I32 [ FailedToGetExitCode { command : Str, err : IOErr } ] Description: Execute command and inherit stdin, stdout and stderr from parent. Returns the exit code. You should prefer using [exec!] or [exec_cmd!] instead, only use this if you want to take a specific action based on a **specific non-zero exit code**. For example, `roc check` returns exit code 1 if there are errors, and exit code 2 if there are only warnings. So, you could use `exec_exit_code!` to ignore warnings on `roc check`. ``` exit_code = Cmd.new("cat") |> Cmd.args(["non_existent.txt"]) |> Cmd.exec_exit_code!()? Stdout.line!("${Num.to_str(exit_code)}")? # "1" ``` Description: Represents a command to be executed in a child process. env : Cmd, Str, Str -> Cmd Description: Add a single environment variable to the command. ``` # Run "env" and add the environment variable "FOO" with value "BAR" Cmd.new("env") |> Cmd.env("FOO", "BAR") ``` envs : Cmd, List ( Str, Str ) -> Cmd Description: Add multiple environment variables to the command. ``` # Run "env" and add the variables "FOO" and "BAZ" Cmd.new("env") |> Cmd.envs([("FOO", "BAR"), ("BAZ", "DUCK")]) ``` clear_envs : Cmd -> Cmd Description: Clear all environment variables, and prevent inheriting from parent, only the environment variables provided by [env] or [envs] are available to the child. ``` # Represents "env" with only "FOO" environment variable set Cmd.new("env") |> Cmd.clear_envs |> Cmd.env("FOO", "BAR") ``` new : Str -> Cmd Description: Create a new command to execute the given program in a child process. arg : Cmd, Str -> Cmd Description: Add a single argument to the command. ❗ Shell features like variable subsitition (e.g. `$FOO`), glob patterns (e.g. `*.txt`), ... are not available. ``` # Represent the command "ls -l" Cmd.new("ls") |> Cmd.arg("-l") ``` args : Cmd, List Str -> Cmd Description: Add multiple arguments to the command. ❗ Shell features like variable subsitition (e.g. `$FOO`), glob patterns (e.g. `*.txt`), ... are not available. ``` # Represent the command "ls -l -a" Cmd.new("ls") |> Cmd.args(["-l", "-a"]) ``` ### Tty enable_raw_mode! : {} => {} Description: Enable terminal [raw mode](https://en.wikipedia.org/wiki/Terminal_mode) to disable some default terminal bevahiour. This leads to the following changes: - Input will not be echoed to the terminal screen. - Input will be sent straight to the program instead of being buffered (= collected) until the Enter key is pressed. - Special keys like Backspace and CTRL+C will not be processed by the terminal driver but will be passed to the program. disable_raw_mode! : {} => {} Description: Revert terminal to default behaviour ### Locale get! : {} => Result Str [NotAvailable] Description: Returns the most preferred locale for the system or application, or `NotAvailable` if the locale could not be obtained. The returned [Str] is a BCP 47 language tag, like `en-US` or `fr-CA`. all! : {} => List Str Description: Returns the preferred locales for the system or application. The returned [Str] are BCP 47 language tags, like `en-US` or `fr-CA`. ### Sqlite Description: Represents a value that can be stored in a Sqlite database. ``` [ Null, Real F64, Integer I64, String Str, Bytes (List U8), ] ``` Description: Represents various error codes that can be returned by Sqlite. ``` [ Error, # SQL error or missing database Internal, # Internal logic error in Sqlite Perm, # Access permission denied Abort, # Callback routine requested an abort Busy, # The database file is locked Locked, # A table in the database is locked NoMem, # A malloc() failed ReadOnly, # Attempt to write a readonly database Interrupt, # Operation terminated by sqlite3_interrupt( IOErr, # Some kind of disk I/O error occurred Corrupt, # The database disk image is malformed NotFound, # Unknown opcode in sqlite3_file_control() Full, # Insertion failed because database is full CanNotOpen, # Unable to open the database file Protocol, # Database lock protocol error Empty, # Database is empty Schema, # The database schema changed TooBig, # String or BLOB exceeds size limit Constraint, # Abort due to constraint violation Mismatch, # Data type mismatch Misuse, # Library used incorrectly NoLfs, # Uses OS features not supported on host AuthDenied, # Authorization denied Format, # Auxiliary database format error OutOfRange, # 2nd parameter to sqlite3_bind out of range NotADatabase, # File opened that is not a database file Notice, # Notifications from sqlite3_log() Warning, # Warnings from sqlite3_log() Row, # sqlite3_step() has another row ready Done, # sqlite3_step() has finished executing Unknown I64, # error code not known ] ``` Description: Bind a name and a value to pass to the Sqlite database. ``` { name : Str, value : SqliteValue, } ``` Description: Represents a prepared statement that can be executed many times. prepare! : { path : Str, query : Str } => Result Stmt [SqliteErr ErrCode Str] Description: Prepare a [Stmt] for execution at a later time. This is useful when you have a query that will be called many times, as it is more efficient than preparing the query each time it is called. This is usually done in `init!` with the prepared `Stmt` stored in the model. ``` prepared_query = Sqlite.prepare!({ path : "path/to/database.db", query : "SELECT * FROM todos;", })? Sqlite.query_many_prepared!({ stmt: prepared_query, bindings: [], rows: { Sqlite.decode_record <- id: Sqlite.i64("id"), task: Sqlite.str("task"), }, })? ``` execute! : { path : Str, query : Str, bindings : List Binding } => Result {} [ SqliteErr ErrCode Str, RowsReturnedUseQueryInstead ] Description: Execute a SQL statement that **doesn't return any rows** (like INSERT, UPDATE, DELETE). Use a function starting with `query_` if you expect rows to be returned. Use execute_prepared! if you expect to run the same query multiple times. Example: ``` Sqlite.execute!({ path: "path/to/database.db", query: "INSERT INTO users (first, last) VALUES (:first, :last);", bindings: [ { name: ":first", value: String("John") }, { name: ":last", value: String("Smith") }, ], })? ``` execute_prepared! : { stmt : Stmt, bindings : List Binding } => Result {} [ SqliteErr ErrCode Str, RowsReturnedUseQueryInstead ] Description: Execute a prepared SQL statement that **doesn't return any rows** (like INSERT, UPDATE, DELETE). Use a function starting with `query_` if you expect rows to be returned. This is more efficient than [execute!] when running the same query multiple times as it reuses the prepared statement. query! : { path : Str, query : Str, bindings : List Binding, row : SqlDecode a (RowCountErr err) } => Result a (SqlDecodeErr (RowCountErr err)) Description: Execute a SQL query and decode exactly one row into a value. Example: ``` # count the number of rows in the `users` table count = Sqlite.query!({ path: db_path, query: "SELECT COUNT(*) as \"count\" FROM users;", bindings: [], row: Sqlite.u64("count"), })? ``` query_prepared! : { stmt : Stmt, bindings : List Binding, row : SqlDecode a (RowCountErr err) } => Result a (SqlDecodeErr (RowCountErr err)) Description: Execute a prepared SQL query and decode exactly one row into a value. This is more efficient than [query!] when running the same query multiple times as it reuses the prepared statement. query_many! : { path : Str, query : Str, bindings : List Binding, rows : SqlDecode a err } => Result (List a) (SqlDecodeErr err) Description: Execute a SQL query and decode multiple rows into a list of values. Example: ``` rows = Sqlite.query_many!({ path: "path/to/database.db", query: "SELECT * FROM todos;", bindings: [], rows: { Sqlite.decode_record <- id: Sqlite.i64("id"), task: Sqlite.str("task"), }, })? ``` query_many_prepared! : { stmt : Stmt, bindings : List Binding, rows : SqlDecode a err } => Result (List a) (SqlDecodeErr err) Description: Execute a prepared SQL query and decode multiple rows into a list of values. This is more efficient than [query_many!] when running the same query multiple times as it reuses the prepared statement. decode_record : SqlDecode a err, SqlDecode b err, (a, b -> c) -> SqlDecode c err Description: Decode a Sqlite row into a record by combining decoders. Example: ``` { Sqlite.decode_record <- id: Sqlite.i64("id"), task: Sqlite.str("task"), } ``` map_value : SqlDecode a err, (a -> b) -> SqlDecode b err Description: Transform the output of a decoder by applying a function to the decoded value. Example: ``` Sqlite.i64("id") |> Sqlite.map_value(Num.to_str) ``` map_value_result : SqlDecode a err, (a -> Result c (SqlDecodeErr err)) -> SqlDecode c err Description: Transform the output of a decoder by applying a function (that returns a Result) to the decoded value. The Result is converted to SqlDecode. Example: ``` decode_status : Str -> Result OnlineStatus UnknownStatusErr decode_status = |status_str| when status_str is "online" -> Ok(Online) "offline" -> Ok(Offline) _ -> Err(UnknownStatus("${status_str}")) Sqlite.str("status") |> Sqlite.map_value_result(decode_status) ``` tagged_value : Str -> SqlDecode Value [] Description: Decode a [Value] keeping it tagged. This is useful when data could be many possible types. For example here we build a decoder that decodes the rows into a list of records with `id` and `mixed_data` fields: ``` rows = Sqlite.query_many!({ path: "path/to/database.db", query: "SELECT id, mix_data FROM users;", bindings: [], rows: { Sqlite.decode_record <- id: Sqlite.i64("id"), mix_data: Sqlite.tagged_value("mixed_data"), }, })? ``` str : Str -> SqlDecode Str UnexpectedTypeErr Description: Decode a [Value] to a [Str]. For example here we build a decoder that decodes the rows into a list of records with `id` and `name` fields: ``` rows = Sqlite.query_many!({ path: "path/to/database.db", query: "SELECT id, name FROM users;", bindings: [], rows: { Sqlite.decode_record <- id: Sqlite.i64("id"), task: Sqlite.str("name"), }, })? ``` bytes : Str -> SqlDecode (List U8) UnexpectedTypeErr Description: Decode a [Value] to a [List U8]. i64 : Str -> SqlDecode I64 [FailedToDecodeInteger []]UnexpectedTypeErr Description: Decode a [Value] to a [I64]. For example here we build a decoder that decodes the rows into a list of records with `id` and `name` fields: ``` rows = Sqlite.query_many!({ path: "path/to/database.db", query: "SELECT id, name FROM users;", bindings: [], rows: { Sqlite.decode_record <- id: Sqlite.i64("id"), task: Sqlite.str("name"), }, })? ``` i32 : Str -> SqlDecode I32 [FailedToDecodeInteger [OutOfBounds]]UnexpectedTypeErr Description: Decode a [Value] to a [I32]. i16 : Str -> SqlDecode I16 [FailedToDecodeInteger [OutOfBounds]]UnexpectedTypeErr Description: Decode a [Value] to a [I16]. i8 : Str -> SqlDecode I8 [FailedToDecodeInteger [OutOfBounds]]UnexpectedTypeErr Description: Decode a [Value] to a [I8]. u64 : Str -> SqlDecode U64 [FailedToDecodeInteger [OutOfBounds]]UnexpectedTypeErr Description: Decode a [Value] to a [U64]. u32 : Str -> SqlDecode U32 [FailedToDecodeInteger [OutOfBounds]]UnexpectedTypeErr Description: Decode a [Value] to a [U32]. u16 : Str -> SqlDecode U16 [FailedToDecodeInteger [OutOfBounds]]UnexpectedTypeErr Description: Decode a [Value] to a [U16]. u8 : Str -> SqlDecode U8 [FailedToDecodeInteger [OutOfBounds]]UnexpectedTypeErr Description: Decode a [Value] to a [U8]. f64 : Str -> SqlDecode F64 [FailedToDecodeReal []]UnexpectedTypeErr Description: Decode a [Value] to a [F64]. f32 : Str -> SqlDecode F32 [FailedToDecodeReal []]UnexpectedTypeErr Description: Decode a [Value] to a [F32]. Nullable : [ NotNull a, Null ] Description: Represents a nullable value that can be stored in a Sqlite database. nullable_str : Str -> SqlDecode (Nullable Str) UnexpectedTypeErr Description: Decode a [Value] to a [Nullable Str]. nullable_bytes : Str -> SqlDecode (Nullable (List U8)) UnexpectedTypeErr Description: Decode a [Value] to a [Nullable (List U8)]. nullable_i64 : Str -> SqlDecode (Nullable I64) [FailedToDecodeInteger []]UnexpectedTypeErr Description: Decode a [Value] to a [Nullable I64]. nullable_i32 : Str -> SqlDecode (Nullable I32) [FailedToDecodeInteger [OutOfBounds]]UnexpectedTypeErr Description: Decode a [Value] to a [Nullable I32]. nullable_i16 : Str -> SqlDecode (Nullable I16) [FailedToDecodeInteger [OutOfBounds]]UnexpectedTypeErr Description: Decode a [Value] to a [Nullable I16]. nullable_i8 : Str -> SqlDecode (Nullable I8) [FailedToDecodeInteger [OutOfBounds]]UnexpectedTypeErr Description: Decode a [Value] to a [Nullable I8]. nullable_u64 : Str -> SqlDecode (Nullable U64) [FailedToDecodeInteger [OutOfBounds]]UnexpectedTypeErr Description: Decode a [Value] to a [Nullable U64]. nullable_u32 : Str -> SqlDecode (Nullable U32) [FailedToDecodeInteger [OutOfBounds]]UnexpectedTypeErr Description: Decode a [Value] to a [Nullable U32]. nullable_u16 : Str -> SqlDecode (Nullable U16) [FailedToDecodeInteger [OutOfBounds]]UnexpectedTypeErr Description: Decode a [Value] to a [Nullable U16]. nullable_u8 : Str -> SqlDecode (Nullable U8) [FailedToDecodeInteger [OutOfBounds]]UnexpectedTypeErr Description: Decode a [Value] to a [Nullable U8]. nullable_f64 : Str -> SqlDecode (Nullable F64) [FailedToDecodeReal []]UnexpectedTypeErr Description: Decode a [Value] to a [Nullable F64]. nullable_f32 : Str -> SqlDecode (Nullable F32) [FailedToDecodeReal []]UnexpectedTypeErr Description: Decode a [Value] to a [Nullable F32]. errcode_to_str : ErrCode -> Str Description: Convert a [ErrCode] to a pretty string for display purposes.