From a1c0c2cc35b676c3667d6bf8b0139b6691a8d8d3 Mon Sep 17 00:00:00 2001 From: Luke Boswell Date: Wed, 8 Jan 2025 13:59:20 +1100 Subject: [PATCH 1/4] add documentation site with previous versions --- .github/workflows/deploy-docs.yml | 39 + docs/0.17.0/Arg/index.html | 327 ++++ docs/0.17.0/Cmd/index.html | 383 ++++ docs/0.17.0/Dir/index.html | 342 ++++ docs/0.17.0/Env/index.html | 344 ++++ docs/0.17.0/File/index.html | 475 +++++ docs/0.17.0/FileMetadata/index.html | 292 +++ docs/0.17.0/Http/index.html | 363 ++++ docs/0.17.0/Path/index.html | 614 +++++++ docs/0.17.0/Sleep/index.html | 252 +++ docs/0.17.0/Stderr/index.html | 287 +++ docs/0.17.0/Stdin/index.html | 299 ++++ docs/0.17.0/Stdout/index.html | 287 +++ docs/0.17.0/Tcp/index.html | 392 ++++ docs/0.17.0/Tty/index.html | 269 +++ docs/0.17.0/Url/index.html | 438 +++++ docs/0.17.0/Utc/index.html | 300 ++++ docs/0.17.0/index.html | 244 +++ docs/0.17.0/llms.txt | 2555 +++++++++++++++++++++++++++ docs/0.17.0/search.js | 284 +++ docs/0.17.0/styles.css | 1025 +++++++++++ docs/0.18.0/Arg/index.html | 295 ++++ docs/0.18.0/Cmd/index.html | 360 ++++ docs/0.18.0/Dir/index.html | 344 ++++ docs/0.18.0/Env/index.html | 350 ++++ docs/0.18.0/File/index.html | 487 +++++ docs/0.18.0/FileMetadata/index.html | 296 ++++ docs/0.18.0/Http/index.html | 336 ++++ docs/0.18.0/Locale/index.html | 264 +++ docs/0.18.0/Path/index.html | 600 +++++++ docs/0.18.0/Sleep/index.html | 256 +++ docs/0.18.0/Stderr/index.html | 285 +++ docs/0.18.0/Stdin/index.html | 307 ++++ docs/0.18.0/Stdout/index.html | 285 +++ docs/0.18.0/Tcp/index.html | 396 +++++ docs/0.18.0/Tty/index.html | 273 +++ docs/0.18.0/Url/index.html | 448 +++++ docs/0.18.0/Utc/index.html | 312 ++++ docs/0.18.0/index.html | 248 +++ docs/0.18.0/llms.txt | 2492 ++++++++++++++++++++++++++ docs/0.18.0/search.js | 284 +++ docs/0.18.0/styles.css | 1025 +++++++++++ docs/index.html | 19 + 43 files changed, 19773 insertions(+) create mode 100644 .github/workflows/deploy-docs.yml create mode 100644 docs/0.17.0/Arg/index.html create mode 100644 docs/0.17.0/Cmd/index.html create mode 100644 docs/0.17.0/Dir/index.html create mode 100644 docs/0.17.0/Env/index.html create mode 100644 docs/0.17.0/File/index.html create mode 100644 docs/0.17.0/FileMetadata/index.html create mode 100644 docs/0.17.0/Http/index.html create mode 100644 docs/0.17.0/Path/index.html create mode 100644 docs/0.17.0/Sleep/index.html create mode 100644 docs/0.17.0/Stderr/index.html create mode 100644 docs/0.17.0/Stdin/index.html create mode 100644 docs/0.17.0/Stdout/index.html create mode 100644 docs/0.17.0/Tcp/index.html create mode 100644 docs/0.17.0/Tty/index.html create mode 100644 docs/0.17.0/Url/index.html create mode 100644 docs/0.17.0/Utc/index.html create mode 100644 docs/0.17.0/index.html create mode 100644 docs/0.17.0/llms.txt create mode 100644 docs/0.17.0/search.js create mode 100644 docs/0.17.0/styles.css create mode 100644 docs/0.18.0/Arg/index.html create mode 100644 docs/0.18.0/Cmd/index.html create mode 100644 docs/0.18.0/Dir/index.html create mode 100644 docs/0.18.0/Env/index.html create mode 100644 docs/0.18.0/File/index.html create mode 100644 docs/0.18.0/FileMetadata/index.html create mode 100644 docs/0.18.0/Http/index.html create mode 100644 docs/0.18.0/Locale/index.html create mode 100644 docs/0.18.0/Path/index.html create mode 100644 docs/0.18.0/Sleep/index.html create mode 100644 docs/0.18.0/Stderr/index.html create mode 100644 docs/0.18.0/Stdin/index.html create mode 100644 docs/0.18.0/Stdout/index.html create mode 100644 docs/0.18.0/Tcp/index.html create mode 100644 docs/0.18.0/Tty/index.html create mode 100644 docs/0.18.0/Url/index.html create mode 100644 docs/0.18.0/Utc/index.html create mode 100644 docs/0.18.0/index.html create mode 100644 docs/0.18.0/llms.txt create mode 100644 docs/0.18.0/search.js create mode 100644 docs/0.18.0/styles.css create mode 100644 docs/index.html diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml new file mode 100644 index 00000000..2af7cec3 --- /dev/null +++ b/.github/workflows/deploy-docs.yml @@ -0,0 +1,39 @@ +name: Deploy docs to Pages + +on: + release: + types: + - published + + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Pages + uses: actions/configure-pages@v5 + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + # Upload docs/ folder + path: "./docs" + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/docs/0.17.0/Arg/index.html b/docs/0.17.0/Arg/index.html new file mode 100644 index 00000000..4803b1de --- /dev/null +++ b/docs/0.17.0/Arg/index.html @@ -0,0 +1,327 @@ + + + + + + Arg - Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + +
+

Arg

+ +list : {} -> Task (List Str) *

Gives a list of the program's command-line arguments.

+

+ +parse : + CliParser state + -> Task state + [ + Exit I32 Str, + StdoutErr Stdout.Err + ]

Parse arguments using a CLI parser or show a useful message on failure.

+

We have the following priorities in returning messages to the user:

+
    +
  1. If the -h/--help flag is passed, the help page for the command/subcommand +called will be displayed no matter if your arguments were correctly parsed.
  2. +
  3. If the -V/--version flag is passed, the version for the app will +be displayed no matter if your arguments were correctly parsed.
  4. +
  5. If the provided arguments were parsed and neither of the above two +built-in flags were passed, we return to you your data.
  6. +
  7. If the provided arguments were not correct, we return a short message +with which argument was not provided correctly, followed by the +usage section of the relevant command/subcommand's help text.
  8. +
+
exampleCli =
+    { Cli.combine <-
+        verbosity: Opt.count { short: "v", help: "How verbose our logs should be." },
+        alpha: Opt.maybeU64 { short: "a", help: "Set the alpha level." },
+    }
+    |> Cli.finish {
+        name: "example",
+        version: "v0.1.0",
+        description: "An example CLI.",
+    }
+    |> Cli.assertValid
+
+expect
+    exampleCli
+    |> Cli.parseOrDisplayMessage ["example", "-h"]
+    == Err
+        """
+        example v0.1.0
+
+        An example CLI.
+
+        Usage:
+          example [OPTIONS]
+
+        Options:
+          -v             How verbose our logs should be.
+          -a             Set the alpha level.
+          -h, --help     Show this help page.
+          -V, --version  Show the version.
+        """
+
+expect
+    exampleCli
+    |> Cli.parseOrDisplayMessage ["example", "-V"]
+    == Err "v0.1.0"
+
+expect
+    exampleCli
+    |> Cli.parseOrDisplayMessage ["example", "-v"]
+    == Ok { verbosity: 1 }
+
+expect
+    exampleCli
+    |> Cli.parseOrDisplayMessage ["example", "-x"]
+    == Err
+        """
+        Error: The argument -x was not recognized.
+
+        Usage:
+          example [OPTIONS]
+        """
+
+
+

Made by people who like to make nice things.

+
+
+ + + diff --git a/docs/0.17.0/Cmd/index.html b/docs/0.17.0/Cmd/index.html new file mode 100644 index 00000000..4aa9f6aa --- /dev/null +++ b/docs/0.17.0/Cmd/index.html @@ -0,0 +1,383 @@ + + + + + + Cmd - Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + +
+

Cmd

+ +Cmd

Represents a command to be executed in a child process.

+

+ +Err

Errors from executing a command.

+

+ +outputErrToStr : + ( + Output, + Err + ) + -> Str

+ +Output

Represents the output of a command.

+

+ +new : Str -> Cmd

Create a new command to execute the given program in a child process.

+

+ +arg : Cmd, Str -> Cmd

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

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"]
+

+ +env : + Cmd, + Str, + Str + -> Cmd

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

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")]
+

+ +clearEnvs : Cmd -> Cmd

Clear all environment variables, and prevent inheriting from parent, only +the environment variables provided to command are available to the child.

+
# Represents "env" with only "FOO" environment variable set
+Cmd.new "env"
+|> Cmd.clearEnvs
+|> Cmd.env "FOO" "BAR"
+

+ +output : + Cmd + -> Task Output + [ + CmdOutputError + ( + Output, + Err + ) + ]

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.

+
+

+ +status : Cmd -> Task {} [CmdError Err]

Execute command and inherit stdin, stdout and stderr from parent

+

+ +exec : Str, List Str -> Task {} [CmdError Err]

Execute command and inherit stdin, stdout and stderr from parent

+
# Call echo to print "hello world"
+Cmd.exec! "echo" ["hello world"]
+
+
+

Made by people who like to make nice things.

+
+
+ + + diff --git a/docs/0.17.0/Dir/index.html b/docs/0.17.0/Dir/index.html new file mode 100644 index 00000000..75dc7368 --- /dev/null +++ b/docs/0.17.0/Dir/index.html @@ -0,0 +1,342 @@ + + + + + + Dir - Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + +
+

Dir

+ +Err : Path.DirErr

NotFound - This error is raised when the specified path does not exist, typically during attempts to access or manipulate it, but also potentially when trying to create a directory and a parent directory does not exist.

+

PermissionDenied - Occurs when the user lacks the necessary permissions to perform an action on a directory, such as reading, writing, or executing.

+

Other - A catch-all for any other types of errors not explicitly listed above.

+
+

This is the same as [Path.DirErr].

+
+

+ +DirEntry : Path.DirEntry

Record which represents a directory

+
+

This is the same as [Path.DirEntry].

+
+

+ +list : Str -> Task (List Path) [DirErr Err]

Lists the files and directories inside the directory.

+
+

Path.listDir does the same thing, except it takes a Path instead of a Str.

+
+

+ +deleteEmpty : Str -> Task {} [DirErr Err]

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.deleteEmpty does the same thing, except it takes a Path instead of a Str.

+
+

+ +deleteAll : Str -> Task {} [DirErr Err]

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 directory is not empty
  • +
  • the user lacks permission to remove the directory.
  • +
+
+

Path.deleteAll does the same thing, except it takes a Path instead of a Str.

+
+

+ +create : Str -> Task {} [DirErr Err]

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.createDir does the same thing, except it takes a Path instead of a Str.

+
+

+ +createAll : Str -> Task {} [DirErr Err]

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.createAll does the same thing, except it takes a Path instead of a Str.

+
+
+
+

Made by people who like to make nice things.

+
+
+ + + diff --git a/docs/0.17.0/Env/index.html b/docs/0.17.0/Env/index.html new file mode 100644 index 00000000..b82047aa --- /dev/null +++ b/docs/0.17.0/Env/index.html @@ -0,0 +1,344 @@ + + + + + + Env - Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + +
+

Env

+ +cwd : Task Path [CwdUnavailable]

Reads the current working directory +from the environment. File operations on relative Paths are relative to this directory.

+

+ +setCwd : Path -> Task {} [InvalidCwd]

Sets the current working directory +in the environment. After changing it, file operations on relative Paths will be relative +to this directory.

+

+ +exePath : Task Path [ExePathUnavailable]

Gets the path to the currently-running executable.

+

+ +var : Str -> Task Str [VarNotFound]

Reads the given environment variable.

+

If the value is invalid Unicode, the invalid parts will be replaced with the +Unicode replacement character ('�').

+

+ +decode : + Str + -> Task val + [ + VarNotFound, + DecodeErr DecodeError + ] + where val implements Decoding

Reads the given environment variable and attempts to decode it.

+

The type being decoded into will be determined by type inference. For example, +if this ends up being used like a Task 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
+getU16Var : Str -> Task U16 [VarNotFound, DecodeErr DecodeError] [Read [Env]]
+getU16Var = \var -> Env.decode var
+

If NUM_THINGS=123 then getU16Var succeeds with the value of 123u16. +However if NUM_THINGS=123456789, then getU16Var will +fail with DecodeErr +because 123456789 is too large to fit in a U16.

+

+ +dict : {} -> Task (Dict Str Str) *

Reads all the process's environment variables into a Dict.

+

If any key or value contains invalid Unicode, the Unicode replacement character +will be used in place of any parts of keys or values that are invalid Unicode.

+

+ +platform : Task + { + arch : ARCH, + os : OS + } *

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.

+

+ +tempDir : {} -> Task Path *

This uses rust's std::env::temp_dir()

+

!! 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.

+
+
+

Made by people who like to make nice things.

+
+
+ + + diff --git a/docs/0.17.0/File/index.html b/docs/0.17.0/File/index.html new file mode 100644 index 00000000..7ac8e1f2 --- /dev/null +++ b/docs/0.17.0/File/index.html @@ -0,0 +1,475 @@ + + + + + + File - Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + +
+

File

+ +ReadErr : Path.ReadErr

Tag union of possible errors when reading a file or directory.

+
+

This is the same as [Path.ReadErr].

+
+

+ +WriteErr : Path.WriteErr

Tag union of possible errors when writing a file or directory.

+
+

This is the same as [Path.WriteErr].

+
+

+ +write : + val, + Str, + fmt + -> Task {} [FileWriteErr Path WriteErr] + where val implements Encoding, fmt implements EncoderFormatting

Write data to a file.

+

First encode a val using a given fmt which implements the ability Encode.EncoderFormatting.

+

For example, suppose you have a Json.toCompactUtf8 which implements +Encode.EncoderFormatting. +You can use this to write JSON +data to a file like this:

+
# Writes `{"some":"json stuff"}` to the file `output.json`:
+File.write
+    { some: "json stuff" }
+    (Path.fromStr "output.json")
+    Json.toCompactUtf8
+

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.writeBytes instead.

+

Path.write does the same thing, except it takes a Path instead of a Str.

+
+

+ +writeBytes : List U8, Str -> Task {} [FileWriteErr Path WriteErr]

Writes bytes to a file.

+
# Writes the bytes 1, 2, 3 to the file `myfile.dat`.
+File.writeBytes [1, 2, 3] (Path.fromStr "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.writeBytes does the same thing, except it takes a Path instead of a Str.

+
+

+ +writeUtf8 : Str, Str -> Task {} [FileWriteErr Path WriteErr]

Writes a Str to a file, encoded as UTF-8.

+
# Writes "Hello!" encoded as UTF-8 to the file `myfile.txt`.
+File.writeUtf8 "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.writeBytes instead.

+

Path.writeUtf8 does the same thing, except it takes a Path instead of a Str.

+
+

+ +delete : Str -> Task {} [FileWriteErr Path WriteErr]

Deletes a file from the filesystem.

+

Performs a DeleteFile +on Windows and unlink 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
+File.delete (Path.fromStr "myfile.dat") [1, 2, 3]
+
+

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 to it has been deleted.

+

Path.delete does the same thing, except it takes a Path instead of a Str.

+
+

+ +readBytes : Str -> Task (List U8) [FileReadErr Path ReadErr]

Reads all the bytes in a file.

+
# Read all the bytes in `myfile.txt`.
+File.readBytes "myfile.txt"
+

This opens the file first and closes it after reading its contents.

+
+

To read and decode data from a file, you can use File.read instead.

+

Path.readBytes does the same thing, except it takes a Path instead of a Str.

+
+

+ +readUtf8 : + Str + -> Task Str + [ + FileReadErr Path ReadErr, + FileReadUtf8Err Path + ]

Reads a Str from a file containing UTF-8-encoded text.

+
# Reads UTF-8 encoded text into a Str from the file "myfile.txt"
+File.readUtf8 "myfile.txt"
+

This opens the file first and closes it after writing to it. +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.readBytes instead.

+
+
+

Path.readUtf8 does the same thing, except it takes a Path instead of a Str.

+
+

+ +isDir : Str -> Task Bool [PathErr MetadataErr]

Returns true if the path exists on disk and is pointing at a directory. +Returns Task.ok false if the path exists and it is not a directory. If the path does not exist, +this function will return Task.err PathErr PathDoesNotExist.

+

This uses rust's std::path::is_dir.

+
+

Path.isDir does the same thing, except it takes a Path instead of a Str.

+
+

+ +isFile : Str -> Task Bool [PathErr MetadataErr]

Returns true if the path exists on disk and is pointing at a regular file. +Returns Task.ok false if the path exists and it is not a file. If the path does not exist, +this function will return Task.err PathErr PathDoesNotExist.

+

This uses rust's std::path::is_file.

+
+

Path.isFile does the same thing, except it takes a Path instead of a Str.

+
+

+ +isSymLink : Str -> Task Bool [PathErr MetadataErr]

Returns true if the path exists on disk and is pointing at a symbolic link. +Returns Task.ok false if the path exists and it is not a symbolic link. If the path does not exist, +this function will return Task.err PathErr PathDoesNotExist.

+

This uses rust's std::path::is_symlink.

+
+

Path.isSymLink does the same thing, except it takes a Path instead of a Str.

+
+

+ +type : + Str + -> Task + [ + IsFile, + IsDir, + IsSymLink + ] [PathErr MetadataErr]

Return the type of the path if the path exists on disk. +This uses rust's std::path::is_symlink.

+
+

Path.type does the same thing, except it takes a Path instead of a Str.

+
+

+ +Reader

+ +openReader : Str -> Task Reader [GetFileReadErr Path ReadErr]

Try to open a File.Reader for buffered (= part by part) reading given a path string. +See examples/file-read-buffered.roc for example usage.

+

This uses rust's std::io::BufReader.

+

Use readUtf8 if you want to get the entire file contents at once.

+

+ +openReaderWithCapacity : Str, U64 -> Task Reader [GetFileReadErr Path ReadErr]

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 for example usage.

+

This uses rust's std::io::BufReader.

+

Use readUtf8 if you want to get the entire file contents at once.

+

+ +readLine : Reader -> Task (List U8) [FileReadErr Path Str]

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 for example usage.

+

This uses rust's BufRead::read_line.

+

Use readUtf8 if you want to get the entire file contents at once.

+
+
+

Made by people who like to make nice things.

+
+
+ + + diff --git a/docs/0.17.0/FileMetadata/index.html b/docs/0.17.0/FileMetadata/index.html new file mode 100644 index 00000000..bf3003a6 --- /dev/null +++ b/docs/0.17.0/FileMetadata/index.html @@ -0,0 +1,292 @@ + + + + + + FileMetadata - Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + +
+

FileMetadata

+ +FileMetadata

An opaque type that represents metadata about a file.

+

+ +bytes : FileMetadata -> U64

Returns the number of bytes in the associated file.

+

+ +isReadonly : FileMetadata -> Bool

Returns Bool.true if the associated file is read-only.

+

+ +type : + FileMetadata + -> + [ + File, + Dir, + Symlink + ]

Returns the type of the associated file.

+

+ +mode : + FileMetadata + -> + [ + Unix U32, + NonUnix + ]

Returns the mode of the associated file.

+
+
+

Made by people who like to make nice things.

+
+
+ + + diff --git a/docs/0.17.0/Http/index.html b/docs/0.17.0/Http/index.html new file mode 100644 index 00000000..13ef1015 --- /dev/null +++ b/docs/0.17.0/Http/index.html @@ -0,0 +1,363 @@ + + + + + + Http - Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + +
+

Http

+ +Request

Represents an HTTP request.

+

+ +Method

Represents an HTTP method.

+

+ +Header

Represents an HTTP header e.g. Content-Type: application/json

+

+ +TimeoutConfig

Represents a timeout configuration for an HTTP request.

+

+ +Response

Represents an HTTP response.

+

+ +Err

Represents an HTTP error.

+

+ +errorBodyToBytes

Convert the ErrorBody of a BadStatus error to List U8.

+

+ +defaultRequest : Request

A default Request value.

+
# GET "roc-lang.org"
+{ Http.defaultRequest &
+    url: "https://www.roc-lang.org",
+}
+

+ +header : Str, Str -> Header

An HTTP header for configuring requests.

+

See common headers here.

+

+ +handleStringResponse : Response -> Result Str Err

Map a Response body to a Str or return an Err.

+

+ +errorToString : Err -> Str

Convert an Err to a Str.

+

+ +send : Request -> Task Response [HttpErr Err]

Task to send an HTTP request, succeeds with a value of Str or fails with an +Err.

+
# Prints out the HTML of the Roc-lang website.
+response =
+    { Http.defaultRequest & url: "https://www.roc-lang.org" }
+    |> Http.send!
+
+response.body
+|> Str.fromUtf8
+|> Result.withDefault "Invalid UTF-8"
+|> Stdout.line
+

+ +get : + Str, + fmt + -> Task body + [ + HttpErr Http.Err, + HttpDecodingFailed + ] + where body implements Decoding, fmt implements DecoderFormatting

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.toBytes {foo: "Hello Json!"} Json.utf8`
+{ foo } = Http.get! "http://localhost:8000" Json.utf8
+
+
+

Made by people who like to make nice things.

+
+
+ + + diff --git a/docs/0.17.0/Path/index.html b/docs/0.17.0/Path/index.html new file mode 100644 index 00000000..2b3a33ca --- /dev/null +++ b/docs/0.17.0/Path/index.html @@ -0,0 +1,614 @@ + + + + + + Path - Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + +
+

Path

+ +MetadataErr

An error when reading a path's file metadata from disk.

+

+ +Path

Represents a path to a file or directory on the filesystem.

+

+ +DirEntry

Record which represents a directory

+
+

This is the same as Dir.DirEntry.

+
+

+ +ReadErr

Tag union of possible errors when reading a file or directory.

+
+

This is the same as File.ReadErr.

+
+

+ +WriteErr

Tag union of possible errors when writing a file or directory.

+
+

This is the same as File.WriteErr.

+
+

+ +DirErr

NotFound - This error is raised when the specified directory does not exist, typically during attempts to access or manipulate it.

+

PermissionDenied - Occurs when the user lacks the necessary permissions to perform an action on a directory, such as reading, writing, or executing.

+

AlreadyExists - This error is thrown when trying to create a directory that already exists.

+

NotADirectory - Raised when an operation that requires a directory (e.g., listing contents) is attempted on a file instead.

+

Other - A catch-all for any other types of errors not explicitly listed above.

+
+

This is the same as Dir.Err.

+
+

+ +write : + val, + Path, + fmt + -> Task {} [FileWriteErr Path WriteErr] + where val implements Encoding, fmt implements EncoderFormatting

Write data to a file.

+

First encode a val using a given fmt which implements the ability Encode.EncoderFormatting.

+

For example, suppose you have a Json.toCompactUtf8 which implements +Encode.EncoderFormatting. +You can use this to write JSON +data to a file like this:

+
# Writes `{"some":"json stuff"}` to the file `output.json`:
+Path.write
+    { some: "json stuff" }
+    (Path.fromStr "output.json")
+    Json.toCompactUtf8
+

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.writeBytes instead.

+
+

+ +writeBytes : List U8, Path -> Task {} [FileWriteErr Path WriteErr]

Writes bytes to a file.

+
# Writes the bytes 1, 2, 3 to the file `myfile.dat`.
+Path.writeBytes [1, 2, 3] (Path.fromStr "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.

+
+

+ +writeUtf8 : Str, Path -> Task {} [FileWriteErr Path WriteErr]

Writes a Str to a file, encoded as UTF-8.

+
# Writes "Hello!" encoded as UTF-8 to the file `myfile.txt`.
+Path.writeUtf8 "Hello!" (Path.fromStr "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.writeBytes instead.

+
+

+ +fromStr : Str -> Path

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.).

+

+ +fromBytes : List U8 -> Path

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.readBytes or WriteStream.openPath) will fail.

+

+ +display : Path -> Str

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. +toInner : Path -> Str Str, Bytes (List U8) +Assumes a path is encoded as 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 +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 +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.

+

If you happen to know the Charset that was used to encode the path, you can use +toStrUsingCharset instead of display.

+

+ +isDir : Path -> Task Bool [PathErr MetadataErr]

Returns true if the path exists on disk and is pointing at a directory. +Returns Task.ok false if the path exists and it is not a directory. If the path does not exist, +this function will return Task.err PathErr PathDoesNotExist.

+

This uses rust's std::path::is_dir.

+
+

File.isDir does the same thing, except it takes a Str instead of a Path.

+
+

+ +isFile : Path -> Task Bool [PathErr MetadataErr]

Returns true if the path exists on disk and is pointing at a regular file. +Returns Task.ok false if the path exists and it is not a file. If the path does not exist, +this function will return Task.err PathErr PathDoesNotExist.

+

This uses rust's std::path::is_file.

+
+

File.isFile does the same thing, except it takes a Str instead of a Path.

+
+

+ +isSymLink : Path -> Task Bool [PathErr MetadataErr]

Returns true if the path exists on disk and is pointing at a symbolic link. +Returns Task.ok false if the path exists and it is not a symbolic link. If the path does not exist, +this function will return Task.err PathErr PathDoesNotExist.

+

This uses rust's std::path::is_symlink.

+
+

File.isSymLink does the same thing, except it takes a Str instead of a Path.

+
+

+ +type : + Path + -> Task + [ + IsFile, + IsDir, + IsSymLink + ] [PathErr MetadataErr]

Return the type of the path if the path exists on disk.

+
+

File.type does the same thing, except it takes a Str instead of a Path.

+
+

+ +withExtension : Path, Str -> Path

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.fromStr "foo/bar/baz" |> Path.withExtension "txt"
+Path.fromStr "foo/bar/baz." |> Path.withExtension "txt"
+Path.fromStr "foo/bar/baz.xz" |> Path.withExtension "txt"
+

+ +delete : Path -> Task {} [FileWriteErr Path WriteErr]

Deletes a file from the filesystem.

+

Performs a DeleteFile +on Windows and unlink 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
+Path.delete (Path.fromStr "myfile.dat") [1, 2, 3]
+
+

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 to it has been deleted.

+
+
+

File.delete does the same thing, except it takes a Str instead of a Path.

+
+

+ +readUtf8 : + Path + -> Task Str + [ + FileReadErr Path ReadErr, + FileReadUtf8Err Path + ]

Reads a Str from a file containing UTF-8-encoded text.

+
# Reads UTF-8 encoded text into a Str from the file "myfile.txt"
+Path.readUtf8 (Path.fromStr "myfile.txt")
+

This opens the file first and closes it after writing to it. +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.readBytes instead.

+

File.readUtf8 does the same thing, except it takes a Str instead of a Path.

+
+

+ +readBytes : Path -> Task (List U8) [FileReadErr Path ReadErr]

Reads all the bytes in a file.

+
# Read all the bytes in `myfile.txt`.
+Path.readBytes (Path.fromStr "myfile.txt")
+

This opens the file first and closes it after reading its contents.

+
+

To read and decode data from a file, you can use Path.read instead.

+

File.readBytes does the same thing, except it takes a Str instead of a Path.

+
+

+ +listDir : Path -> Task (List Path) [DirErr DirErr]

Lists the files and directories inside the directory.

+
+

Dir.list does the same thing, except it takes a Str instead of a Path.

+
+

+ +deleteEmpty : Path -> Task {} [DirErr DirErr]

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.deleteEmpty does the same thing, except it takes a Str instead of a Path.

+
+

+ +deleteAll : Path -> Task {} [DirErr DirErr]

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 directory is not empty
  • +
  • the user lacks permission to remove the directory.
  • +
+
+

Dir.deleteAll does the same thing, except it takes a Str instead of a Path.

+
+

+ +createDir : Path -> Task {} [DirErr DirErr]

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 does the same thing, except it takes a Str instead of a Path.

+
+

+ +createAll : Path -> Task {} [DirErr DirErr]

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.createAll does the same thing, except it takes a Str instead of a Path.

+
+
+
+

Made by people who like to make nice things.

+
+
+ + + diff --git a/docs/0.17.0/Sleep/index.html b/docs/0.17.0/Sleep/index.html new file mode 100644 index 00000000..87a61856 --- /dev/null +++ b/docs/0.17.0/Sleep/index.html @@ -0,0 +1,252 @@ + + + + + + Sleep - Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + +
+

Sleep

+ +millis : U64 -> Task {} *

Sleep for at least the given number of milliseconds. +This uses rust's std::thread::sleep.

+
+
+

Made by people who like to make nice things.

+
+
+ + + diff --git a/docs/0.17.0/Stderr/index.html b/docs/0.17.0/Stderr/index.html new file mode 100644 index 00000000..0f8d06ef --- /dev/null +++ b/docs/0.17.0/Stderr/index.html @@ -0,0 +1,287 @@ + + + + + + Stderr - Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + +
+

Stderr

+ +Err

BrokenPipe - This error can occur when writing to a stdout that is no longer connected +to a valid input. For example, if the process on the receiving end of a pipe closes its +end, any write to that pipe could lead to a BrokenPipe error.

+

WouldBlock - This error might occur if stdout is set to non-blocking mode and the write +operation would block because the output buffer is full.

+

WriteZero - This indicates an attempt to write "zero" bytes which is technically a no-operation +(no-op), but if detected, it could be raised as an error.

+

Unsupported - If the stdout operation involves writing data in a manner or format that is not +supported, this error could be raised.

+

Interrupted - This can happen if a signal interrupts the writing process before it completes.

+

OutOfMemory - This could occur if there is not enough memory available to buffer the data being +written to stdout.

+

Other - This is a catch-all for any error not specifically categorized by the other ErrorKind +variants.

+

+ +line : Str -> Task {} [StderrErr Err]

Write the given string to standard error, +followed by a newline.

+
+

To write to stderr without the newline, see Stderr.write.

+
+

+ +write : Str -> Task {} [StderrErr Err]

Write the given string to standard error.

+

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.

+
+
+
+

Made by people who like to make nice things.

+
+
+ + + diff --git a/docs/0.17.0/Stdin/index.html b/docs/0.17.0/Stdin/index.html new file mode 100644 index 00000000..d5d0be1b --- /dev/null +++ b/docs/0.17.0/Stdin/index.html @@ -0,0 +1,299 @@ + + + + + + Stdin - Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + +
+

Stdin

+ +Err

EndOfFile - This error occurs when an end-of-file (EOF) condition is met unexpectedly +during input operations. Typically indicates that no more data is available for reading.

+

BrokenPipe - This error happens when an attempt to write to a pipe cannot proceed because +the other end of the pipe has been closed. Common in IPC (Inter-Process Communication) scenarios.

+

UnexpectedEof - Similar to EndOfFile but specifically refers to cases where the EOF occurs +unexpectedly, possibly indicating truncated or corrupted data streams.

+

InvalidInput - This error is raised when an input operation receives data that is not in a +valid format, suggesting possible data corruption or a mismatch in expected data format.

+

OutOfMemory - Occurs when an operation fails due to insufficient memory available to +complete the operation. This can affect data reading, buffering, or processing.

+

Interrupted - This error can happen if an input operation is interrupted by a system +signal before it could complete, often needing a retry or causing the operation to fail.

+

Unsupported - Raised when an operation involves a feature or operation mode that is not +supported. This might involve character encodings, data compression formats, etc.

+

Other - A catch-all category for errors that do not fall into the specified categories. +Allows for flexible error handling of uncommon or unexpected conditions.

+

+ +line : Task Str [StdinErr Err]

Read a line from standard input.

+
+

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 : {} -> Task (List U8) *

Read bytes from standard input. +‼️ This function can read no more than 16,384 bytes at a time. Use readToEnd if you need more.

+
+

This is typically used in combintation with Tty.enableRawMode, +which disables defaults terminal bevahiour and allows reading input +without buffering until Enter key is pressed.

+
+

+ +readToEnd : {} -> Task (List U8) [StdinErr Err]

Read all bytes from standard input until EOF in this source.

+
+
+

Made by people who like to make nice things.

+
+
+ + + diff --git a/docs/0.17.0/Stdout/index.html b/docs/0.17.0/Stdout/index.html new file mode 100644 index 00000000..bec2c016 --- /dev/null +++ b/docs/0.17.0/Stdout/index.html @@ -0,0 +1,287 @@ + + + + + + Stdout - Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + +
+

Stdout

+ +Err

BrokenPipe - This error can occur when writing to a stdout that is no longer connected +to a valid input. For example, if the process on the receiving end of a pipe closes its +end, any write to that pipe could lead to a BrokenPipe error.

+

WouldBlock - This error might occur if stdout is set to non-blocking mode and the write +operation would block because the output buffer is full.

+

WriteZero - This indicates an attempt to write "zero" bytes which is technically a no-operation +(no-op), but if detected, it could be raised as an error.

+

Unsupported - If the stdout operation involves writing data in a manner or format that is not +supported, this error could be raised.

+

Interrupted - This can happen if a signal interrupts the writing process before it completes.

+

OutOfMemory - This could occur if there is not enough memory available to buffer the data being +written to stdout.

+

Other - This is a catch-all for any error not specifically categorized by the other ErrorKind +variants.

+

+ +line : Str -> Task {} [StdoutErr Err]

Write the given string to standard output, +followed by a newline.

+
+

To write to stdout without the newline, see Stdout.write.

+
+

+ +write : Str -> Task {} [StdoutErr Err]

Write the given string to standard output.

+

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.

+
+
+
+

Made by people who like to make nice things.

+
+
+ + + diff --git a/docs/0.17.0/Tcp/index.html b/docs/0.17.0/Tcp/index.html new file mode 100644 index 00000000..3158dfd4 --- /dev/null +++ b/docs/0.17.0/Tcp/index.html @@ -0,0 +1,392 @@ + + + + + + Tcp - Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + +
+

Tcp

+ +Stream

Represents a TCP stream.

+

+ +ConnectErr

Represents errors that can occur when connecting to a remote host.

+

+ +StreamErr

Represents errors that can occur when performing a Task with a Stream.

+

+ +connect : Str, U16 -> Task Stream ConnectErr

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
  • +
+

+ +readUpTo : Stream, U64 -> Task (List U8) [TcpReadErr StreamErr]

Read up to a number of bytes from the TCP stream.

+
# Read up to 64 bytes from the stream and convert to a Str
+received = File.readUpTo! stream 64
+Str.fromUtf8 received
+
+

To read an exact number of bytes or fail, you can use Tcp.readExactly instead.

+
+

+ +readExactly : + Stream, + U64 + -> Task (List U8) + [ + TcpReadErr StreamErr, + TcpUnexpectedEOF + ]

Read an exact number of bytes or fail.

+
File.readExactly stream 64
+

TcpUnexpectedEOF is returned if the stream ends before the specfied number of bytes is reached.

+

+ +readUntil : Stream, U8 -> Task (List U8) [TcpReadErr StreamErr]

Read until a delimiter or EOF is reached.

+
# Read until null terminator
+File.readUntil stream 0
+

If found, the delimiter is included as the last byte.

+
+

To read until a newline is found, you can use Tcp.readLine which +conveniently decodes to a Str.

+
+

+ +readLine : + Stream + -> Task Str + [ + TcpReadErr StreamErr, + TcpReadBadUtf8 + ]

Read until a newline or EOF is reached.

+
# Read a line and then print it to `stdout`
+lineStr = File.readLine! stream
+Stdout.line lineStr
+

If found, the newline is included as the last character in the Str.

+

+ +write : Stream, List U8 -> Task {} [TcpWriteErr StreamErr]

Writes bytes to a TCP stream.

+
# Writes the bytes 1, 2, 3
+Tcp.writeBytes stream [1, 2, 3]
+
+

To write a Str, you can use Tcp.writeUtf8 instead.

+
+

+ +writeUtf8 : Stream, Str -> Task {} [TcpWriteErr StreamErr]

Writes a Str to a TCP stream, encoded as UTF-8.

+
# Write "Hi from Roc!" encoded as UTF-8
+Tcp.writeUtf8 stream "Hi from Roc!"
+
+

To write unformatted bytes, you can use Tcp.write instead.

+
+

+ +connectErrToStr : ConnectErr -> Str

Convert a ConnectErr to a Str you can print.

+
when err is
+    TcpPerfomErr (TcpConnectErr connectErr) ->
+        Stderr.line (Tcp.connectErrToStr connectErr)
+

+ +streamErrToStr : StreamErr -> Str

Convert a StreamErr to a Str you can print.

+
when err is
+    TcpPerformErr (TcpReadErr err) ->
+        errStr = Tcp.streamErrToStr err
+        Stderr.line "Error while reading: $(errStr)"
+
+    TcpPerformErr (TcpWriteErr err) ->
+        errStr = Tcp.streamErrToStr err
+        Stderr.line "Error while writing: $(errStr)"
+
+
+

Made by people who like to make nice things.

+
+
+ + + diff --git a/docs/0.17.0/Tty/index.html b/docs/0.17.0/Tty/index.html new file mode 100644 index 00000000..955f4f37 --- /dev/null +++ b/docs/0.17.0/Tty/index.html @@ -0,0 +1,269 @@ + + + + + + Tty - Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + +
+

Tty

Provides functionality to work with the terminal

+

Note: we plan on moving this file away from basic-cli in the future, see github.com/roc-lang/basic-cli/issues/73

+

+ +enableRawMode : {} -> Task {} *

Enable terminal raw mode which disables some default terminal bevahiour.

+

The following modes are disabled:

+
    +
  • Input will not be echo to the terminal screen
  • +
  • Input will not be buffered until Enter key is pressed
  • +
  • Input will not be line buffered (input sent byte-by-byte to input buffer)
  • +
  • Special keys like Backspace and CTRL+C will not be processed by terminal driver
  • +
+

Note: we plan on moving this function away from basic-cli in the future, see github.com/roc-lang/basic-cli/issues/73

+

+ +disableRawMode : {} -> Task {} *

Revert terminal to default behaviour

+

Note: we plan on moving this function away from basic-cli in the future, see github.com/roc-lang/basic-cli/issues/73

+
+
+

Made by people who like to make nice things.

+
+
+ + + diff --git a/docs/0.17.0/Url/index.html b/docs/0.17.0/Url/index.html new file mode 100644 index 00000000..dfa9d78c --- /dev/null +++ b/docs/0.17.0/Url/index.html @@ -0,0 +1,438 @@ + + + + + + Url - Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + +
+

Url

+ +Url

A Uniform Resource Locator.

+

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.fromStr.

+

+ +reserve : Url, U64 -> Url

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.fromStr "https://example.com"
+|> Url.reserve 50
+|> Url.append "stuff"
+|> Url.appendParam "café" "du Monde"
+|> Url.appendParam "email" "hi@example.com"
+

The Str.countUtf8Bytes function can be helpful in finding out how many bytes to reserve.

+

There is no Url.withCapacity because it's better to reserve extra capacity +on a Str first, and then pass that string to Url.fromStr. This function will make use +of the extra capacity.

+

+ +fromStr : Str -> Url

Create a Url without validating or percent-encoding +anything.

+
Url.fromStr "https://example.com#stuff"
+

URLs can be absolute, like https://example.com, or they can be relative, like /blah.

+
Url.fromStr "/this/is#relative"
+

Since nothing is validated, this can return invalid URLs.

+
Url.fromStr "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.

+

+ +toStr : Url -> Str

Return a Str representation of this URL.

+
# Gives "https://example.com/two%20words"
+Url.fromStr "https://example.com"
+|> Url.append "two words"
+|> Url.toStr
+

+ +append : Url, Str -> Url

Percent-encodes a +path component +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.fromStr "https://example.com"
+|> Url.append "some stuff"
+
+# Gives https://example.com/stuff?search=blah#fragment
+Url.fromStr "https://example.com?search=blah#fragment"
+|> Url.append "stuff"
+
+# Gives https://example.com/things/stuff/more/etc/"
+Url.fromStr "https://example.com/things/"
+|> Url.append "/stuff/"
+|> Url.append "/more/etc/"
+
+# Gives https://example.com/things
+Url.fromStr "https://example.com/things"
+|> Url.append ""
+

+ +appendParam : + Url, + Str, + Str + -> Url

Adds a Str query parameter to the end of the Url.

+

The key and value both get percent-encoded.

+
# Gives https://example.com?email=someone%40example.com
+Url.fromStr "https://example.com"
+|> Url.appendParam "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.fromStr "https://example.com"
+|> Url.appendParam "café" "du Monde"
+|> Url.appendParam "email" "hi@example.com"
+

+ +withQuery : Url, Str -> Url

Replaces the URL's query—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.fromStr "https://example.com?key1=val1&key2=val2#stuff"
+|> Url.withQuery "newQuery=thisRightHere"
+
+# Gives https://example.com#stuff
+Url.fromStr "https://example.com?key1=val1&key2=val2#stuff"
+|> Url.withQuery ""
+

+ +query : Url -> Str

Returns the URL's query—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.fromStr "https://example.com?key1=val1&key2=val2&key3=val3#stuff"
+|> Url.query
+
+# Gives ""
+Url.fromStr "https://example.com#stuff"
+|> Url.query
+

+ +hasQuery : Url -> Bool

Returns Bool.true if the URL has a ? in it.

+
# Gives Bool.true
+Url.fromStr "https://example.com?key=value#stuff"
+|> Url.hasQuery
+
+# Gives Bool.false
+Url.fromStr "https://example.com#stuff"
+|> Url.hasQuery
+

+ +fragment : Url -> Str

Returns the URL's fragment—the part after +the #, if it has one.

+

Returns "" if the URL has no fragment.

+
# Gives "stuff"
+Url.fromStr "https://example.com#stuff"
+|> Url.fragment
+
+# Gives ""
+Url.fromStr "https://example.com"
+|> Url.fragment
+

+ +withFragment : Url, Str -> Url

Replaces the URL's fragment.

+

If the URL didn't have a fragment, adds one. Passing "" removes the fragment.

+
# Gives https://example.com#things
+Url.fromStr "https://example.com#stuff"
+|> Url.withFragment "things"
+
+# Gives https://example.com#things
+Url.fromStr "https://example.com"
+|> Url.withFragment "things"
+
+# Gives https://example.com
+Url.fromStr "https://example.com#stuff"
+|> Url.withFragment ""
+

+ +hasFragment : Url -> Bool

Returns Bool.true if the URL has a # in it.

+
# Gives Bool.true
+Url.fromStr "https://example.com?key=value#stuff"
+|> Url.hasFragment
+
+# Gives Bool.false
+Url.fromStr "https://example.com?key=value"
+|> Url.hasFragment
+
+
+

Made by people who like to make nice things.

+
+
+ + + diff --git a/docs/0.17.0/Utc/index.html b/docs/0.17.0/Utc/index.html new file mode 100644 index 00000000..8a67565b --- /dev/null +++ b/docs/0.17.0/Utc/index.html @@ -0,0 +1,300 @@ + + + + + + Utc - Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + +
+

Utc

+ +Utc

Stores a timestamp as nanoseconds since UNIX EPOCH

+

+ +now : {} -> Task Utc *

Duration since UNIX EPOCH

+

+ +toMillisSinceEpoch : Utc -> I128

Convert Utc timestamp to milliseconds

+

+ +fromMillisSinceEpoch : I128 -> Utc

Convert milliseconds to Utc timestamp

+

+ +toNanosSinceEpoch : Utc -> I128

Convert Utc timestamp to nanoseconds

+

+ +fromNanosSinceEpoch : I128 -> Utc

Convert nanoseconds to Utc timestamp

+

+ +deltaAsMillis : Utc, Utc -> U128

Calculate milliseconds between two Utc timestamps

+

+ +deltaAsNanos : Utc, Utc -> U128

Calculate nanoseconds between two Utc timestamps

+
+
+

Made by people who like to make nice things.

+
+
+ + + diff --git a/docs/0.17.0/index.html b/docs/0.17.0/index.html new file mode 100644 index 00000000..aff43899 --- /dev/null +++ b/docs/0.17.0/index.html @@ -0,0 +1,244 @@ + + + + + + - Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + +
+

Exposed Modules

LLM docs
+
+

Made by people who like to make nice things.

+
+
+ + + diff --git a/docs/0.17.0/llms.txt b/docs/0.17.0/llms.txt new file mode 100644 index 00000000..d89fe3ba --- /dev/null +++ b/docs/0.17.0/llms.txt @@ -0,0 +1,2555 @@ +# LLM Prompt for Documentation + +## Documentation + +### Path + +#### MetadataErr + +**Type Annotation** + +**Description** + +An error when reading a path's file metadata from disk. + +#### Path + +**Type Annotation** + +**Description** + +Represents a path to a file or directory on the filesystem. + +#### DirEntry + +**Type Annotation** + +**Description** + +Record which represents a directory + +> This is the same as [`Dir.DirEntry`](Dir#DirEntry). + +#### ReadErr + +**Type Annotation** + +**Description** + +Tag union of possible errors when reading a file or directory. + +> This is the same as [`File.ReadErr`](File#ReadErr). + +#### WriteErr + +**Type Annotation** + +**Description** + +Tag union of possible errors when writing a file or directory. + +> This is the same as [`File.WriteErr`](File#WriteErr). + +#### DirErr + +**Type Annotation** + +**Description** + +**NotFound** - This error is raised when the specified directory does not exist, typically during attempts to access or manipulate it. + +**PermissionDenied** - Occurs when the user lacks the necessary permissions to perform an action on a directory, such as reading, writing, or executing. + +**AlreadyExists** - This error is thrown when trying to create a directory that already exists. + +**NotADirectory** - Raised when an operation that requires a directory (e.g., listing contents) is attempted on a file instead. + +**Other** - A catch-all for any other types of errors not explicitly listed above. + +> This is the same as [`Dir.Err`](Dir#Err). + +#### write + +**Type Annotation** + +```roc + + val, + Path, + fmt + -> Task {} [FileWriteErr Path WriteErr] + 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.toCompactUtf8` 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.fromStr "output.json") + Json.toCompactUtf8 +``` + +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.writeBytes] instead. + +#### writeBytes + +**Type Annotation** + +```roc +List U8, Path -> Task {} [FileWriteErr Path WriteErr] +``` + +**Description** + +Writes bytes to a file. + +``` +# Writes the bytes 1, 2, 3 to the file `myfile.dat`. +Path.writeBytes [1, 2, 3] (Path.fromStr "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. + +#### writeUtf8 + +**Type Annotation** + +```roc +Str, Path -> Task {} [FileWriteErr Path WriteErr] +``` + +**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.writeUtf8 "Hello!" (Path.fromStr "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.writeBytes] instead. + +#### fromStr + +**Type Annotation** + +```roc +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.). + +#### fromBytes + +**Type Annotation** + +```roc +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.readBytes` or `WriteStream.openPath`) will fail. + +#### display + +**Type Annotation** + +```roc +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]. +toInner : 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 +`toStrUsingCharset` instead of [display]. + +#### isDir + +**Type Annotation** + +```roc +Path -> Task Bool [PathErr MetadataErr] +``` + +**Description** + +Returns true if the path exists on disk and is pointing at a directory. +Returns `Task.ok false` if the path exists and it is not a directory. If the path does not exist, +this function will return `Task.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.isDir`](File#isDir) does the same thing, except it takes a [Str] instead of a [Path]. + +#### isFile + +**Type Annotation** + +```roc +Path -> Task Bool [PathErr MetadataErr] +``` + +**Description** + +Returns true if the path exists on disk and is pointing at a regular file. +Returns `Task.ok false` if the path exists and it is not a file. If the path does not exist, +this function will return `Task.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.isFile`](File#isFile) does the same thing, except it takes a [Str] instead of a [Path]. + +#### isSymLink + +**Type Annotation** + +```roc +Path -> Task Bool [PathErr MetadataErr] +``` + +**Description** + +Returns true if the path exists on disk and is pointing at a symbolic link. +Returns `Task.ok false` if the path exists and it is not a symbolic link. If the path does not exist, +this function will return `Task.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.isSymLink`](File#isSymLink) does the same thing, except it takes a [Str] instead of a [Path]. + +#### type + +**Type Annotation** + +```roc + + Path + -> Task + [ + IsFile, + IsDir, + IsSymLink + ] [PathErr MetadataErr] +``` + +**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]. + +#### withExtension + +**Type Annotation** + +```roc +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.fromStr "foo/bar/baz" |> Path.withExtension "txt" +Path.fromStr "foo/bar/baz." |> Path.withExtension "txt" +Path.fromStr "foo/bar/baz.xz" |> Path.withExtension "txt" +``` + +#### delete + +**Type Annotation** + +```roc +Path -> Task {} [FileWriteErr Path WriteErr] +``` + +**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 +Path.delete (Path.fromStr "myfile.dat") [1, 2, 3] +``` + +> 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]. + +#### readUtf8 + +**Type Annotation** + +```roc + + Path + -> Task Str + [ + FileReadErr Path ReadErr, + 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" +Path.readUtf8 (Path.fromStr "myfile.txt") +``` + +This opens the file first and closes it after writing to it. +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.readBytes] instead. +> +> [`File.readUtf8`](File#readUtf8) does the same thing, except it takes a [Str] instead of a [Path]. + +#### readBytes + +**Type Annotation** + +```roc +Path -> Task (List U8) [FileReadErr Path ReadErr] +``` + +**Description** + +Reads all the bytes in a file. + +``` +# Read all the bytes in `myfile.txt`. +Path.readBytes (Path.fromStr "myfile.txt") +``` + +This opens the file first and closes it after reading its contents. + +> To read and decode data from a file, you can use `Path.read` instead. +> +> [`File.readBytes`](File#readBytes) does the same thing, except it takes a [Str] instead of a [Path]. + +#### listDir + +**Type Annotation** + +```roc +Path -> Task (List Path) [DirErr DirErr] +``` + +**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]. + +#### deleteEmpty + +**Type Annotation** + +```roc +Path -> Task {} [DirErr DirErr] +``` + +**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.deleteEmpty`](Dir#deleteEmpty) does the same thing, except it takes a [Str] instead of a [Path]. + +#### deleteAll + +**Type Annotation** + +```roc +Path -> Task {} [DirErr DirErr] +``` + +**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 directory is not empty + - the user lacks permission to remove the directory. + +> [`Dir.deleteAll`](Dir#deleteAll) does the same thing, except it takes a [Str] instead of a [Path]. + +#### createDir + +**Type Annotation** + +```roc +Path -> Task {} [DirErr DirErr] +``` + +**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]. + +#### createAll + +**Type Annotation** + +```roc +Path -> Task {} [DirErr DirErr] +``` + +**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.createAll`](Dir#createAll) does the same thing, except it takes a [Str] instead of a [Path]. + +### Arg + +#### list + +**Type Annotation** + +```roc +{} -> Task (List Str) * +``` + +**Description** + +Gives a list of the program's command-line arguments. + +#### parse + +**Type Annotation** + +```roc + + CliParser state + -> Task state + [ + Exit I32 Str, + StdoutErr Stdout.Err + ] +``` + +**Description** + +Parse arguments using a CLI parser or show a useful message on failure. + +We have the following priorities in returning messages to the user: +1) If the `-h/--help` flag is passed, the help page for the command/subcommand + called will be displayed no matter if your arguments were correctly parsed. +2) If the `-V/--version` flag is passed, the version for the app will + be displayed no matter if your arguments were correctly parsed. +3) If the provided arguments were parsed and neither of the above two + built-in flags were passed, we return to you your data. +4) If the provided arguments were not correct, we return a short message + with which argument was not provided correctly, followed by the + usage section of the relevant command/subcommand's help text. + +```roc +exampleCli = + { Cli.combine <- + verbosity: Opt.count { short: "v", help: "How verbose our logs should be." }, + alpha: Opt.maybeU64 { short: "a", help: "Set the alpha level." }, + } + |> Cli.finish { + name: "example", + version: "v0.1.0", + description: "An example CLI.", + } + |> Cli.assertValid + +expect + exampleCli + |> Cli.parseOrDisplayMessage ["example", "-h"] + == Err + """ + example v0.1.0 + + An example CLI. + + Usage: + example [OPTIONS] + + Options: + -v How verbose our logs should be. + -a Set the alpha level. + -h, --help Show this help page. + -V, --version Show the version. + """ + +expect + exampleCli + |> Cli.parseOrDisplayMessage ["example", "-V"] + == Err "v0.1.0" + +expect + exampleCli + |> Cli.parseOrDisplayMessage ["example", "-v"] + == Ok { verbosity: 1 } + +expect + exampleCli + |> Cli.parseOrDisplayMessage ["example", "-x"] + == Err + """ + Error: The argument -x was not recognized. + + Usage: + example [OPTIONS] + """ +``` + +### Dir + +#### Err + +**Type Annotation** + +```roc +Path.DirErr +``` + +**Description** + +**NotFound** - This error is raised when the specified path does not exist, typically during attempts to access or manipulate it, but also potentially when trying to create a directory and a parent directory does not exist. + +**PermissionDenied** - Occurs when the user lacks the necessary permissions to perform an action on a directory, such as reading, writing, or executing. + +**Other** - A catch-all for any other types of errors not explicitly listed above. + +> This is the same as [`Path.DirErr`]. + +#### DirEntry + +**Type Annotation** + +```roc +Path.DirEntry +``` + +**Description** + +Record which represents a directory + +> This is the same as [`Path.DirEntry`]. + +#### list + +**Type Annotation** + +```roc +Str -> Task (List Path) [DirErr Err] +``` + +**Description** + +Lists the files and directories inside the directory. + +> [Path.listDir] does the same thing, except it takes a [Path] instead of a [Str]. + +#### deleteEmpty + +**Type Annotation** + +```roc +Str -> Task {} [DirErr Err] +``` + +**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.deleteEmpty] does the same thing, except it takes a [Path] instead of a [Str]. + +#### deleteAll + +**Type Annotation** + +```roc +Str -> Task {} [DirErr Err] +``` + +**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 directory is not empty + - the user lacks permission to remove the directory. + +> [Path.deleteAll] does the same thing, except it takes a [Path] instead of a [Str]. + +#### create + +**Type Annotation** + +```roc +Str -> Task {} [DirErr Err] +``` + +**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.createDir] does the same thing, except it takes a [Path] instead of a [Str]. + +#### createAll + +**Type Annotation** + +```roc +Str -> Task {} [DirErr Err] +``` + +**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.createAll] does the same thing, except it takes a [Path] instead of a [Str]. + +### Env + +#### cwd + +**Type Annotation** + +```roc +Task 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. + +#### setCwd + +**Type Annotation** + +```roc +Path -> Task {} [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. + +#### exePath + +**Type Annotation** + +```roc +Task Path [ExePathUnavailable] +``` + +**Description** + +Gets the path to the currently-running executable. + +#### var + +**Type Annotation** + +```roc +Str -> Task Str [VarNotFound] +``` + +**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 + +**Type Annotation** + +```roc + + Str + -> Task val + [ + VarNotFound, + DecodeErr DecodeError + ] + where val implements Decoding +``` + +**Description** + +Reads the given environment variable and attempts to decode it. + +The type being decoded into will be determined by type inference. For example, +if this ends up being used like a `Task 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 +getU16Var : Str -> Task U16 [VarNotFound, DecodeErr DecodeError] [Read [Env]] +getU16Var = \var -> Env.decode var +``` + +If `NUM_THINGS=123` then `getU16Var` succeeds with the value of `123u16`. +However if `NUM_THINGS=123456789`, then `getU16Var` 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 + +**Type Annotation** + +```roc +{} -> Task (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 + +**Type Annotation** + +```roc +Task + { + 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. + + +#### tempDir + +**Type Annotation** + +```roc +{} -> Task 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 + +#### ReadErr + +**Type Annotation** + +```roc +Path.ReadErr +``` + +**Description** + +Tag union of possible errors when reading a file or directory. + +> This is the same as [`Path.ReadErr`]. + +#### WriteErr + +**Type Annotation** + +```roc +Path.WriteErr +``` + +**Description** + +Tag union of possible errors when writing a file or directory. + +> This is the same as [`Path.WriteErr`]. + +#### write + +**Type Annotation** + +```roc + + val, + Str, + fmt + -> Task {} [FileWriteErr Path WriteErr] + 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.toCompactUtf8` 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" } + (Path.fromStr "output.json") + Json.toCompactUtf8 +``` + +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.writeBytes] instead. +> +> [Path.write] does the same thing, except it takes a [Path] instead of a [Str]. + +#### writeBytes + +**Type Annotation** + +```roc +List U8, Str -> Task {} [FileWriteErr Path WriteErr] +``` + +**Description** + +Writes bytes to a file. + +``` +# Writes the bytes 1, 2, 3 to the file `myfile.dat`. +File.writeBytes [1, 2, 3] (Path.fromStr "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.writeBytes] does the same thing, except it takes a [Path] instead of a [Str]. + +#### writeUtf8 + +**Type Annotation** + +```roc +Str, Str -> Task {} [FileWriteErr Path WriteErr] +``` + +**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.writeUtf8 "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.writeBytes] instead. +> +> [Path.writeUtf8] does the same thing, except it takes a [Path] instead of a [Str]. + +#### delete + +**Type Annotation** + +```roc +Str -> Task {} [FileWriteErr Path WriteErr] +``` + +**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 +File.delete (Path.fromStr "myfile.dat") [1, 2, 3] +``` + +> 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]. + +#### readBytes + +**Type Annotation** + +```roc +Str -> Task (List U8) [FileReadErr Path ReadErr] +``` + +**Description** + +Reads all the bytes in a file. + +``` +# Read all the bytes in `myfile.txt`. +File.readBytes "myfile.txt" +``` + +This opens the file first and closes it after reading its contents. + +> To read and decode data from a file, you can use `File.read` instead. +> +> [Path.readBytes] does the same thing, except it takes a [Path] instead of a [Str]. + +#### readUtf8 + +**Type Annotation** + +```roc + + Str + -> Task Str + [ + FileReadErr Path ReadErr, + 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" +File.readUtf8 "myfile.txt" +``` + +This opens the file first and closes it after writing to it. +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.readBytes] instead. + +> [Path.readUtf8] does the same thing, except it takes a [Path] instead of a [Str]. + +#### isDir + +**Type Annotation** + +```roc +Str -> Task Bool [PathErr MetadataErr] +``` + +**Description** + +Returns true if the path exists on disk and is pointing at a directory. +Returns `Task.ok false` if the path exists and it is not a directory. If the path does not exist, +this function will return `Task.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.isDir] does the same thing, except it takes a [Path] instead of a [Str]. + +#### isFile + +**Type Annotation** + +```roc +Str -> Task Bool [PathErr MetadataErr] +``` + +**Description** + +Returns true if the path exists on disk and is pointing at a regular file. +Returns `Task.ok false` if the path exists and it is not a file. If the path does not exist, +this function will return `Task.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.isFile] does the same thing, except it takes a [Path] instead of a [Str]. + +#### isSymLink + +**Type Annotation** + +```roc +Str -> Task Bool [PathErr MetadataErr] +``` + +**Description** + +Returns true if the path exists on disk and is pointing at a symbolic link. +Returns `Task.ok false` if the path exists and it is not a symbolic link. If the path does not exist, +this function will return `Task.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.isSymLink] does the same thing, except it takes a [Path] instead of a [Str]. + +#### type + +**Type Annotation** + +```roc + + Str + -> Task + [ + IsFile, + IsDir, + IsSymLink + ] [PathErr MetadataErr] +``` + +**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]. + +#### Reader + +**Type Annotation** + +#### openReader + +**Type Annotation** + +```roc +Str -> Task Reader [GetFileReadErr Path ReadErr] +``` + +**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 [readUtf8] if you want to get the entire file contents at once. + +#### openReaderWithCapacity + +**Type Annotation** + +```roc +Str, U64 -> Task Reader [GetFileReadErr Path ReadErr] +``` + +**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 [readUtf8] if you want to get the entire file contents at once. + +#### readLine + +**Type Annotation** + +```roc +Reader -> Task (List U8) [FileReadErr Path Str] +``` + +**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 [readUtf8] if you want to get the entire file contents at once. + +### FileMetadata + +#### FileMetadata + +**Type Annotation** + +**Description** + +An opaque type that represents metadata about a file. + +#### bytes + +**Type Annotation** + +```roc +FileMetadata -> U64 +``` + +**Description** + +Returns the number of bytes in the associated file. + +#### isReadonly + +**Type Annotation** + +```roc +FileMetadata -> Bool +``` + +**Description** + +Returns [Bool.true] if the associated file is read-only. + +#### type + +**Type Annotation** + +```roc + + FileMetadata + -> + [ + File, + Dir, + Symlink + ] +``` + +**Description** + +Returns the type of the associated file. + +#### mode + +**Type Annotation** + +```roc + + FileMetadata + -> + [ + Unix U32, + NonUnix + ] +``` + +**Description** + +Returns the mode of the associated file. + +### Http + +#### Request + +**Type Annotation** + +**Description** + +Represents an HTTP request. + +#### Method + +**Type Annotation** + +**Description** + +Represents an HTTP method. + +#### Header + +**Type Annotation** + +**Description** + +Represents an HTTP header e.g. `Content-Type: application/json` + +#### TimeoutConfig + +**Type Annotation** + +**Description** + +Represents a timeout configuration for an HTTP request. + +#### Response + +**Type Annotation** + +**Description** + +Represents an HTTP response. + +#### Err + +**Type Annotation** + +**Description** + +Represents an HTTP error. + +#### errorBodyToBytes + +**Type Annotation** + +**Description** + +Convert the ErrorBody of a BadStatus error to List U8. + +#### defaultRequest + +**Type Annotation** + +```roc +Request +``` + +**Description** + +A default [Request] value. + +``` +# GET "roc-lang.org" +{ Http.defaultRequest & + url: "https://www.roc-lang.org", +} +``` + + +#### header + +**Type Annotation** + +```roc +Str, Str -> Header +``` + +**Description** + +An HTTP header for configuring requests. + +See common headers [here](https://en.wikipedia.org/wiki/List_of_HTTP_header_fields). + + +#### handleStringResponse + +**Type Annotation** + +```roc +Response -> Result Str Err +``` + +**Description** + +Map a [Response] body to a [Str] or return an [Err]. + +#### errorToString + +**Type Annotation** + +```roc +Err -> Str +``` + +**Description** + +Convert an [Err] to a [Str]. + +#### send + +**Type Annotation** + +```roc +Request -> Task Response [HttpErr Err] +``` + +**Description** + +Task to send an HTTP request, succeeds with a value of [Str] or fails with an +[Err]. + +``` +# Prints out the HTML of the Roc-lang website. +response = + { Http.defaultRequest & url: "https://www.roc-lang.org" } + |> Http.send! + +response.body +|> Str.fromUtf8 +|> Result.withDefault "Invalid UTF-8" +|> Stdout.line +``` + +#### get + +**Type Annotation** + +```roc + + Str, + fmt + -> Task body + [ + HttpErr Http.Err, + HttpDecodingFailed + ] + 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.toBytes {foo: "Hello Json!"} Json.utf8` +{ foo } = Http.get! "http://localhost:8000" Json.utf8 +``` + +### Stderr + +#### Err + +**Type Annotation** + +**Description** + +**BrokenPipe** - This error can occur when writing to a stdout that is no longer connected +to a valid input. For example, if the process on the receiving end of a pipe closes its +end, any write to that pipe could lead to a BrokenPipe error. + +**WouldBlock** - This error might occur if stdout is set to non-blocking mode and the write +operation would block because the output buffer is full. + +**WriteZero** - This indicates an attempt to write "zero" bytes which is technically a no-operation +(no-op), but if detected, it could be raised as an error. + +**Unsupported** - If the stdout operation involves writing data in a manner or format that is not +supported, this error could be raised. + +**Interrupted** - This can happen if a signal interrupts the writing process before it completes. + +**OutOfMemory** - This could occur if there is not enough memory available to buffer the data being +written to stdout. + +**Other** - This is a catch-all for any error not specifically categorized by the other ErrorKind +variants. + +#### line + +**Type Annotation** + +```roc +Str -> Task {} [StderrErr Err] +``` + +**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 + +**Type Annotation** + +```roc +Str -> Task {} [StderrErr Err] +``` + +**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]. + +### Stdin + +#### Err + +**Type Annotation** + +**Description** + +**EndOfFile** - This error occurs when an end-of-file (EOF) condition is met unexpectedly +during input operations. Typically indicates that no more data is available for reading. + +**BrokenPipe** - This error happens when an attempt to write to a pipe cannot proceed because +the other end of the pipe has been closed. Common in IPC (Inter-Process Communication) scenarios. + +**UnexpectedEof** - Similar to EndOfFile but specifically refers to cases where the EOF occurs +unexpectedly, possibly indicating truncated or corrupted data streams. + +**InvalidInput** - This error is raised when an input operation receives data that is not in a +valid format, suggesting possible data corruption or a mismatch in expected data format. + +**OutOfMemory** - Occurs when an operation fails due to insufficient memory available to +complete the operation. This can affect data reading, buffering, or processing. + +**Interrupted** - This error can happen if an input operation is interrupted by a system +signal before it could complete, often needing a retry or causing the operation to fail. + +**Unsupported** - Raised when an operation involves a feature or operation mode that is not +supported. This might involve character encodings, data compression formats, etc. + +**Other** - A catch-all category for errors that do not fall into the specified categories. +Allows for flexible error handling of uncommon or unexpected conditions. + +#### line + +**Type Annotation** + +```roc +Task Str [StdinErr Err] +``` + +**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 + +**Type Annotation** + +```roc +{} -> Task (List U8) * +``` + +**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 [readToEnd] if you need more. + +> This is typically used in combintation with [Tty.enableRawMode], +which disables defaults terminal bevahiour and allows reading input +without buffering until Enter key is pressed. + +#### readToEnd + +**Type Annotation** + +```roc +{} -> Task (List U8) [StdinErr Err] +``` + +**Description** + +Read all bytes from [standard input](https://en.wikipedia.org/wiki/Standard_streams#Standard_input_(stdin)) until EOF in this source. + +### Stdout + +#### Err + +**Type Annotation** + +**Description** + +**BrokenPipe** - This error can occur when writing to a stdout that is no longer connected +to a valid input. For example, if the process on the receiving end of a pipe closes its +end, any write to that pipe could lead to a BrokenPipe error. + +**WouldBlock** - This error might occur if stdout is set to non-blocking mode and the write +operation would block because the output buffer is full. + +**WriteZero** - This indicates an attempt to write "zero" bytes which is technically a no-operation +(no-op), but if detected, it could be raised as an error. + +**Unsupported** - If the stdout operation involves writing data in a manner or format that is not +supported, this error could be raised. + +**Interrupted** - This can happen if a signal interrupts the writing process before it completes. + +**OutOfMemory** - This could occur if there is not enough memory available to buffer the data being +written to stdout. + +**Other** - This is a catch-all for any error not specifically categorized by the other ErrorKind +variants. + +#### line + +**Type Annotation** + +```roc +Str -> Task {} [StdoutErr Err] +``` + +**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 + +**Type Annotation** + +```roc +Str -> Task {} [StdoutErr Err] +``` + +**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]. + +### Tcp + +#### Stream + +**Type Annotation** + +**Description** + +Represents a TCP stream. + +#### ConnectErr + +**Type Annotation** + +**Description** + +Represents errors that can occur when connecting to a remote host. + +#### StreamErr + +**Type Annotation** + +**Description** + +Represents errors that can occur when performing a [Task] with a [Stream]. + +#### connect + +**Type Annotation** + +```roc +Str, U16 -> Task 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` + + +#### readUpTo + +**Type Annotation** + +```roc +Stream, U64 -> Task (List U8) [TcpReadErr StreamErr] +``` + +**Description** + +Read up to a number of bytes from the TCP stream. + +``` +# Read up to 64 bytes from the stream and convert to a Str +received = File.readUpTo! stream 64 +Str.fromUtf8 received +``` + +> To read an exact number of bytes or fail, you can use [Tcp.readExactly] instead. + +#### readExactly + +**Type Annotation** + +```roc + + Stream, + U64 + -> Task (List U8) + [ + TcpReadErr StreamErr, + TcpUnexpectedEOF + ] +``` + +**Description** + +Read an exact number of bytes or fail. + +``` +File.readExactly stream 64 +``` + +`TcpUnexpectedEOF` is returned if the stream ends before the specfied number of bytes is reached. + + +#### readUntil + +**Type Annotation** + +```roc +Stream, U8 -> Task (List U8) [TcpReadErr StreamErr] +``` + +**Description** + +Read until a delimiter or EOF is reached. + +``` +# Read until null terminator +File.readUntil stream 0 +``` + +If found, the delimiter is included as the last byte. + +> To read until a newline is found, you can use [Tcp.readLine] which +conveniently decodes to a [Str]. + +#### readLine + +**Type Annotation** + +```roc + + Stream + -> Task Str + [ + TcpReadErr StreamErr, + TcpReadBadUtf8 + ] +``` + +**Description** + +Read until a newline or EOF is reached. + +``` +# Read a line and then print it to `stdout` +lineStr = File.readLine! stream +Stdout.line lineStr +``` + +If found, the newline is included as the last character in the [Str]. + + +#### write + +**Type Annotation** + +```roc +Stream, List U8 -> Task {} [TcpWriteErr StreamErr] +``` + +**Description** + +Writes bytes to a TCP stream. + +``` +# Writes the bytes 1, 2, 3 +Tcp.writeBytes stream [1, 2, 3] +``` + +> To write a [Str], you can use [Tcp.writeUtf8] instead. + +#### writeUtf8 + +**Type Annotation** + +```roc +Stream, Str -> Task {} [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.writeUtf8 stream "Hi from Roc!" +``` + +> To write unformatted bytes, you can use [Tcp.write] instead. + +#### connectErrToStr + +**Type Annotation** + +```roc +ConnectErr -> Str +``` + +**Description** + +Convert a [ConnectErr] to a [Str] you can print. + +``` +when err is + TcpPerfomErr (TcpConnectErr connectErr) -> + Stderr.line (Tcp.connectErrToStr connectErr) +``` + + +#### streamErrToStr + +**Type Annotation** + +```roc +StreamErr -> Str +``` + +**Description** + +Convert a [StreamErr] to a [Str] you can print. + +``` +when err is + TcpPerformErr (TcpReadErr err) -> + errStr = Tcp.streamErrToStr err + Stderr.line "Error while reading: $(errStr)" + + TcpPerformErr (TcpWriteErr err) -> + errStr = Tcp.streamErrToStr err + Stderr.line "Error while writing: $(errStr)" +``` + + +### Url + +#### Url + +**Type Annotation** + +**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.fromStr]. + +#### reserve + +**Type Annotation** + +```roc +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.fromStr "https://example.com" +|> Url.reserve 50 +|> Url.append "stuff" +|> Url.appendParam "café" "du Monde" +|> Url.appendParam "email" "hi@example.com" +``` +The [Str.countUtf8Bytes](https://www.roc-lang.org/builtins/Str#countUtf8Bytes) function can be helpful in finding out how many bytes to reserve. + +There is no `Url.withCapacity` because it's better to reserve extra capacity +on a [Str] first, and then pass that string to [Url.fromStr]. This function will make use +of the extra capacity. + +#### fromStr + +**Type Annotation** + +```roc +Str -> Url +``` + +**Description** + +Create a [Url] without validating or [percent-encoding](https://en.wikipedia.org/wiki/Percent-encoding) +anything. + +``` +Url.fromStr "https://example.com#stuff" +``` + +URLs can be absolute, like `https://example.com`, or they can be relative, like `/blah`. + +``` +Url.fromStr "/this/is#relative" +``` + +Since nothing is validated, this can return invalid URLs. + +``` +Url.fromStr "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. + + +#### toStr + +**Type Annotation** + +```roc +Url -> Str +``` + +**Description** + +Return a [Str] representation of this URL. +``` +# Gives "https://example.com/two%20words" +Url.fromStr "https://example.com" +|> Url.append "two words" +|> Url.toStr +``` + +#### append + +**Type Annotation** + +```roc +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.fromStr "https://example.com" +|> Url.append "some stuff" + +# Gives https://example.com/stuff?search=blah#fragment +Url.fromStr "https://example.com?search=blah#fragment" +|> Url.append "stuff" + +# Gives https://example.com/things/stuff/more/etc/" +Url.fromStr "https://example.com/things/" +|> Url.append "/stuff/" +|> Url.append "/more/etc/" + +# Gives https://example.com/things +Url.fromStr "https://example.com/things" +|> Url.append "" +``` + +#### appendParam + +**Type Annotation** + +```roc + + 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.fromStr "https://example.com" +|> Url.appendParam "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.fromStr "https://example.com" +|> Url.appendParam "café" "du Monde" +|> Url.appendParam "email" "hi@example.com" +``` + + +#### withQuery + +**Type Annotation** + +```roc +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.fromStr "https://example.com?key1=val1&key2=val2#stuff" +|> Url.withQuery "newQuery=thisRightHere" + +# Gives https://example.com#stuff +Url.fromStr "https://example.com?key1=val1&key2=val2#stuff" +|> Url.withQuery "" +``` + +#### query + +**Type Annotation** + +```roc +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.fromStr "https://example.com?key1=val1&key2=val2&key3=val3#stuff" +|> Url.query + +# Gives "" +Url.fromStr "https://example.com#stuff" +|> Url.query +``` + + +#### hasQuery + +**Type Annotation** + +```roc +Url -> Bool +``` + +**Description** + +Returns [Bool.true] if the URL has a `?` in it. + +``` +# Gives Bool.true +Url.fromStr "https://example.com?key=value#stuff" +|> Url.hasQuery + +# Gives Bool.false +Url.fromStr "https://example.com#stuff" +|> Url.hasQuery +``` + + +#### fragment + +**Type Annotation** + +```roc +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.fromStr "https://example.com#stuff" +|> Url.fragment + +# Gives "" +Url.fromStr "https://example.com" +|> Url.fragment +``` + + +#### withFragment + +**Type Annotation** + +```roc +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.fromStr "https://example.com#stuff" +|> Url.withFragment "things" + +# Gives https://example.com#things +Url.fromStr "https://example.com" +|> Url.withFragment "things" + +# Gives https://example.com +Url.fromStr "https://example.com#stuff" +|> Url.withFragment "" +``` + + +#### hasFragment + +**Type Annotation** + +```roc +Url -> Bool +``` + +**Description** + +Returns [Bool.true] if the URL has a `#` in it. + +``` +# Gives Bool.true +Url.fromStr "https://example.com?key=value#stuff" +|> Url.hasFragment + +# Gives Bool.false +Url.fromStr "https://example.com?key=value" +|> Url.hasFragment +``` + + +### Utc + +#### Utc + +**Type Annotation** + +**Description** + +Stores a timestamp as nanoseconds since UNIX EPOCH + +#### now + +**Type Annotation** + +```roc +{} -> Task Utc * +``` + +**Description** + +Duration since UNIX EPOCH + +#### toMillisSinceEpoch + +**Type Annotation** + +```roc +Utc -> I128 +``` + +**Description** + +Convert Utc timestamp to milliseconds + +#### fromMillisSinceEpoch + +**Type Annotation** + +```roc +I128 -> Utc +``` + +**Description** + +Convert milliseconds to Utc timestamp + +#### toNanosSinceEpoch + +**Type Annotation** + +```roc +Utc -> I128 +``` + +**Description** + +Convert Utc timestamp to nanoseconds + +#### fromNanosSinceEpoch + +**Type Annotation** + +```roc +I128 -> Utc +``` + +**Description** + +Convert nanoseconds to Utc timestamp + +#### deltaAsMillis + +**Type Annotation** + +```roc +Utc, Utc -> U128 +``` + +**Description** + +Calculate milliseconds between two Utc timestamps + +#### deltaAsNanos + +**Type Annotation** + +```roc +Utc, Utc -> U128 +``` + +**Description** + +Calculate nanoseconds between two Utc timestamps + +### Sleep + +#### millis + +**Type Annotation** + +```roc +U64 -> Task {} * +``` + +**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 + +#### Cmd + +**Type Annotation** + +**Description** + +Represents a command to be executed in a child process. + +#### Err + +**Type Annotation** + +**Description** + +Errors from executing a command. + +#### outputErrToStr + +**Type Annotation** + +```roc + + ( + Output, + Err + ) + -> Str +``` + +#### Output + +**Type Annotation** + +**Description** + +Represents the output of a command. + +#### new + +**Type Annotation** + +```roc +Str -> Cmd +``` + +**Description** + +Create a new command to execute the given program in a child process. + +#### arg + +**Type Annotation** + +```roc +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 + +**Type Annotation** + +```roc +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"] +``` + + +#### env + +**Type Annotation** + +```roc + + 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 + +**Type Annotation** + +```roc + + 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")] +``` + + +#### clearEnvs + +**Type Annotation** + +```roc +Cmd -> Cmd +``` + +**Description** + +Clear all environment variables, and prevent inheriting from parent, only +the environment variables provided to command are available to the child. + +``` +# Represents "env" with only "FOO" environment variable set +Cmd.new "env" +|> Cmd.clearEnvs +|> Cmd.env "FOO" "BAR" +``` + + +#### output + +**Type Annotation** + +```roc + + Cmd + -> Task Output + [ + CmdOutputError + ( + Output, + Err + ) + ] +``` + +**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. + + +#### status + +**Type Annotation** + +```roc +Cmd -> Task {} [CmdError Err] +``` + +**Description** + +Execute command and inherit stdin, stdout and stderr from parent + + +#### exec + +**Type Annotation** + +```roc +Str, List Str -> Task {} [CmdError Err] +``` + +**Description** + +Execute command and inherit stdin, stdout and stderr from parent + +``` +# Call echo to print "hello world" +Cmd.exec! "echo" ["hello world"] +``` + +### Tty + +#### enableRawMode + +**Type Annotation** + +```roc +{} -> Task {} * +``` + +**Description** + +Enable terminal raw mode which disables some default terminal bevahiour. + +The following modes are disabled: +- Input will not be echo to the terminal screen +- Input will not be buffered until Enter key is pressed +- Input will not be line buffered (input sent byte-by-byte to input buffer) +- Special keys like Backspace and CTRL+C will not be processed by terminal driver + +Note: we plan on moving this function away from basic-cli in the future, see github.com/roc-lang/basic-cli/issues/73 + + +#### disableRawMode + +**Type Annotation** + +```roc +{} -> Task {} * +``` + +**Description** + +Revert terminal to default behaviour + +Note: we plan on moving this function away from basic-cli in the future, see github.com/roc-lang/basic-cli/issues/73 + + diff --git a/docs/0.17.0/search.js b/docs/0.17.0/search.js new file mode 100644 index 00000000..d21d3eff --- /dev/null +++ b/docs/0.17.0/search.js @@ -0,0 +1,284 @@ +const toggleSidebarEntryActive = (moduleName) => { + let sidebar = document.getElementById("sidebar-nav"); + + if (sidebar != null) { + // Un-hide everything + sidebar.querySelectorAll(".sidebar-entry").forEach((entry) => { + let entryName = entry.querySelector(".sidebar-module-link").dataset + .moduleName; + if (moduleName === entryName) { + entry.firstChild.classList.toggle("active"); + } + }); + } +}; + +const setupSidebarNav = () => { + // Re-hide all the sub-entries except for those of the current module + let currentModuleName = document.querySelector(".module-name").textContent; + toggleSidebarEntryActive(currentModuleName); + + document.querySelectorAll(".entry-toggle").forEach((el) => { + el.addEventListener("click", (e) => { + e.preventDefault(); + e.stopImmediatePropagation(); + const moduleName = e.target.parentElement.dataset.moduleName; + toggleSidebarEntryActive(moduleName); + }); + }); +}; + +const setupSearch = () => { + let searchTypeAhead = document.getElementById("search-type-ahead"); + let searchBox = document.getElementById("module-search"); + let searchForm = document.getElementById("module-search-form"); + let topSearchResultListItem = undefined; + + // Hide the results whenever anyone clicks outside the search results. + window.addEventListener("click", function (event) { + if (!searchForm?.contains(event.target)) { + searchTypeAhead.classList.add("hidden"); + } + }); + + if (searchBox != null) { + function searchKeyDown(event) { + switch (event.key) { + case "ArrowDown": { + event.preventDefault(); + + const focused = document.querySelector( + "#search-type-ahead > li:not([class*='hidden']) > a:focus", + ); + + // Find the next element to focus. + let nextToFocus = focused?.parentElement?.nextElementSibling; + + while ( + nextToFocus != null && + nextToFocus.classList.contains("hidden") + ) { + nextToFocus = nextToFocus.nextElementSibling; + } + + if (nextToFocus == null) { + // If none of the links were focused, focus the first one. + // Also if we've reached the last one in the list, wrap around to the first. + document + .querySelector( + "#search-type-ahead > li:not([class*='hidden']) > a", + ) + ?.focus(); + } else { + nextToFocus.querySelector("a").focus(); + } + + break; + } + case "ArrowUp": { + event.preventDefault(); + + const focused = document.querySelector( + "#search-type-ahead > li:not([class*='hidden']) > a:focus", + ); + + // Find the next element to focus. + let nextToFocus = focused?.parentElement?.previousElementSibling; + while ( + nextToFocus != null && + nextToFocus.classList.contains("hidden") + ) { + nextToFocus = nextToFocus.previousElementSibling; + } + + if (nextToFocus == null) { + // If none of the links were focused, or we're at the first one, focus the search box again. + searchBox?.focus(); + } else { + // If one of the links was focused, focus the previous one + nextToFocus.querySelector("a").focus(); + } + + break; + } + } + } + + searchForm.addEventListener("keydown", searchKeyDown); + + function search() { + topSearchResultListItem = undefined; + let text = searchBox.value.toLowerCase(); // Search is case-insensitive. + + if (text === "") { + searchTypeAhead.classList.add("hidden"); + } else { + let totalResults = 0; + // Firsttype-ahead-signature", show/hide all the sub-entries within each module (top-level functions etc.) + searchTypeAhead.querySelectorAll("li").forEach((entry) => { + const entryModule = entry + .querySelector(".type-ahead-module-name") + .textContent.toLowerCase(); + const entryName = entry + .querySelector(".type-ahead-def-name") + .textContent.toLowerCase(); + const entrySignature = entry + .querySelector(".type-ahead-signature") + ?.textContent?.toLowerCase() + ?.replace(/\s+/g, ""); + + const qualifiedEntryName = `${entryModule}.${entryName}`; + + if ( + qualifiedEntryName.includes(text) || + entrySignature?.includes(text.replace(/\s+/g, "")) + ) { + totalResults++; + entry.classList.remove("hidden"); + if (topSearchResultListItem === undefined) { + topSearchResultListItem = entry; + } + } else { + entry.classList.add("hidden"); + } + }); + if (totalResults < 1) { + searchTypeAhead.classList.add("hidden"); + } else { + searchTypeAhead.classList.remove("hidden"); + } + } + } + + searchBox.addEventListener("input", search); + + search(); + + function searchSubmit(e) { + // pick the top result if the user submits search form + e.preventDefault(); + if (topSearchResultListItem !== undefined) { + let topSearchResultListItemAnchor = + topSearchResultListItem.querySelector("a"); + if (topSearchResultListItemAnchor !== null) { + topSearchResultListItemAnchor.click(); + } + } + } + searchForm.addEventListener("submit", searchSubmit); + + // Capture '/' keypress for quick search + window.addEventListener("keyup", (e) => { + if (e.key === "s" && document.activeElement !== searchBox) { + e.preventDefault(); + searchBox.focus(); + searchBox.value = ""; + } + + if (e.key === "Escape") { + if (document.activeElement === searchBox) { + // De-focus and clear input box + searchBox.value = ""; + searchBox.blur(); + } else { + // Hide the search results + searchTypeAhead.classList.add("hidden"); + + if (searchTypeAhead.contains(document.activeElement)) { + searchBox.focus(); + } + } + } + }); + } +}; + +const isTouchSupported = () => { + try { + document.createEvent("TouchEvent"); + return true; + } catch (e) { + return false; + } +}; + +const setupCodeBlocks = () => { + // Select all elements that are children of
 elements
+  const codeBlocks = document.querySelectorAll("pre > samp");
+
+  // Iterate over each code block
+  codeBlocks.forEach((codeBlock) => {
+    // Create a "Copy" button
+    const copyButton = document.createElement("button");
+    copyButton.classList.add("copy-button");
+    copyButton.textContent = "Copy";
+
+    // Add event listener to copy button
+    copyButton.addEventListener("click", () => {
+      const codeText = codeBlock.innerText;
+      navigator.clipboard.writeText(codeText);
+      copyButton.textContent = "Copied!";
+      copyButton.classList.add("copy-button-copied");
+      copyButton.addEventListener("mouseleave", () => {
+        copyButton.textContent = "Copy";
+        copyButton.classList.remove("copy-button-copied");
+      });
+    });
+
+    // Create a container for the copy button and append it to the document
+    const buttonContainer = document.createElement("div");
+    buttonContainer.classList.add("button-container");
+    buttonContainer.appendChild(copyButton);
+    codeBlock.parentNode.insertBefore(buttonContainer, codeBlock);
+
+    // Hide the button container by default
+    buttonContainer.style.display = "none";
+
+    if (isTouchSupported()) {
+      // Show the button container on click for touch support (e.g. mobile)
+      document.addEventListener("click", (event) => {
+        if (event.target.closest("pre > samp") !== codeBlock) {
+          buttonContainer.style.display = "none";
+        } else {
+          buttonContainer.style.display = "block";
+        }
+      });
+    } else {
+      // Show the button container on hover for non-touch support (e.g. desktop)
+      codeBlock.parentNode.addEventListener("mouseenter", () => {
+        buttonContainer.style.display = "block";
+      });
+
+      codeBlock.parentNode.addEventListener("mouseleave", () => {
+        buttonContainer.style.display = "none";
+      });
+    }
+  });
+};
+
+const setupSidebarToggle = () => {
+  let body = document.body;
+  const sidebarOpen = "sidebar-open";
+  const removeOpenClass = () => {
+    body.classList.remove(sidebarOpen);
+    document.body
+      .querySelector("main")
+      .removeEventListener("click", removeOpenClass);
+  };
+  Array.from(document.body.querySelectorAll(".menu-toggle")).forEach(
+    (menuToggle) => {
+      menuToggle.addEventListener("click", (e) => {
+        body.classList.toggle(sidebarOpen);
+        e.stopPropagation();
+        if (body.classList.contains(sidebarOpen)) {
+          document.body.addEventListener("click", removeOpenClass);
+        }
+      });
+    },
+  );
+};
+
+setupSidebarNav();
+setupSearch();
+setupCodeBlocks();
+setupSidebarToggle();
diff --git a/docs/0.17.0/styles.css b/docs/0.17.0/styles.css
new file mode 100644
index 00000000..a188e7f0
--- /dev/null
+++ b/docs/0.17.0/styles.css
@@ -0,0 +1,1025 @@
+:root {
+  /* WCAG AAA Compliant colors - important that luminence (the l in hsl) is 18% for font colors against the bg's luminence of 96-97% when the font-size is at least 14pt */
+  --code-bg: hsl(262 33% 96% / 1);
+  --gray: hsl(0 0% 18% / 1);
+  --orange: hsl(25 100% 18% / 1);
+  --green: hsl(115 100% 18% / 1);
+  --cyan: hsl(190 100% 18% / 1);
+  --blue: #05006d;
+  --violet: #7c38f5;
+  --violet-bg: hsl(262.22deg 87.1% 96%);
+  --magenta: #a20031;
+  --link-hover-color: #333;
+  --link-color: var(--violet);
+  --code-link-color: var(--violet);
+  --text-color: #000;
+  --text-hover-color: var(--violet);
+  --body-bg-color: #ffffff;
+  --border-color: #717171;
+  --faded-color: #4c4c4c;
+  --font-sans: -apple-system, BlinkMacSystemFont, Roboto, Helvetica, Arial, sans-serif;
+  --font-mono: SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace;
+  --top-header-height: 67px;
+  --sidebar-width: clamp(280px, 25dvw, 500px);
+  --module-search-height: 56px;
+  --module-search-padding-height: 16px;
+  --module-search-form-padding-width: 20px;
+  --sidebar-bg-color: hsl(from var(--violet-bg) h calc(s * 1.05) calc(l * 0.95));
+}
+
+
+a {
+  color: var(--violet);
+}
+
+table tr th {
+  border: 1px solid var(--gray);
+}
+
+table tr th,
+table tr td {
+  padding: 6px 13px;
+}
+
+.logo svg {
+  height: 48px;
+  width: 48px;
+  fill: var(--violet);
+}
+
+.logo:hover {
+  text-decoration: none;
+}
+
+.logo svg:hover {
+  fill: var(--link-hover-color);
+}
+
+.pkg-full-name {
+  display: flex;
+  align-items: center;
+  font-size: 24px;
+  margin: 0 8px;
+  font-weight: normal;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+
+.entry-name {
+  white-space: pre-wrap;
+  font-family: var(--font-mono);
+  font-size: 16px;
+  font-weight: normal;
+  background-color: var(--violet-bg);
+  color: var(--text-color);
+  width: auto;
+  margin-top: 0;
+  margin-bottom: 24px;
+  padding: 12px 16px;
+  border-left: 4px solid var(--violet);
+  display: flex;
+}
+
+.entry-name strong {
+    color: var(--text-color);
+}
+
+.entry-name code {
+    background: none;
+}
+
+.entry-name:target {
+  background-color: var(--violet-bg);
+}
+
+.entry-name a {
+  visibility: hidden;
+  display: inline-block;
+  width: 18px;
+  height: 14px;
+  margin-left: -8px;
+  margin-right: 4px;
+  user-select: none;
+  color: var(--violet);
+}
+
+.entry-name:hover a {
+  visibility: visible;
+  text-decoration: none;
+}
+
+.entry-name:not(:hover) a {
+  visibility: hidden;
+  transition: visibility 2s;
+}
+
+.pkg-full-name a {
+  padding-top: 12px;
+  padding-bottom: 16px;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+a {
+  text-decoration: none;
+}
+
+a:hover,
+a:hover code {
+  text-decoration: underline;
+}
+
+.pkg-and-logo {
+  min-width: 0; /* necessary for text-overflow: ellipsis to work in descendants */
+  display: flex;
+  align-items: center;
+  justify-content: flex-start;
+  gap: 8px;
+  background-color: var(--violet-bg);
+  padding: 16px;
+}
+
+.pkg-and-logo a,
+.pkg-and-logo a:visited {
+  color: var(--violet);
+}
+
+.pkg-and-logo a:hover {
+  color: var(--link-hover-color);
+  text-decoration: none;
+}
+
+.search-button {
+  flex-shrink: 0; /* always shrink the package name before these; they have a relatively constrained length */
+  padding: 12px 18px;
+  margin-right: 42px;
+  display: none; /* only show this in the mobile view */
+}
+
+.version {
+  padding: 18px 10px;
+  min-width: 48px;
+  margin-right: 8px;
+}
+
+body {
+  display: grid;
+  grid-template-columns:
+      [sidebar] var(--sidebar-width)
+      [main-content] 1fr
+      [content-end];
+  grid-template-rows: 1fr;
+  height: 100dvh;
+  box-sizing: border-box;
+  margin: 0;
+  padding: 0;
+  font-family: var(--font-sans);
+  color: var(--text-color);
+  background-color: var(--body-bg-color);
+  overflow: hidden;
+}
+
+main {
+  grid-column-start: main-content;
+  grid-column-end: content-end;
+  box-sizing: border-box;
+  position: relative;
+  font-size: 14pt; /* This keeps links AAA compliant while still making the links distinctly colored. */
+  line-height: 1.85em;
+  margin-top: 2px;
+  padding: 16px;
+  padding-top: 0px;
+  /* necessary for text-overflow: ellipsis to work in descendants */
+  min-width: 0;
+  overflow-x: auto;
+  /* fixes issues with horizonatal scroll in cases where word is too long,
+  like in one of the examples at https://www.roc-lang.org/builtins/Num#Dec */
+  overflow-wrap: break-word;
+  overflow-y: auto;
+  display: grid;
+  --main-content-width: clamp(100px, calc(100% - 32px), 60ch); 
+  grid-template-columns: [main-start] minmax(16px,1fr) [main-content-start] var(--main-content-width) [main-content-end] minmax(16px,1fr) [main-end];
+  grid-template-rows: auto;
+  flex-direction: column;
+  scrollbar-color: var(--violet) var(--body-bg-color);
+  scrollbar-gutter: stable both-edges;
+  scroll-padding-top: calc(16px + 16px + 1lh + 16px + 16px);
+}
+
+main > * {
+    grid-column-start: main-content-start;
+    grid-column-end: main-content-end;
+}
+
+/* Module links on the package index page (/index.html) */
+.index-module-links {
+    margin: 0;
+    padding: 0;
+    list-style-type: none;
+}
+
+section {
+  padding: 0px 0px 16px 0px;
+  margin: 36px 0px;
+}
+
+section blockquote {
+  font-style: italic;
+  position: relative;
+  margin-left: 0;
+  margin-right: 0;
+}
+
+section blockquote:before {
+  content: "";
+  position: absolute;
+  top: 0;
+  right: 0;
+  width: 2px;
+  height: 100%;
+  background-color: var(--gray);
+}
+
+
+section > *:last-child {
+  margin-bottom: 0;
+}
+
+section h1,
+section h2,
+section h3,
+section h4,
+section p {
+padding: 0px 16px;
+}
+
+#sidebar-nav {
+    grid-column-start: sidebar;
+    grid-column-end: main-content;
+    position: relative;
+    display: grid;
+    grid-template-rows: min-content 1fr;
+    box-sizing: border-box;
+    width: 100%;
+    background-color: var(--sidebar-bg-color);
+    transition: all 1s linear;
+}
+
+#sidebar-nav .module-links-container {
+    position: relative;
+}
+
+
+#sidebar-nav .module-links {
+    position: absolute;
+    inset: 0;
+    overflow-y: auto;
+    overflow-x: hidden;
+    scrollbar-color: var(--violet) var(--sidebar-bg-color);
+    scrollbar-gutter: stable;
+    padding: 16px 8px;
+    transition: all .2s linear;
+}
+
+.top-header {
+  grid-column-start: sidebar;
+  grid-column-end: end;
+  grid-row-start: top-header;
+  grid-row-end: top-header;
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  flex-wrap: nowrap;
+  box-sizing: border-box;
+  font-family: var(--font-sans);
+  font-size: 20px;
+  height: 100%;
+  background-color: var(--violet-bg);
+  /* min-width must be set to something (even 0) for text-overflow: ellipsis to work in descendants, but we want this anyway. */
+  min-width: 1024px;
+}
+
+p {
+  overflow-wrap: break-word;
+  margin: 24px 0;
+}
+
+footer {
+  max-width: var(--main-content-max-width);
+  font-size: 14px;
+  box-sizing: border-box;
+  padding: 16px;
+}
+
+footer p {
+  display: inline-block;
+  margin-top: 0;
+  margin-bottom: 8px;
+}
+
+.content {
+  box-sizing: border-box;
+  display: flex;
+  flex-direction: row;
+  justify-content: space-between;
+}
+
+.sidebar-entry ul {
+  list-style-type: none;
+  margin: 0;
+}
+
+.sidebar-entry a {
+  box-sizing: border-box;
+  min-height: 40px;
+  min-width: 48px;
+  padding: 8px 16px;
+  font-family: var(--font-mono);
+}
+
+.sidebar-entry a,
+.sidebar-entry a:visited {
+  color: var(--text-color);
+}
+
+.sidebar-sub-entries {
+    font-size: 12pt;
+    display: none;
+}
+
+.active + .sidebar-sub-entries {
+    display: block;
+}
+
+.sidebar-sub-entries a {
+  display: block;
+  line-height: 24px;
+  width: 100%;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  margin-left: 20px;
+  padding-left: 27px;
+  border-left: 2px solid rgb(from var(--gray) r g b / .30);
+  white-space: nowrap;
+  display: flex;
+  align-items: center;
+}
+
+.sidebar-sub-entries a:first-child {
+    margin-top: 8px;
+    padding-top: 0;
+}
+
+.sidebar-sub-entries a:last-child {
+    margin-bottom: 8px;
+    padding-bottom: 0;
+}
+
+.sidebar-sub-entries a:hover {
+    border-left-color: rgb(from var(--violet) r g b / .60);
+    color: var(--violet);
+    text-decoration: none;
+}
+
+.module-name {
+  font-size: 40pt;
+  line-height: 1em;
+  font-family: var(--font-mono);
+  font-weight: bold;
+  margin-top: 36px;
+  margin-bottom: 16px;
+  color: var(--violet);
+}
+
+main h2 {
+    font-size: 28pt;
+}
+main h3 {
+    font-size: 24pt;
+}
+main h4 {
+    font-size: 20pt;
+}
+
+.module-name a,
+.module-name a:visited {
+color: inherit;
+}
+
+.module-name a:hover {
+  color: var(--link-hover-color);
+}
+
+a.sidebar-module-link {
+  box-sizing: border-box;
+  font-size: 14pt;
+  line-height: 24px;
+  font-family: var(--font-mono);
+  display: flex;
+  flex-direction: row-reverse;
+  justify-content: space-between;
+  align-items: center;
+  width: 100%;
+  padding: 0;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+
+.sidebar-module-link:hover {
+    text-decoration: none;
+
+    span:hover {
+        color: var(--violet);
+    }
+}
+
+.sidebar-module-link span {
+    display: inline-block;
+    flex-grow: 1;
+}
+
+.sidebar-module-link.active {
+  font-weight: bold;
+}
+
+.sidebar-module-link > .entry-toggle {
+    background-color: transparent;
+    appearance: none;
+    border: none;
+    color: transparent;
+    color: rgba(from var(--text-color) r g b / .6);
+    transition: all 80ms linear;
+    text-decoration: none;
+    font-size: 0.6rem;
+    cursor: pointer;
+    padding: 8px 16px;
+    
+    &:hover {
+        color: var(--violet);
+    }
+}
+
+:hover >  .entry-toggle {
+    color: var(--text-color);
+}
+
+.active .entry-toggle {
+    rotate: 90deg;
+}
+
+a,
+a:visited {
+  color: var(--link-color);
+}
+
+h3 {
+  font-size: 32px;
+  margin: 48px 0 24px 0;
+}
+
+h4 {
+  font-size: 24px;
+}
+
+.type-def {
+  font-size: 24px;
+  color: var(--link-color);
+}
+
+.code-snippet {
+  padding: 8px 16px;
+  display: block;
+  box-sizing: border-box;
+  font-family: var(--font-mono);
+  background-color: var(--code-bg);
+}
+
+code {
+  font-family: var(--font-mono);
+  color: var(--code-color);
+  background-color: var(--code-bg);
+  display: inline-block;
+}
+
+p code {
+  padding: 0 8px;
+}
+
+code a,
+a code {
+  text-decoration: none;
+  color: var(--code-link-color);
+  background: none;
+  padding: 0;
+  font-weight: bold; /* Important for AAA compliance while keeping color distinct */
+}
+
+code a:visited,
+a:visited code {
+  color: var(--code-link-color);
+}
+
+pre {
+  margin: 36px 0;
+  padding: 8px 16px;
+  box-sizing: border-box;
+  background-color: var(--code-bg);
+  position: relative;
+  word-wrap: normal;
+}
+
+pre>samp {
+    overflow-x: auto;
+    display: block;
+    scrollbar-color: var(--violet) var(--code-bg);
+    scrollbar-width: thin;
+    scrollbar-gutter: stable;
+}
+
+.hidden {
+  /* Use !important to win all specificity fights. */
+  display: none !important;
+}
+
+#module-search-form {
+  display: flex;
+  align-items: center;
+  align-content: center;
+  height: 100%;
+  position: sticky;
+  flex-grow: 1;
+  box-sizing: border-box;
+  padding-block: 16px;
+  background-color: var(--body-bg-color);
+  top: 0;
+  z-index: 1;
+}
+
+.menu-toggle {
+    display: none;
+    margin-right: 8px;
+    appearance: none;
+    background-color: transparent;
+    outline: none;
+    border: none;
+    color: var(--violet);
+    padding: 0;
+    cursor: pointer;
+}
+
+.menu-toggle svg {
+    height: 48px;
+    width: 48px;
+}
+
+
+#module-search,
+#module-search:focus {
+  opacity: 1;
+  padding: 12px 16px;
+  height: var(--module-search-height);
+}
+
+#module-search {
+  border-radius: 8px;
+  display: block;
+  position: relative;
+  box-sizing: border-box;
+  width: 100%;
+  box-sizing: border-box;
+  font-size: 16px;
+  line-height: 18px;
+  border: none;
+  color: var(--faded-color);
+  background-color: var(--body-bg-color);
+  font-family: var(--font-serif);
+  border: 2px solid var(--violet-bg);
+}
+
+@media (prefers-color-scheme: light) {
+    #module-search {
+        outline: 1px solid var(--gray);
+    }
+}
+
+#module-search::placeholder {
+  color: var(--faded-color);
+  opacity: 1;
+}
+
+#module-search:focus, #module-search:hover {
+  outline: 2px solid var(--violet);
+}
+
+#search-type-ahead {
+  font-family: var(--font-mono);
+  display: flex;
+  gap: 0px;
+  flex-direction: column;
+  position: absolute;
+  top: calc(var(--module-search-padding-height) + var(--module-search-height));
+  left: var(--module-search-form-padding-width);
+  width: calc(100% - 2 * var(--module-search-form-padding-width));
+  box-sizing: border-box;
+  z-index: 100;
+  background-color: var(--body-bg-color);
+  border-width: 1px;
+  border-style: solid;
+  border-color: var(--border-color);
+  list-style-type: none;
+  margin: 0;
+  padding: 0;
+}
+
+.search-icon {
+    fill: var(--faded-color);
+    pointer-events: none;
+    opacity: 0.6;
+    position: absolute;
+    right: 32px;
+}
+
+#search-type-ahead .type-ahead-link {
+  font-size: 1rem;
+  color: var(--text-color);
+  line-height: 2em;
+  position: relative;
+  box-sizing: border-box;
+  width: 100%;
+  height: 100%;
+  padding: 4px 8px;
+
+  max-height: 6em;
+  text-overflow: ellipsis;
+  overflow: hidden;
+  display: -webkit-box;
+  -webkit-line-clamp: 3;
+  -webkit-box-orient: vertical;
+
+  /* if it wraps, indent after the first line */
+  padding-left: calc(2em + 8px);
+  text-indent: -2em;
+
+
+  span {
+    margin: 0px;
+  }
+
+  .type-ahead-module-name, .type-ahead-def-name {
+    color: var(--violet);
+    font-size: 1rem;
+  }
+}
+
+#search-type-ahead li {
+  box-sizing: border-box;
+  position: relative;
+}
+
+#search-type-ahead a:focus {
+  outline: none;
+  background: var(--violet-bg);
+}
+
+#module-search-form:focus-within #search-label, #module-search-form:focus-within .search-icon {
+  display: none;
+}
+
+#search-label {
+  color: var(--faded-color);
+  box-sizing: border-box;
+  align-items: center;
+  font-size: 18px;
+  pointer-events: none;
+  position: absolute;
+  right: 72px;
+}
+
+#search-shortcut-key {
+  font-family: var(--font-mono);
+  border: 1px solid #666;
+  border-radius: 5px;
+  padding: 1px 3px 3px;
+  font-style: normal;
+  line-height: 15px;
+  pointer-events: none;
+}
+
+.builtins-tip {
+  padding: 1em;
+  font-style: italic;
+  line-height: 1.3em;
+}
+
+.module-header-container {
+  display: flex;
+  justify-content: space-between;
+  align-items: flex-end;
+  margin-bottom: 48px;
+}
+
+.llm-prompt-link {
+  flex-shrink: 0;
+}
+
+.module-name {
+    flex-grow: 1;
+}
+
+@media (prefers-color-scheme: dark) {
+  :root {
+      /* WCAG AAA Compliant colors */
+    --code-bg: hsl(228.95deg 37.25% 15%);
+    --gray: hsl(0 0% 70% / 1);
+    --orange: hsl(25 98% 70% / 1);
+    --green: hsl(115 40% 70% / 1);
+    --cyan: hsl(176 84% 70% / 1);
+    --blue: hsl(243 43% 80% / 1);
+    --violet: #caadfb;
+    --violet-bg: hsl(262 25% 15% / 1);
+    --magenta: hsl(348 79% 80% / 1);
+      --link-hover-color: #fff;
+
+      --link-color: var(--violet);
+      --code-link-color: var(--violet);
+      --text-color: #eaeaea;
+      --body-bg-color: hsl(from var(--violet-bg) h s calc(l * .5));
+      --border-color: var(--gray);
+      --code-color: #eeeeee;
+      --logo-solid: #8f8f8f;
+      --faded-color: #bbbbbb;
+      --sidebar-bg-color: hsl(from var(--violet-bg) h calc(s * 1.1) calc(l * 0.75));
+  }
+
+  html {
+      scrollbar-color: #8f8f8f #2f2f2f;
+  }
+}
+
+@media only screen and (max-width: 768px) {
+    :root {
+        --sidebar-width: clamp(280px, 50dvw, 385px);
+    }
+    body {
+        display: block;
+        overflow-y: auto;
+        overflow-x: hidden;
+    }
+
+    #sidebar-nav {
+        left: calc(-1 * var(--sidebar-width));
+        top: 0;
+        bottom: 0;
+        position: fixed;
+        z-index: 2;
+        transition: all .2s linear;
+    }
+
+    .entry-toggle {
+        height: 48px;
+        width: 48px;
+    }
+
+    body.sidebar-open #sidebar-nav {
+        left: 0;
+    }
+
+    main {
+        display: block;
+        margin: 0 16px;
+        --main-content-width: minmax(calc(100% - 32px), 60ch);
+    }
+
+    :root {
+        --top-header-height: 160px;
+    }
+
+    #search-shortcut-key, .header-start-extension, .header-end-extension, #search-label {
+        display: none;
+    }
+
+    #module-search-form {
+        padding: 16px 16px;
+        height: auto;
+        margin-bottom: 16px;
+        grid-column-start: main-content-start;
+        grid-column-end: main-content-end;
+        position: fixed;
+        left: 0;
+        right: 0;
+    }
+
+    .menu-toggle {
+        display: inline-block;
+        margin-right: 8px;
+        appearance: none;
+        background-color: transparent;
+        outline: none;
+        border: none;
+        color: var(--text-color);
+        padding: 0;
+    }
+
+    .menu-toggle svg {
+        height: 48px;
+        width: 48px;
+    }
+
+    /* Hide the Copy Link button on mobile. */
+    .entry-name a:first-of-type {
+        display: none;
+    }
+
+    .search-icon {
+        display: block; /* This is only visible in mobile. */
+        top: calc(1lh / 2 + 16px);
+    }
+
+    .top-header {
+        flex-direction: column;
+        height: auto;
+        justify-content: space-between;
+        /* min-width must be set to something (even 0) for text-overflow: ellipsis to work in descendants. */
+        min-width: 0;
+    }
+
+    .pkg-full-name {
+        font-size: 20px;
+        padding-bottom: 14px;
+    }
+
+    .pkg-full-name a {
+        vertical-align: middle;
+        padding: 18px 0;
+    }
+
+    .logo {
+        width: 50px;
+        height: 54px;
+    }
+
+    .version {
+        margin: 0;
+        font-weight: normal;
+        font-size: 18px;
+        padding-bottom: 16px;
+    }
+
+    .module-name {
+        font-size: 24px;
+        margin-top: 8px;
+        margin-bottom: 8px;
+        overflow: hidden;
+        text-overflow: ellipsis;
+    }
+
+    main {
+        padding: 18px;
+        font-size: 16px;
+        padding-top: calc(16px + 16px + 1lh + 16px);
+    }
+
+    #sidebar-nav {
+        margin-top: 0;
+        padding-left: 0;
+        width: var(--sidebar-width);
+    }
+
+    #sidebar-heading {
+        font-size: 24px;
+        margin: 16px;
+    }
+
+    h3 {
+        font-size: 18px;
+        margin: 0;
+        padding: 0;
+    }
+
+    h4 {
+        font-size: 16px;
+    }
+
+    body {
+        margin: 0;
+        min-width: 320px;
+        max-width: 100dvw;
+    }
+
+    .top-header-triangle {
+        display: none;
+    }
+
+    .pkg-and-logo {
+        padding-block: 4px;
+    }
+
+    .pkg-full-name {
+        flex-grow: 1;
+    }
+
+    .pkg-full-name a {
+        padding-top: 24px;
+        padding-bottom: 12px;
+    }
+}
+
+/* Comments `#` and Documentation comments `##` */
+samp .comment,
+code .comment {
+  color: var(--green);
+}
+
+/* Number, String, Tag literals */
+samp .storage.type,
+code .storage.type,
+samp .string,
+code .string,
+samp .string.begin,
+code .string.begin,
+samp .string.end,
+code .string.end,
+samp .constant,
+code .constant,
+samp .literal,
+code .literal {
+  color: var(--cyan);
+}
+
+/* Keywords and punctuation */
+samp .keyword,
+code .keyword,
+samp .punctuation.section,
+code .punctuation.section,
+samp .punctuation.separator,
+code .punctuation.separator,
+samp .punctuation.terminator,
+code .punctuation.terminator,
+samp .kw,
+code .kw {
+    color: var(--magenta);
+}
+
+/* Operators */
+samp .op,
+code .op,
+samp .keyword.operator,
+code .keyword.operator {
+  color: var(--orange);
+}
+
+/* Delimieters */
+samp .delimeter,
+code .delimeter {
+  color: var(--gray);
+}
+
+/* Variables modules and field names */
+samp .function,
+code .function,
+samp .meta.group,
+code .meta.group,
+samp .meta.block,
+code .meta.block,
+samp .lowerident,
+code .lowerident {
+  color: var(--blue);
+}
+
+/* Types, Tags, and Modules */
+samp .type,
+code .type,
+samp .meta.path,
+code .meta.path,
+samp .upperident,
+code .upperident {
+  color: var(--green);
+}
+
+samp .dim,
+code .dim {
+  opacity: 0.55;
+}
+
+.button-container {
+  position: absolute;
+  top: 0;
+  right: 0;
+}
+
+.copy-button {
+  background: var(--code-bg);
+  border: 1px solid var(--magenta);
+  color: var(--magenta);
+  display: inline-block;
+  cursor: pointer;
+  margin: 8px;
+}
+
+.copy-button:hover {
+  border-color: var(--link-hover-color);
+  color: var(--link-hover-color);
+}
diff --git a/docs/0.18.0/Arg/index.html b/docs/0.18.0/Arg/index.html
new file mode 100644
index 00000000..056dfb76
--- /dev/null
+++ b/docs/0.18.0/Arg/index.html
@@ -0,0 +1,295 @@
+
+
+
+
+    
+    Arg - Documentation
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+
+
+
+    
+    
+
+ + + + + + + + + + +
+

Arg

+ +Arg

An OS-aware 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) + ]

Unwrap an Arg into a raw, OS-aware numeric list.

+

This is a good way to pass Args to Roc packages.

+

+ +from_os_raw : + [ + Unix (List U8), + Windows (List U16) + ] + -> Arg

Wrap a raw, OS-aware numeric list into an Arg.

+

+ +display : Arg -> Str

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 �

+
+
+

Made by people who like to make nice things.

+
+
+ + + diff --git a/docs/0.18.0/Cmd/index.html b/docs/0.18.0/Cmd/index.html new file mode 100644 index 00000000..6dba5f7e --- /dev/null +++ b/docs/0.18.0/Cmd/index.html @@ -0,0 +1,360 @@ + + + + + + Cmd - Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + +
+

Cmd

+ +Cmd

Represents a command to be executed in a child process.

+

+ +Output

Represents the output of a command.

+

+ +new : Str -> Cmd

Create a new command to execute the given program in a child process.

+

+ +arg : Cmd, Str -> Cmd

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

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"]
+

+ +env : + Cmd, + Str, + Str + -> Cmd

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

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

Clear all environment variables, and prevent inheriting from parent, only +the environment variables provided to command are available to the child.

+
# Represents "env" with only "FOO" environment variable set
+Cmd.new "env"
+|> Cmd.clear_envs
+|> Cmd.env "FOO" "BAR"
+

+ +output! : Cmd => Output

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.

+
+

+ +status! : Cmd => Result I32 [CmdStatusErr InternalIOErr.IOErr]

Execute command and inherit stdin, stdout and stderr from parent

+

+ +exec! : Str, List Str => Result {} [CmdStatusErr InternalIOErr.IOErr]

Execute command and inherit stdin, stdout and stderr from parent

+
# Call echo to print "hello world"
+Cmd.exec! "echo" ["hello world"]
+
+
+

Made by people who like to make nice things.

+
+ + + + diff --git a/docs/0.18.0/Dir/index.html b/docs/0.18.0/Dir/index.html new file mode 100644 index 00000000..792e53f9 --- /dev/null +++ b/docs/0.18.0/Dir/index.html @@ -0,0 +1,344 @@ + + + + + + Dir - Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + +
+

Dir

+ +IOErr

Tag union of possible errors when reading and writing a file or directory.

+
+

This is the same as File.IOErr.

+
+

+ +DirEntry : Path.DirEntry

Record which represents a directory

+
+

This is the same as Path.DirEntry.

+
+

+ +list! : Str => Result (List Path) [DirErr IOErr]

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]

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]

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 directory is not empty
  • +
  • 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]

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]

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.

+
+
+
+

Made by people who like to make nice things.

+
+
+ + + diff --git a/docs/0.18.0/Env/index.html b/docs/0.18.0/Env/index.html new file mode 100644 index 00000000..547eb154 --- /dev/null +++ b/docs/0.18.0/Env/index.html @@ -0,0 +1,350 @@ + + + + + + Env - Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + +
+

Env

+ +cwd! : {} => Result Path [CwdUnavailable]

Reads the current working directory +from the environment. File operations on relative Paths are relative to this directory.

+

+ +set_cwd! : Path => Result {} [InvalidCwd]

Sets the current working directory +in the environment. After changing it, file operations on relative Paths will be relative +to this directory.

+

+ +exe_path! : {} => Result Path [ExePathUnavailable]

Gets the path to the currently-running executable.

+

+ +var! : Str => Result Str [VarNotFound]

Reads the given environment variable.

+

If the value is invalid Unicode, the invalid parts will be replaced with the +Unicode replacement character ('�').

+

+ +decode! : + Str + => Result val + [ + VarNotFound, + DecodeErr DecodeError + ] + where val implements Decoding

Reads the given environment variable and attempts to decode it.

+

The type being decoded into will be determined by type inference. For example, +if this ends up being used like a Task 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
+getU16Var : Str -> Task U16 [VarNotFound, DecodeErr DecodeError] [Read [Env]]
+getU16Var = \var -> Env.decode! var
+

If NUM_THINGS=123 then getU16Var succeeds with the value of 123u16. +However if NUM_THINGS=123456789, then getU16Var will +fail with DecodeErr +because 123456789 is too large to fit in a U16.

+

+ +dict! : {} => Dict Str Str

Reads all the process's environment variables into a Dict.

+

If any key or value contains invalid Unicode, the Unicode replacement character +will be used in place of any parts of keys or values that are invalid Unicode.

+

+ +platform! : + {} + => + { + arch : ARCH, + os : OS + }

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

This uses rust's std::env::temp_dir()

+

!! 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.

+
+
+

Made by people who like to make nice things.

+
+
+ + + diff --git a/docs/0.18.0/File/index.html b/docs/0.18.0/File/index.html new file mode 100644 index 00000000..4683a0fc --- /dev/null +++ b/docs/0.18.0/File/index.html @@ -0,0 +1,487 @@ + + + + + + File - Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + +
+

File

+ +IOErr

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

Write data to a file.

+

First encode a val using a given fmt which implements the ability Encode.EncoderFormatting.

+

For example, suppose you have a Json.toCompactUtf8 which implements +Encode.EncoderFormatting. +You can use this to write JSON +data to a file like this:

+
# Writes `{"some":"json stuff"}` to the file `output.json`:
+File.write!
+    { some: "json stuff" }
+    (Path.from_str "output.json")
+    Json.toCompactUtf8
+

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]

Writes bytes to a file.

+
# Writes the bytes 1, 2, 3 to the file `myfile.dat`.
+File.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 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]

Writes a Str to a file, encoded as 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]

Deletes a file from the filesystem.

+

Performs a DeleteFile +on Windows and unlink 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! (Path.from_str "myfile.dat") [1, 2, 3]
+
+

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 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]

Reads all the bytes in a file.

+
# Read all the bytes in `myfile.txt`.
+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, you can use File.read 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 + ]

Reads a Str from a file containing UTF-8-encoded text.

+
# Reads UTF-8 encoded text into a Str from the file "myfile.txt"
+File.read_utf8! "myfile.txt"
+

This opens the file first and closes it after writing to it. +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 => Result {} [LinkErr IOErr]

Creates a new 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.

+
+

Path.hard_link! does the same thing, except it takes a Path instead of a Str.

+
+

+ +is_dir! : Str => Result Bool [PathErr IOErr]

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.

+
+

Path.is_dir! does the same thing, except it takes a Path instead of a Str.

+
+

+ +is_file! : Str => Result Bool [PathErr IOErr]

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.

+
+

Path.is_file! does the same thing, except it takes a Path instead of a Str.

+
+

+ +is_sym_link! : Str => Result Bool [PathErr IOErr]

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.

+
+

Path.is_sym_link! does the same thing, except it takes a Path instead of a Str.

+
+

+ +type! : + Str + => Result + [ + IsFile, + IsDir, + IsSymLink + ] [PathErr IOErr]

Return the type of the path if the path exists on disk. +This uses rust's std::path::is_symlink.

+
+

Path.type! does the same thing, except it takes a Path instead of a Str.

+
+

+ +Reader

+ +open_reader! : Str => Result Reader [GetFileReadErr Path IOErr]

Try to open a File.Reader for buffered (= part by part) reading given a path string. +See examples/file-read-buffered.roc for example usage.

+

This uses rust's std::io::BufReader.

+

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]

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 for example usage.

+

This uses rust's std::io::BufReader.

+

Use read_utf8! if you want to get the entire file contents at once.

+

+ +read_line! : Reader => Result (List U8) [FileReadErr Path IOErr]

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 for example usage.

+

This uses rust's BufRead::read_line.

+

Use read_utf8! if you want to get the entire file contents at once.

+
+
+

Made by people who like to make nice things.

+
+
+ + + diff --git a/docs/0.18.0/FileMetadata/index.html b/docs/0.18.0/FileMetadata/index.html new file mode 100644 index 00000000..cc873641 --- /dev/null +++ b/docs/0.18.0/FileMetadata/index.html @@ -0,0 +1,296 @@ + + + + + + FileMetadata - Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + +
+

FileMetadata

+ +FileMetadata

An opaque type that represents metadata about a file.

+

+ +bytes : FileMetadata -> U64

Returns the number of bytes in the associated file.

+

+ +is_readonly : FileMetadata -> Bool

Returns Bool.true if the associated file is read-only.

+

+ +type : + FileMetadata + -> + [ + File, + Dir, + Symlink + ]

Returns the type of the associated file.

+

+ +mode : + FileMetadata + -> + [ + Unix U32, + NonUnix + ]

Returns the mode of the associated file.

+
+
+

Made by people who like to make nice things.

+
+
+ + + diff --git a/docs/0.18.0/Http/index.html b/docs/0.18.0/Http/index.html new file mode 100644 index 00000000..7125cb7f --- /dev/null +++ b/docs/0.18.0/Http/index.html @@ -0,0 +1,336 @@ + + + + + + Http - Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + +
+

Http

+ +Method

Represents an HTTP method.

+

+ +Header

Represents an HTTP header e.g. Content-Type: application/json

+

+ +Request

Represents an HTTP request.

+

+ +Response

Represents an HTTP response.

+

+ +default_request : Request

A default Request value.

+
# GET "roc-lang.org"
+{ Http.default_request &
+    url: "https://www.roc-lang.org",
+}
+

+ +header : + ( + Str, + Str + ) + -> Header

An HTTP header for configuring requests.

+

See common headers here.

+

+ +send! : Request => Response

Send an HTTP request, succeeds with a value of Str or fails with an +[Err].

+
# Prints out the HTML of the Roc-lang website.
+response =
+    { Http.default_request & url: "https://www.roc-lang.org" }
+    |> Http.send!
+
+response.body
+|> Str.fromUtf8
+|> Result.withDefault "Invalid UTF-8"
+|> Stdout.line
+

+ +get! : Str, fmt => Result body [HttpDecodingFailed] + where body implements Decoding, fmt implements DecoderFormatting

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.toBytes {foo: "Hello Json!"} Json.utf8`
+{ foo } = Http.get! "http://localhost:8000" Json.utf8
+

+ +get_utf8! : Str => Result Str [BadBody Str]

+
+

Made by people who like to make nice things.

+
+
+ + + diff --git a/docs/0.18.0/Locale/index.html b/docs/0.18.0/Locale/index.html new file mode 100644 index 00000000..96b5be50 --- /dev/null +++ b/docs/0.18.0/Locale/index.html @@ -0,0 +1,264 @@ + + + + + + Locale - Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + +
+

Locale

+ +get! : {} => Result Str [NotAvailable]

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

Returns the preferred locales for the system or application.

+

The returned Str are BCP 47 language tags, like en-US or fr-CA.

+
+
+

Made by people who like to make nice things.

+
+
+ + + diff --git a/docs/0.18.0/Path/index.html b/docs/0.18.0/Path/index.html new file mode 100644 index 00000000..7e39ab95 --- /dev/null +++ b/docs/0.18.0/Path/index.html @@ -0,0 +1,600 @@ + + + + + + Path - Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + +
+

Path

+ +Path

Represents a path to a file or directory on the filesystem.

+

+ +DirEntry

Record which represents a directory

+
+

This is the same as Dir.DirEntry.

+
+

+ +IOErr

Tag union of possible errors when reading and writing a file or directory.

+
+

This is the same as File.Err.

+
+

+ +write! : + val, + Path, + fmt + => Result {} [FileWriteErr Path IOErr] + where val implements Encoding, fmt implements EncoderFormatting

Write data to a file.

+

First encode a val using a given fmt which implements the ability Encode.EncoderFormatting.

+

For example, suppose you have a Json.toCompactUtf8 which implements +Encode.EncoderFormatting. +You can use this to write 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.toCompactUtf8
+

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]

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]

Writes a Str to a file, encoded as 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

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

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.openPath) will fail.

+

+ +display : Path -> Str

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. +toInner : Path -> Str Str, Bytes (List U8) +Assumes a path is encoded as 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 +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 +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.

+

If you happen to know the Charset that was used to encode the path, you can use +toStrUsingCharset instead of display.

+

+ +is_dir! : Path => Result Bool [PathErr IOErr]

Returns true if the path exists on disk and is pointing at a directory. +Returns Task.ok false if the path exists and it is not a directory. If the path does not exist, +this function will return Task.err PathErr PathDoesNotExist.

+

This uses rust's std::path::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]

Returns true if the path exists on disk and is pointing at a regular file. +Returns Task.ok false if the path exists and it is not a file. If the path does not exist, +this function will return Task.err PathErr PathDoesNotExist.

+

This uses rust's std::path::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]

Returns true if the path exists on disk and is pointing at a symbolic link. +Returns Task.ok false if the path exists and it is not a symbolic link. If the path does not exist, +this function will return Task.err PathErr PathDoesNotExist.

+

This uses rust's std::path::is_symlink.

+
+

File.is_sym_link does the same thing, except it takes a Str instead of a Path.

+
+

+ +type! : + Path + => Result + [ + IsFile, + IsDir, + IsSymLink + ] [PathErr IOErr]

Return the type of the path if the path exists on disk.

+
+

File.type does the same thing, except it takes a Str instead of a Path.

+
+

+ +with_extension : Path, Str -> Path

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]

Deletes a file from the filesystem.

+

Performs a DeleteFile +on Windows and unlink 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") [1, 2, 3]
+
+

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 to it has been deleted.

+
+
+

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 + ]

Reads a Str from a file containing UTF-8-encoded text.

+
# Reads UTF-8 encoded text into a Str from the file "myfile.txt"
+Path.read_utf8 (Path.from_str "myfile.txt")
+

This opens the file first and closes it after writing to it. +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 does the same thing, except it takes a Str instead of a Path.

+
+

+ +read_bytes! : Path => Result (List U8) [FileReadErr Path IOErr]

Reads all the bytes in a file.

+
# Read all the bytes in `myfile.txt`.
+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, you can use Path.read instead.

+

File.read_bytes does the same thing, except it takes a Str instead of a Path.

+
+

+ +list_dir! : Path => Result (List Path) [DirErr IOErr]

Lists the files and directories inside the directory.

+
+

Dir.list does the same thing, except it takes a Str instead of a Path.

+
+

+ +delete_empty! : Path => Result {} [DirErr IOErr]

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 does the same thing, except it takes a Str instead of a Path.

+
+

+ +delete_all! : Path => Result {} [DirErr IOErr]

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 directory is not empty
  • +
  • the user lacks permission to remove the directory.
  • +
+
+

Dir.delete_all does the same thing, except it takes a Str instead of a Path.

+
+

+ +create_dir! : Path => Result {} [DirErr IOErr]

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 does the same thing, except it takes a Str instead of a Path.

+
+

+ +create_all! : Path => Result {} [DirErr IOErr]

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 does the same thing, except it takes a Str instead of a Path.

+
+

+ +hard_link! : Path => Result {} [LinkErr IOErr]

Creates a new 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.

+
+

File.hard_link! does the same thing, except it takes a Str instead of a Path.

+
+
+
+

Made by people who like to make nice things.

+
+
+ + + diff --git a/docs/0.18.0/Sleep/index.html b/docs/0.18.0/Sleep/index.html new file mode 100644 index 00000000..7792e59b --- /dev/null +++ b/docs/0.18.0/Sleep/index.html @@ -0,0 +1,256 @@ + + + + + + Sleep - Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + +
+

Sleep

+ +millis! : U64 => {}

Sleep for at least the given number of milliseconds. +This uses rust's std::thread::sleep.

+
+
+

Made by people who like to make nice things.

+
+
+ + + diff --git a/docs/0.18.0/Stderr/index.html b/docs/0.18.0/Stderr/index.html new file mode 100644 index 00000000..6f56a99e --- /dev/null +++ b/docs/0.18.0/Stderr/index.html @@ -0,0 +1,285 @@ + + + + + + Stderr - Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + +
+

Stderr

+ +IOErr

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]

Write the given string to standard error, +followed by a newline.

+
+

To write to stderr without the newline, see Stderr.write!.

+
+

+ +write! : Str => Result {} [StderrErr IOErr]

Write the given string to standard error.

+

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!.

+
+
+
+

Made by people who like to make nice things.

+
+
+ + + diff --git a/docs/0.18.0/Stdin/index.html b/docs/0.18.0/Stdin/index.html new file mode 100644 index 00000000..533630ee --- /dev/null +++ b/docs/0.18.0/Stdin/index.html @@ -0,0 +1,307 @@ + + + + + + Stdin - Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + +
+

Stdin

+ +IOErr

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 + ]

Read a line from standard input.

+
+

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 + ]

Read bytes from standard input. +‼️ This function can read no more than 16,384 bytes at a time. Use [readToEnd!] 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]

Read all bytes from standard input until EOF in this source.

+
+
+

Made by people who like to make nice things.

+
+
+ + + diff --git a/docs/0.18.0/Stdout/index.html b/docs/0.18.0/Stdout/index.html new file mode 100644 index 00000000..432d318b --- /dev/null +++ b/docs/0.18.0/Stdout/index.html @@ -0,0 +1,285 @@ + + + + + + Stdout - Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + +
+

Stdout

+ +IOErr

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]

Write the given string to standard output, +followed by a newline.

+
+

To write to stdout without the newline, see Stdout.write!.

+
+

+ +write! : Str => Result {} [StdoutErr IOErr]

Write the given string to standard output.

+

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!.

+
+
+
+

Made by people who like to make nice things.

+
+
+ + + diff --git a/docs/0.18.0/Tcp/index.html b/docs/0.18.0/Tcp/index.html new file mode 100644 index 00000000..4a8ea207 --- /dev/null +++ b/docs/0.18.0/Tcp/index.html @@ -0,0 +1,396 @@ + + + + + + Tcp - Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + +
+

Tcp

+ +Stream

Represents a TCP stream.

+

+ +ConnectErr

Represents errors that can occur when connecting to a remote host.

+

+ +StreamErr

Represents errors that can occur when performing a Task with a Stream.

+

+ +connect! : Str, U16 => Result Stream ConnectErr

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]

Read up to a number of bytes from the TCP stream.

+
# Read up to 64 bytes from the stream and convert to a Str
+received = File.read_up_to! stream 64
+Str.fromUtf8 received
+
+

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 + ]

Read an exact number of bytes or fail.

+
bytes = File.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]

Read until a delimiter or EOF is reached.

+
# Read until null terminator
+File.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 + ]

Read until a newline or EOF is reached.

+
# Read a line and then print it to `stdout`
+lineStr = File.read_line! stream
+Stdout.line lineStr
+

If found, the newline is included as the last character in the Str.

+

+ +write! : Stream, List U8 => Result {} [TcpWriteErr StreamErr]

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]

Writes a Str to a TCP stream, encoded as 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

Convert a ConnectErr to a Str you can print.

+
when err is
+    TcpPerfomErr (TcpConnectErr connectErr) ->
+        Stderr.line (Tcp.connect_err_to_str connectErr)
+

+ +stream_err_to_str : StreamErr -> Str

Convert a StreamErr to a Str you can print.

+
when err is
+    TcpPerformErr (TcpReadErr err) ->
+        errStr = Tcp.stream_err_to_str err
+        Stderr.line "Error while reading: $(errStr)"
+
+    TcpPerformErr (TcpWriteErr err) ->
+        errStr = Tcp.stream_err_to_str err
+        Stderr.line "Error while writing: $(errStr)"
+
+
+

Made by people who like to make nice things.

+
+
+ + + diff --git a/docs/0.18.0/Tty/index.html b/docs/0.18.0/Tty/index.html new file mode 100644 index 00000000..bc566830 --- /dev/null +++ b/docs/0.18.0/Tty/index.html @@ -0,0 +1,273 @@ + + + + + + Tty - Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + +
+

Tty

Provides functionality to work with the terminal

+

Note: we plan on moving this file away from basic-cli in the future, see github.com/roc-lang/basic-cli/issues/73

+

+ +enable_raw_mode! : {} => {}

Enable terminal raw mode which disables some default terminal bevahiour.

+

The following modes are disabled:

+
    +
  • Input will not be echo to the terminal screen
  • +
  • Input will not be buffered until Enter key is pressed
  • +
  • Input will not be line buffered (input sent byte-by-byte to input buffer)
  • +
  • Special keys like Backspace and CTRL+C will not be processed by terminal driver
  • +
+

Note: we plan on moving this function away from basic-cli in the future, see github.com/roc-lang/basic-cli/issues/73

+

+ +disable_raw_mode! : {} => {}

Revert terminal to default behaviour

+

Note: we plan on moving this function away from basic-cli in the future, see github.com/roc-lang/basic-cli/issues/73

+
+
+

Made by people who like to make nice things.

+
+
+ + + diff --git a/docs/0.18.0/Url/index.html b/docs/0.18.0/Url/index.html new file mode 100644 index 00000000..8bf91d2b --- /dev/null +++ b/docs/0.18.0/Url/index.html @@ -0,0 +1,448 @@ + + + + + + Url - Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + +
+

Url

+ +Url

A Uniform Resource Locator.

+

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

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.countUtf8Bytes function can be helpful in finding out how many bytes to reserve.

+

There is no Url.withCapacity 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

Create a Url without validating or 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

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

Percent-encodes a +path component +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

Adds a Str query parameter to the end of the Url.

+

The key and value both get percent-encoded.

+
# 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

Replaces the URL's query—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

Returns the URL's query—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

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

Returns the URL's fragment—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

Replaces the URL's fragment.

+

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

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

+
+

Made by people who like to make nice things.

+
+
+ + + diff --git a/docs/0.18.0/Utc/index.html b/docs/0.18.0/Utc/index.html new file mode 100644 index 00000000..dca41b2e --- /dev/null +++ b/docs/0.18.0/Utc/index.html @@ -0,0 +1,312 @@ + + + + + + Utc - Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + +
+

Utc

+ +Utc

Stores a timestamp as nanoseconds since UNIX EPOCH

+

+ +now! : {} => Utc

Duration since UNIX EPOCH

+

+ +to_millis_since_epoch : Utc -> I128

Convert Utc timestamp to milliseconds

+

+ +from_millis_since_epoch : I128 -> Utc

Convert milliseconds to Utc timestamp

+

+ +to_nanos_since_epoch : Utc -> I128

Convert Utc timestamp to nanoseconds

+

+ +from_nanos_since_epoch : I128 -> Utc

Convert nanoseconds to Utc timestamp

+

+ +delta_as_millis : Utc, Utc -> U128

Calculate milliseconds between two Utc timestamps

+

+ +delta_as_nanos : Utc, Utc -> U128

Calculate nanoseconds between two Utc timestamps

+

+ +to_iso_8601 : Utc -> Str

Convert Utc timestamp to ISO 8601 string +Example: 2023-11-14T23:39:39Z

+
+
+

Made by people who like to make nice things.

+
+
+ + + diff --git a/docs/0.18.0/index.html b/docs/0.18.0/index.html new file mode 100644 index 00000000..e1709493 --- /dev/null +++ b/docs/0.18.0/index.html @@ -0,0 +1,248 @@ + + + + + + - Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + +
+

Exposed Modules

LLM docs
+
+

Made by people who like to make nice things.

+
+
+ + + diff --git a/docs/0.18.0/llms.txt b/docs/0.18.0/llms.txt new file mode 100644 index 00000000..4c3ca09f --- /dev/null +++ b/docs/0.18.0/llms.txt @@ -0,0 +1,2492 @@ +# LLM Prompt for Documentation + +## Documentation + +### Path + +#### Path + +**Type Annotation** + +**Description** + +Represents a path to a file or directory on the filesystem. + +#### DirEntry + +**Type Annotation** + +**Description** + +Record which represents a directory + +> This is the same as [`Dir.DirEntry`](Dir#DirEntry). + +#### IOErr + +**Type Annotation** + +**Description** + +Tag union of possible errors when reading and writing a file or directory. + +> This is the same as [`File.Err`](File#Err). + +#### write! + +**Type Annotation** + +```roc + + 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.toCompactUtf8` 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.toCompactUtf8 +``` + +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! + +**Type Annotation** + +```roc +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! + +**Type Annotation** + +```roc +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 + +**Type Annotation** + +```roc +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 + +**Type Annotation** + +```roc +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.openPath`) will fail. + +#### display + +**Type Annotation** + +```roc +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]. +toInner : 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 +`toStrUsingCharset` instead of [display]. + +#### is_dir! + +**Type Annotation** + +```roc +Path => Result Bool [PathErr IOErr] +``` + +**Description** + +Returns true if the path exists on disk and is pointing at a directory. +Returns `Task.ok false` if the path exists and it is not a directory. If the path does not exist, +this function will return `Task.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! + +**Type Annotation** + +```roc +Path => Result Bool [PathErr IOErr] +``` + +**Description** + +Returns true if the path exists on disk and is pointing at a regular file. +Returns `Task.ok false` if the path exists and it is not a file. If the path does not exist, +this function will return `Task.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! + +**Type Annotation** + +```roc +Path => Result Bool [PathErr IOErr] +``` + +**Description** + +Returns true if the path exists on disk and is pointing at a symbolic link. +Returns `Task.ok false` if the path exists and it is not a symbolic link. If the path does not exist, +this function will return `Task.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]. + +#### type! + +**Type Annotation** + +```roc + + 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 + +**Type Annotation** + +```roc +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! + +**Type Annotation** + +```roc +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") [1, 2, 3] +``` + +> 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! + +**Type Annotation** + +```roc + + 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" +Path.read_utf8 (Path.from_str "myfile.txt") +``` + +This opens the file first and closes it after writing to it. +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! + +**Type Annotation** + +```roc +Path => Result (List U8) [FileReadErr Path IOErr] +``` + +**Description** + +Reads all the bytes in a file. + +``` +# Read all the bytes in `myfile.txt`. +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, you can use `Path.read` instead. +> +> [`File.read_bytes`](File#read_bytes!) does the same thing, except it takes a [Str] instead of a [Path]. + +#### list_dir! + +**Type Annotation** + +```roc +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! + +**Type Annotation** + +```roc +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! + +**Type Annotation** + +```roc +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 directory is not empty + - 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! + +**Type Annotation** + +```roc +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! + +**Type Annotation** + +```roc +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! + +**Type Annotation** + +```roc +Path => Result {} [LinkErr IOErr] +``` + +**Description** + +Creates a new 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]. + +### Arg + +#### Arg + +**Type Annotation** + +**Description** + +An OS-aware 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 + +**Type Annotation** + +```roc + + 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 + +**Type Annotation** + +```roc + + [ + Unix (List U8), + Windows (List U16) + ] + -> Arg +``` + +**Description** + +Wrap a raw, OS-aware numeric list into an [Arg]. + +#### display + +**Type Annotation** + +```roc +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 + +#### IOErr + +**Type Annotation** + +**Description** + +Tag union of possible errors when reading and writing a file or directory. + +> This is the same as [`File.IOErr`](File#IOErr). + +#### DirEntry + +**Type Annotation** + +```roc +Path.DirEntry +``` + +**Description** + +Record which represents a directory + +> This is the same as [`Path.DirEntry`](Path#DirEntry). + +#### list! + +**Type Annotation** + +```roc +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! + +**Type Annotation** + +```roc +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! + +**Type Annotation** + +```roc +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 directory is not empty + - 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! + +**Type Annotation** + +```roc +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! + +**Type Annotation** + +```roc +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! + +**Type Annotation** + +```roc +{} => 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! + +**Type Annotation** + +```roc +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! + +**Type Annotation** + +```roc +{} => Result Path [ExePathUnavailable] +``` + +**Description** + +Gets the path to the currently-running executable. + +#### var! + +**Type Annotation** + +```roc +Str => Result Str [VarNotFound] +``` + +**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! + +**Type Annotation** + +```roc + + Str + => Result val + [ + VarNotFound, + DecodeErr DecodeError + ] + where val implements Decoding +``` + +**Description** + +Reads the given environment variable and attempts to decode it. + +The type being decoded into will be determined by type inference. For example, +if this ends up being used like a `Task 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 +getU16Var : Str -> Task U16 [VarNotFound, DecodeErr DecodeError] [Read [Env]] +getU16Var = \var -> Env.decode! var +``` + +If `NUM_THINGS=123` then `getU16Var` succeeds with the value of `123u16`. +However if `NUM_THINGS=123456789`, then `getU16Var` 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! + +**Type Annotation** + +```roc +{} => 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! + +**Type Annotation** + +```roc + + {} + => + { + 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! + +**Type Annotation** + +```roc +{} => 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 + +#### IOErr + +**Type Annotation** + +**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! + +**Type Annotation** + +```roc + + 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.toCompactUtf8` 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" } + (Path.from_str "output.json") + Json.toCompactUtf8 +``` + +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! + +**Type Annotation** + +```roc +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] (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 [File.write!] instead. +> +> [Path.write_bytes!] does the same thing, except it takes a [Path] instead of a [Str]. + +#### write_utf8! + +**Type Annotation** + +```roc +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! + +**Type Annotation** + +```roc +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! (Path.from_str "myfile.dat") [1, 2, 3] +``` + +> 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! + +**Type Annotation** + +```roc +Str => Result (List U8) [FileReadErr Path IOErr] +``` + +**Description** + +Reads all the bytes in a file. + +``` +# Read all the bytes in `myfile.txt`. +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, you can use `File.read` instead. +> +> [Path.read_bytes!] does the same thing, except it takes a [Path] instead of a [Str]. + +#### read_utf8! + +**Type Annotation** + +```roc + + 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" +File.read_utf8! "myfile.txt" +``` + +This opens the file first and closes it after writing to it. +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! + +**Type Annotation** + +```roc +Str => Result {} [LinkErr IOErr] +``` + +**Description** + +Creates a new 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! + +**Type Annotation** + +```roc +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! + +**Type Annotation** + +```roc +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! + +**Type Annotation** + +```roc +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]. + +#### type! + +**Type Annotation** + +```roc + + 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]. + +#### Reader + +**Type Annotation** + +#### open_reader! + +**Type Annotation** + +```roc +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! + +**Type Annotation** + +```roc +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! + +**Type Annotation** + +```roc +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. + +### FileMetadata + +#### FileMetadata + +**Type Annotation** + +**Description** + +An opaque type that represents metadata about a file. + +#### bytes + +**Type Annotation** + +```roc +FileMetadata -> U64 +``` + +**Description** + +Returns the number of bytes in the associated file. + +#### is_readonly + +**Type Annotation** + +```roc +FileMetadata -> Bool +``` + +**Description** + +Returns [Bool.true] if the associated file is read-only. + +#### type + +**Type Annotation** + +```roc + + FileMetadata + -> + [ + File, + Dir, + Symlink + ] +``` + +**Description** + +Returns the type of the associated file. + +#### mode + +**Type Annotation** + +```roc + + FileMetadata + -> + [ + Unix U32, + NonUnix + ] +``` + +**Description** + +Returns the mode of the associated file. + +### Http + +#### Method + +**Type Annotation** + +**Description** + +Represents an HTTP method. + +#### Header + +**Type Annotation** + +**Description** + +Represents an HTTP header e.g. `Content-Type: application/json` + +#### Request + +**Type Annotation** + +**Description** + +Represents an HTTP request. + +#### Response + +**Type Annotation** + +**Description** + +Represents an HTTP response. + +#### default_request + +**Type Annotation** + +```roc +Request +``` + +**Description** + +A default [Request] value. + +``` +# GET "roc-lang.org" +{ Http.default_request & + url: "https://www.roc-lang.org", +} +``` + + +#### header + +**Type Annotation** + +```roc + + ( + Str, + Str + ) + -> Header +``` + +**Description** + +An HTTP header for configuring requests. + +See common headers [here](https://en.wikipedia.org/wiki/List_of_HTTP_header_fields). + + +#### send! + +**Type Annotation** + +```roc +Request => Response +``` + +**Description** + +Send an HTTP request, succeeds with a value of [Str] or fails with an +[Err]. + +``` +# Prints out the HTML of the Roc-lang website. +response = + { Http.default_request & url: "https://www.roc-lang.org" } + |> Http.send! + +response.body +|> Str.fromUtf8 +|> Result.withDefault "Invalid UTF-8" +|> Stdout.line +``` + +#### get! + +**Type Annotation** + +```roc +Str, fmt => Result body [HttpDecodingFailed] + 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.toBytes {foo: "Hello Json!"} Json.utf8` +{ foo } = Http.get! "http://localhost:8000" Json.utf8 +``` + +#### get_utf8! + +**Type Annotation** + +```roc +Str => Result Str [BadBody Str] +``` + +### Stderr + +#### IOErr + +**Type Annotation** + +**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! + +**Type Annotation** + +```roc +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! + +**Type Annotation** + +```roc +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!]. + +### Stdin + +#### IOErr + +**Type Annotation** + +**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! + +**Type Annotation** + +```roc + + {} + => 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! + +**Type Annotation** + +```roc + + {} + => 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 [readToEnd!] 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! + +**Type Annotation** + +```roc +{} => Result (List U8) [StdinErr IOErr] +``` + +**Description** + +Read all bytes from [standard input](https://en.wikipedia.org/wiki/Standard_streams#Standard_input_(stdin)) until EOF in this source. + +### Stdout + +#### IOErr + +**Type Annotation** + +**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! + +**Type Annotation** + +```roc +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! + +**Type Annotation** + +```roc +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!]. + +### Tcp + +#### Stream + +**Type Annotation** + +**Description** + +Represents a TCP stream. + +#### ConnectErr + +**Type Annotation** + +**Description** + +Represents errors that can occur when connecting to a remote host. + +#### StreamErr + +**Type Annotation** + +**Description** + +Represents errors that can occur when performing a [Task] with a [Stream]. + +#### connect! + +**Type Annotation** + +```roc +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! + +**Type Annotation** + +```roc +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 and convert to a Str +received = File.read_up_to! stream 64 +Str.fromUtf8 received +``` + +> To read an exact number of bytes or fail, you can use [Tcp.read_exactly!] instead. + +#### read_exactly! + +**Type Annotation** + +```roc + + Stream, + U64 + => Result (List U8) + [ + TcpReadErr StreamErr, + TcpUnexpectedEOF + ] +``` + +**Description** + +Read an exact number of bytes or fail. + +``` +bytes = File.read_exactly!? stream 64 +``` + +`TcpUnexpectedEOF` is returned if the stream ends before the specfied number of bytes is reached. + + +#### read_until! + +**Type Annotation** + +```roc +Stream, U8 => Result (List U8) [TcpReadErr StreamErr] +``` + +**Description** + +Read until a delimiter or EOF is reached. + +``` +# Read until null terminator +File.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! + +**Type Annotation** + +```roc + + Stream + => Result Str + [ + TcpReadErr StreamErr, + TcpReadBadUtf8 + ] +``` + +**Description** + +Read until a newline or EOF is reached. + +``` +# Read a line and then print it to `stdout` +lineStr = File.read_line! stream +Stdout.line lineStr +``` + +If found, the newline is included as the last character in the [Str]. + + +#### write! + +**Type Annotation** + +```roc +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! + +**Type Annotation** + +```roc +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 + +**Type Annotation** + +```roc +ConnectErr -> Str +``` + +**Description** + +Convert a [ConnectErr] to a [Str] you can print. + +``` +when err is + TcpPerfomErr (TcpConnectErr connectErr) -> + Stderr.line (Tcp.connect_err_to_str connectErr) +``` + + +#### stream_err_to_str + +**Type Annotation** + +```roc +StreamErr -> Str +``` + +**Description** + +Convert a [StreamErr] to a [Str] you can print. + +``` +when err is + TcpPerformErr (TcpReadErr err) -> + errStr = Tcp.stream_err_to_str err + Stderr.line "Error while reading: $(errStr)" + + TcpPerformErr (TcpWriteErr err) -> + errStr = Tcp.stream_err_to_str err + Stderr.line "Error while writing: $(errStr)" +``` + + +### Url + +#### Url + +**Type Annotation** + +**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 + +**Type Annotation** + +```roc +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.countUtf8Bytes](https://www.roc-lang.org/builtins/Str#countUtf8Bytes) function can be helpful in finding out how many bytes to reserve. + +There is no `Url.withCapacity` 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 + +**Type Annotation** + +```roc +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 + +**Type Annotation** + +```roc +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 + +**Type Annotation** + +```roc +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 + +**Type Annotation** + +```roc + + 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 + +**Type Annotation** + +```roc +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 + +**Type Annotation** + +```roc +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 + +**Type Annotation** + +```roc +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 + +**Type Annotation** + +```roc +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 + +**Type Annotation** + +```roc +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 + +**Type Annotation** + +```roc +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 + +**Type Annotation** + +```roc +Url -> Dict Str Str +``` + +### Utc + +#### Utc + +**Type Annotation** + +**Description** + +Stores a timestamp as nanoseconds since UNIX EPOCH + +#### now! + +**Type Annotation** + +```roc +{} => Utc +``` + +**Description** + +Duration since UNIX EPOCH + +#### to_millis_since_epoch + +**Type Annotation** + +```roc +Utc -> I128 +``` + +**Description** + +Convert Utc timestamp to milliseconds + +#### from_millis_since_epoch + +**Type Annotation** + +```roc +I128 -> Utc +``` + +**Description** + +Convert milliseconds to Utc timestamp + +#### to_nanos_since_epoch + +**Type Annotation** + +```roc +Utc -> I128 +``` + +**Description** + +Convert Utc timestamp to nanoseconds + +#### from_nanos_since_epoch + +**Type Annotation** + +```roc +I128 -> Utc +``` + +**Description** + +Convert nanoseconds to Utc timestamp + +#### delta_as_millis + +**Type Annotation** + +```roc +Utc, Utc -> U128 +``` + +**Description** + +Calculate milliseconds between two Utc timestamps + +#### delta_as_nanos + +**Type Annotation** + +```roc +Utc, Utc -> U128 +``` + +**Description** + +Calculate nanoseconds between two Utc timestamps + +#### to_iso_8601 + +**Type Annotation** + +```roc +Utc -> Str +``` + +**Description** + +Convert Utc timestamp to ISO 8601 string +Example: 2023-11-14T23:39:39Z + +### Sleep + +#### millis! + +**Type Annotation** + +```roc +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 + +#### Cmd + +**Type Annotation** + +**Description** + +Represents a command to be executed in a child process. + +#### Output + +**Type Annotation** + +**Description** + +Represents the output of a command. + +#### new + +**Type Annotation** + +```roc +Str -> Cmd +``` + +**Description** + +Create a new command to execute the given program in a child process. + +#### arg + +**Type Annotation** + +```roc +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 + +**Type Annotation** + +```roc +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"] +``` + + +#### env + +**Type Annotation** + +```roc + + 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 + +**Type Annotation** + +```roc + + 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 + +**Type Annotation** + +```roc +Cmd -> Cmd +``` + +**Description** + +Clear all environment variables, and prevent inheriting from parent, only +the environment variables provided to command are available to the child. + +``` +# Represents "env" with only "FOO" environment variable set +Cmd.new "env" +|> Cmd.clear_envs +|> Cmd.env "FOO" "BAR" +``` + + +#### output! + +**Type Annotation** + +```roc +Cmd => Output +``` + +**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. + + +#### status! + +**Type Annotation** + +```roc +Cmd => Result I32 [CmdStatusErr InternalIOErr.IOErr] +``` + +**Description** + +Execute command and inherit stdin, stdout and stderr from parent + + +#### exec! + +**Type Annotation** + +```roc +Str, List Str => Result {} [CmdStatusErr InternalIOErr.IOErr] +``` + +**Description** + +Execute command and inherit stdin, stdout and stderr from parent + +``` +# Call echo to print "hello world" +Cmd.exec! "echo" ["hello world"] +``` + +### Tty + +#### enable_raw_mode! + +**Type Annotation** + +```roc +{} => {} +``` + +**Description** + +Enable terminal raw mode which disables some default terminal bevahiour. + +The following modes are disabled: +- Input will not be echo to the terminal screen +- Input will not be buffered until Enter key is pressed +- Input will not be line buffered (input sent byte-by-byte to input buffer) +- Special keys like Backspace and CTRL+C will not be processed by terminal driver + +Note: we plan on moving this function away from basic-cli in the future, see github.com/roc-lang/basic-cli/issues/73 + + +#### disable_raw_mode! + +**Type Annotation** + +```roc +{} => {} +``` + +**Description** + +Revert terminal to default behaviour + +Note: we plan on moving this function away from basic-cli in the future, see github.com/roc-lang/basic-cli/issues/73 + + +### Locale + +#### get! + +**Type Annotation** + +```roc +{} => 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! + +**Type Annotation** + +```roc +{} => 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`. + diff --git a/docs/0.18.0/search.js b/docs/0.18.0/search.js new file mode 100644 index 00000000..d21d3eff --- /dev/null +++ b/docs/0.18.0/search.js @@ -0,0 +1,284 @@ +const toggleSidebarEntryActive = (moduleName) => { + let sidebar = document.getElementById("sidebar-nav"); + + if (sidebar != null) { + // Un-hide everything + sidebar.querySelectorAll(".sidebar-entry").forEach((entry) => { + let entryName = entry.querySelector(".sidebar-module-link").dataset + .moduleName; + if (moduleName === entryName) { + entry.firstChild.classList.toggle("active"); + } + }); + } +}; + +const setupSidebarNav = () => { + // Re-hide all the sub-entries except for those of the current module + let currentModuleName = document.querySelector(".module-name").textContent; + toggleSidebarEntryActive(currentModuleName); + + document.querySelectorAll(".entry-toggle").forEach((el) => { + el.addEventListener("click", (e) => { + e.preventDefault(); + e.stopImmediatePropagation(); + const moduleName = e.target.parentElement.dataset.moduleName; + toggleSidebarEntryActive(moduleName); + }); + }); +}; + +const setupSearch = () => { + let searchTypeAhead = document.getElementById("search-type-ahead"); + let searchBox = document.getElementById("module-search"); + let searchForm = document.getElementById("module-search-form"); + let topSearchResultListItem = undefined; + + // Hide the results whenever anyone clicks outside the search results. + window.addEventListener("click", function (event) { + if (!searchForm?.contains(event.target)) { + searchTypeAhead.classList.add("hidden"); + } + }); + + if (searchBox != null) { + function searchKeyDown(event) { + switch (event.key) { + case "ArrowDown": { + event.preventDefault(); + + const focused = document.querySelector( + "#search-type-ahead > li:not([class*='hidden']) > a:focus", + ); + + // Find the next element to focus. + let nextToFocus = focused?.parentElement?.nextElementSibling; + + while ( + nextToFocus != null && + nextToFocus.classList.contains("hidden") + ) { + nextToFocus = nextToFocus.nextElementSibling; + } + + if (nextToFocus == null) { + // If none of the links were focused, focus the first one. + // Also if we've reached the last one in the list, wrap around to the first. + document + .querySelector( + "#search-type-ahead > li:not([class*='hidden']) > a", + ) + ?.focus(); + } else { + nextToFocus.querySelector("a").focus(); + } + + break; + } + case "ArrowUp": { + event.preventDefault(); + + const focused = document.querySelector( + "#search-type-ahead > li:not([class*='hidden']) > a:focus", + ); + + // Find the next element to focus. + let nextToFocus = focused?.parentElement?.previousElementSibling; + while ( + nextToFocus != null && + nextToFocus.classList.contains("hidden") + ) { + nextToFocus = nextToFocus.previousElementSibling; + } + + if (nextToFocus == null) { + // If none of the links were focused, or we're at the first one, focus the search box again. + searchBox?.focus(); + } else { + // If one of the links was focused, focus the previous one + nextToFocus.querySelector("a").focus(); + } + + break; + } + } + } + + searchForm.addEventListener("keydown", searchKeyDown); + + function search() { + topSearchResultListItem = undefined; + let text = searchBox.value.toLowerCase(); // Search is case-insensitive. + + if (text === "") { + searchTypeAhead.classList.add("hidden"); + } else { + let totalResults = 0; + // Firsttype-ahead-signature", show/hide all the sub-entries within each module (top-level functions etc.) + searchTypeAhead.querySelectorAll("li").forEach((entry) => { + const entryModule = entry + .querySelector(".type-ahead-module-name") + .textContent.toLowerCase(); + const entryName = entry + .querySelector(".type-ahead-def-name") + .textContent.toLowerCase(); + const entrySignature = entry + .querySelector(".type-ahead-signature") + ?.textContent?.toLowerCase() + ?.replace(/\s+/g, ""); + + const qualifiedEntryName = `${entryModule}.${entryName}`; + + if ( + qualifiedEntryName.includes(text) || + entrySignature?.includes(text.replace(/\s+/g, "")) + ) { + totalResults++; + entry.classList.remove("hidden"); + if (topSearchResultListItem === undefined) { + topSearchResultListItem = entry; + } + } else { + entry.classList.add("hidden"); + } + }); + if (totalResults < 1) { + searchTypeAhead.classList.add("hidden"); + } else { + searchTypeAhead.classList.remove("hidden"); + } + } + } + + searchBox.addEventListener("input", search); + + search(); + + function searchSubmit(e) { + // pick the top result if the user submits search form + e.preventDefault(); + if (topSearchResultListItem !== undefined) { + let topSearchResultListItemAnchor = + topSearchResultListItem.querySelector("a"); + if (topSearchResultListItemAnchor !== null) { + topSearchResultListItemAnchor.click(); + } + } + } + searchForm.addEventListener("submit", searchSubmit); + + // Capture '/' keypress for quick search + window.addEventListener("keyup", (e) => { + if (e.key === "s" && document.activeElement !== searchBox) { + e.preventDefault(); + searchBox.focus(); + searchBox.value = ""; + } + + if (e.key === "Escape") { + if (document.activeElement === searchBox) { + // De-focus and clear input box + searchBox.value = ""; + searchBox.blur(); + } else { + // Hide the search results + searchTypeAhead.classList.add("hidden"); + + if (searchTypeAhead.contains(document.activeElement)) { + searchBox.focus(); + } + } + } + }); + } +}; + +const isTouchSupported = () => { + try { + document.createEvent("TouchEvent"); + return true; + } catch (e) { + return false; + } +}; + +const setupCodeBlocks = () => { + // Select all elements that are children of
 elements
+  const codeBlocks = document.querySelectorAll("pre > samp");
+
+  // Iterate over each code block
+  codeBlocks.forEach((codeBlock) => {
+    // Create a "Copy" button
+    const copyButton = document.createElement("button");
+    copyButton.classList.add("copy-button");
+    copyButton.textContent = "Copy";
+
+    // Add event listener to copy button
+    copyButton.addEventListener("click", () => {
+      const codeText = codeBlock.innerText;
+      navigator.clipboard.writeText(codeText);
+      copyButton.textContent = "Copied!";
+      copyButton.classList.add("copy-button-copied");
+      copyButton.addEventListener("mouseleave", () => {
+        copyButton.textContent = "Copy";
+        copyButton.classList.remove("copy-button-copied");
+      });
+    });
+
+    // Create a container for the copy button and append it to the document
+    const buttonContainer = document.createElement("div");
+    buttonContainer.classList.add("button-container");
+    buttonContainer.appendChild(copyButton);
+    codeBlock.parentNode.insertBefore(buttonContainer, codeBlock);
+
+    // Hide the button container by default
+    buttonContainer.style.display = "none";
+
+    if (isTouchSupported()) {
+      // Show the button container on click for touch support (e.g. mobile)
+      document.addEventListener("click", (event) => {
+        if (event.target.closest("pre > samp") !== codeBlock) {
+          buttonContainer.style.display = "none";
+        } else {
+          buttonContainer.style.display = "block";
+        }
+      });
+    } else {
+      // Show the button container on hover for non-touch support (e.g. desktop)
+      codeBlock.parentNode.addEventListener("mouseenter", () => {
+        buttonContainer.style.display = "block";
+      });
+
+      codeBlock.parentNode.addEventListener("mouseleave", () => {
+        buttonContainer.style.display = "none";
+      });
+    }
+  });
+};
+
+const setupSidebarToggle = () => {
+  let body = document.body;
+  const sidebarOpen = "sidebar-open";
+  const removeOpenClass = () => {
+    body.classList.remove(sidebarOpen);
+    document.body
+      .querySelector("main")
+      .removeEventListener("click", removeOpenClass);
+  };
+  Array.from(document.body.querySelectorAll(".menu-toggle")).forEach(
+    (menuToggle) => {
+      menuToggle.addEventListener("click", (e) => {
+        body.classList.toggle(sidebarOpen);
+        e.stopPropagation();
+        if (body.classList.contains(sidebarOpen)) {
+          document.body.addEventListener("click", removeOpenClass);
+        }
+      });
+    },
+  );
+};
+
+setupSidebarNav();
+setupSearch();
+setupCodeBlocks();
+setupSidebarToggle();
diff --git a/docs/0.18.0/styles.css b/docs/0.18.0/styles.css
new file mode 100644
index 00000000..a188e7f0
--- /dev/null
+++ b/docs/0.18.0/styles.css
@@ -0,0 +1,1025 @@
+:root {
+  /* WCAG AAA Compliant colors - important that luminence (the l in hsl) is 18% for font colors against the bg's luminence of 96-97% when the font-size is at least 14pt */
+  --code-bg: hsl(262 33% 96% / 1);
+  --gray: hsl(0 0% 18% / 1);
+  --orange: hsl(25 100% 18% / 1);
+  --green: hsl(115 100% 18% / 1);
+  --cyan: hsl(190 100% 18% / 1);
+  --blue: #05006d;
+  --violet: #7c38f5;
+  --violet-bg: hsl(262.22deg 87.1% 96%);
+  --magenta: #a20031;
+  --link-hover-color: #333;
+  --link-color: var(--violet);
+  --code-link-color: var(--violet);
+  --text-color: #000;
+  --text-hover-color: var(--violet);
+  --body-bg-color: #ffffff;
+  --border-color: #717171;
+  --faded-color: #4c4c4c;
+  --font-sans: -apple-system, BlinkMacSystemFont, Roboto, Helvetica, Arial, sans-serif;
+  --font-mono: SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace;
+  --top-header-height: 67px;
+  --sidebar-width: clamp(280px, 25dvw, 500px);
+  --module-search-height: 56px;
+  --module-search-padding-height: 16px;
+  --module-search-form-padding-width: 20px;
+  --sidebar-bg-color: hsl(from var(--violet-bg) h calc(s * 1.05) calc(l * 0.95));
+}
+
+
+a {
+  color: var(--violet);
+}
+
+table tr th {
+  border: 1px solid var(--gray);
+}
+
+table tr th,
+table tr td {
+  padding: 6px 13px;
+}
+
+.logo svg {
+  height: 48px;
+  width: 48px;
+  fill: var(--violet);
+}
+
+.logo:hover {
+  text-decoration: none;
+}
+
+.logo svg:hover {
+  fill: var(--link-hover-color);
+}
+
+.pkg-full-name {
+  display: flex;
+  align-items: center;
+  font-size: 24px;
+  margin: 0 8px;
+  font-weight: normal;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+
+.entry-name {
+  white-space: pre-wrap;
+  font-family: var(--font-mono);
+  font-size: 16px;
+  font-weight: normal;
+  background-color: var(--violet-bg);
+  color: var(--text-color);
+  width: auto;
+  margin-top: 0;
+  margin-bottom: 24px;
+  padding: 12px 16px;
+  border-left: 4px solid var(--violet);
+  display: flex;
+}
+
+.entry-name strong {
+    color: var(--text-color);
+}
+
+.entry-name code {
+    background: none;
+}
+
+.entry-name:target {
+  background-color: var(--violet-bg);
+}
+
+.entry-name a {
+  visibility: hidden;
+  display: inline-block;
+  width: 18px;
+  height: 14px;
+  margin-left: -8px;
+  margin-right: 4px;
+  user-select: none;
+  color: var(--violet);
+}
+
+.entry-name:hover a {
+  visibility: visible;
+  text-decoration: none;
+}
+
+.entry-name:not(:hover) a {
+  visibility: hidden;
+  transition: visibility 2s;
+}
+
+.pkg-full-name a {
+  padding-top: 12px;
+  padding-bottom: 16px;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+a {
+  text-decoration: none;
+}
+
+a:hover,
+a:hover code {
+  text-decoration: underline;
+}
+
+.pkg-and-logo {
+  min-width: 0; /* necessary for text-overflow: ellipsis to work in descendants */
+  display: flex;
+  align-items: center;
+  justify-content: flex-start;
+  gap: 8px;
+  background-color: var(--violet-bg);
+  padding: 16px;
+}
+
+.pkg-and-logo a,
+.pkg-and-logo a:visited {
+  color: var(--violet);
+}
+
+.pkg-and-logo a:hover {
+  color: var(--link-hover-color);
+  text-decoration: none;
+}
+
+.search-button {
+  flex-shrink: 0; /* always shrink the package name before these; they have a relatively constrained length */
+  padding: 12px 18px;
+  margin-right: 42px;
+  display: none; /* only show this in the mobile view */
+}
+
+.version {
+  padding: 18px 10px;
+  min-width: 48px;
+  margin-right: 8px;
+}
+
+body {
+  display: grid;
+  grid-template-columns:
+      [sidebar] var(--sidebar-width)
+      [main-content] 1fr
+      [content-end];
+  grid-template-rows: 1fr;
+  height: 100dvh;
+  box-sizing: border-box;
+  margin: 0;
+  padding: 0;
+  font-family: var(--font-sans);
+  color: var(--text-color);
+  background-color: var(--body-bg-color);
+  overflow: hidden;
+}
+
+main {
+  grid-column-start: main-content;
+  grid-column-end: content-end;
+  box-sizing: border-box;
+  position: relative;
+  font-size: 14pt; /* This keeps links AAA compliant while still making the links distinctly colored. */
+  line-height: 1.85em;
+  margin-top: 2px;
+  padding: 16px;
+  padding-top: 0px;
+  /* necessary for text-overflow: ellipsis to work in descendants */
+  min-width: 0;
+  overflow-x: auto;
+  /* fixes issues with horizonatal scroll in cases where word is too long,
+  like in one of the examples at https://www.roc-lang.org/builtins/Num#Dec */
+  overflow-wrap: break-word;
+  overflow-y: auto;
+  display: grid;
+  --main-content-width: clamp(100px, calc(100% - 32px), 60ch); 
+  grid-template-columns: [main-start] minmax(16px,1fr) [main-content-start] var(--main-content-width) [main-content-end] minmax(16px,1fr) [main-end];
+  grid-template-rows: auto;
+  flex-direction: column;
+  scrollbar-color: var(--violet) var(--body-bg-color);
+  scrollbar-gutter: stable both-edges;
+  scroll-padding-top: calc(16px + 16px + 1lh + 16px + 16px);
+}
+
+main > * {
+    grid-column-start: main-content-start;
+    grid-column-end: main-content-end;
+}
+
+/* Module links on the package index page (/index.html) */
+.index-module-links {
+    margin: 0;
+    padding: 0;
+    list-style-type: none;
+}
+
+section {
+  padding: 0px 0px 16px 0px;
+  margin: 36px 0px;
+}
+
+section blockquote {
+  font-style: italic;
+  position: relative;
+  margin-left: 0;
+  margin-right: 0;
+}
+
+section blockquote:before {
+  content: "";
+  position: absolute;
+  top: 0;
+  right: 0;
+  width: 2px;
+  height: 100%;
+  background-color: var(--gray);
+}
+
+
+section > *:last-child {
+  margin-bottom: 0;
+}
+
+section h1,
+section h2,
+section h3,
+section h4,
+section p {
+padding: 0px 16px;
+}
+
+#sidebar-nav {
+    grid-column-start: sidebar;
+    grid-column-end: main-content;
+    position: relative;
+    display: grid;
+    grid-template-rows: min-content 1fr;
+    box-sizing: border-box;
+    width: 100%;
+    background-color: var(--sidebar-bg-color);
+    transition: all 1s linear;
+}
+
+#sidebar-nav .module-links-container {
+    position: relative;
+}
+
+
+#sidebar-nav .module-links {
+    position: absolute;
+    inset: 0;
+    overflow-y: auto;
+    overflow-x: hidden;
+    scrollbar-color: var(--violet) var(--sidebar-bg-color);
+    scrollbar-gutter: stable;
+    padding: 16px 8px;
+    transition: all .2s linear;
+}
+
+.top-header {
+  grid-column-start: sidebar;
+  grid-column-end: end;
+  grid-row-start: top-header;
+  grid-row-end: top-header;
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  flex-wrap: nowrap;
+  box-sizing: border-box;
+  font-family: var(--font-sans);
+  font-size: 20px;
+  height: 100%;
+  background-color: var(--violet-bg);
+  /* min-width must be set to something (even 0) for text-overflow: ellipsis to work in descendants, but we want this anyway. */
+  min-width: 1024px;
+}
+
+p {
+  overflow-wrap: break-word;
+  margin: 24px 0;
+}
+
+footer {
+  max-width: var(--main-content-max-width);
+  font-size: 14px;
+  box-sizing: border-box;
+  padding: 16px;
+}
+
+footer p {
+  display: inline-block;
+  margin-top: 0;
+  margin-bottom: 8px;
+}
+
+.content {
+  box-sizing: border-box;
+  display: flex;
+  flex-direction: row;
+  justify-content: space-between;
+}
+
+.sidebar-entry ul {
+  list-style-type: none;
+  margin: 0;
+}
+
+.sidebar-entry a {
+  box-sizing: border-box;
+  min-height: 40px;
+  min-width: 48px;
+  padding: 8px 16px;
+  font-family: var(--font-mono);
+}
+
+.sidebar-entry a,
+.sidebar-entry a:visited {
+  color: var(--text-color);
+}
+
+.sidebar-sub-entries {
+    font-size: 12pt;
+    display: none;
+}
+
+.active + .sidebar-sub-entries {
+    display: block;
+}
+
+.sidebar-sub-entries a {
+  display: block;
+  line-height: 24px;
+  width: 100%;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  margin-left: 20px;
+  padding-left: 27px;
+  border-left: 2px solid rgb(from var(--gray) r g b / .30);
+  white-space: nowrap;
+  display: flex;
+  align-items: center;
+}
+
+.sidebar-sub-entries a:first-child {
+    margin-top: 8px;
+    padding-top: 0;
+}
+
+.sidebar-sub-entries a:last-child {
+    margin-bottom: 8px;
+    padding-bottom: 0;
+}
+
+.sidebar-sub-entries a:hover {
+    border-left-color: rgb(from var(--violet) r g b / .60);
+    color: var(--violet);
+    text-decoration: none;
+}
+
+.module-name {
+  font-size: 40pt;
+  line-height: 1em;
+  font-family: var(--font-mono);
+  font-weight: bold;
+  margin-top: 36px;
+  margin-bottom: 16px;
+  color: var(--violet);
+}
+
+main h2 {
+    font-size: 28pt;
+}
+main h3 {
+    font-size: 24pt;
+}
+main h4 {
+    font-size: 20pt;
+}
+
+.module-name a,
+.module-name a:visited {
+color: inherit;
+}
+
+.module-name a:hover {
+  color: var(--link-hover-color);
+}
+
+a.sidebar-module-link {
+  box-sizing: border-box;
+  font-size: 14pt;
+  line-height: 24px;
+  font-family: var(--font-mono);
+  display: flex;
+  flex-direction: row-reverse;
+  justify-content: space-between;
+  align-items: center;
+  width: 100%;
+  padding: 0;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+
+.sidebar-module-link:hover {
+    text-decoration: none;
+
+    span:hover {
+        color: var(--violet);
+    }
+}
+
+.sidebar-module-link span {
+    display: inline-block;
+    flex-grow: 1;
+}
+
+.sidebar-module-link.active {
+  font-weight: bold;
+}
+
+.sidebar-module-link > .entry-toggle {
+    background-color: transparent;
+    appearance: none;
+    border: none;
+    color: transparent;
+    color: rgba(from var(--text-color) r g b / .6);
+    transition: all 80ms linear;
+    text-decoration: none;
+    font-size: 0.6rem;
+    cursor: pointer;
+    padding: 8px 16px;
+    
+    &:hover {
+        color: var(--violet);
+    }
+}
+
+:hover >  .entry-toggle {
+    color: var(--text-color);
+}
+
+.active .entry-toggle {
+    rotate: 90deg;
+}
+
+a,
+a:visited {
+  color: var(--link-color);
+}
+
+h3 {
+  font-size: 32px;
+  margin: 48px 0 24px 0;
+}
+
+h4 {
+  font-size: 24px;
+}
+
+.type-def {
+  font-size: 24px;
+  color: var(--link-color);
+}
+
+.code-snippet {
+  padding: 8px 16px;
+  display: block;
+  box-sizing: border-box;
+  font-family: var(--font-mono);
+  background-color: var(--code-bg);
+}
+
+code {
+  font-family: var(--font-mono);
+  color: var(--code-color);
+  background-color: var(--code-bg);
+  display: inline-block;
+}
+
+p code {
+  padding: 0 8px;
+}
+
+code a,
+a code {
+  text-decoration: none;
+  color: var(--code-link-color);
+  background: none;
+  padding: 0;
+  font-weight: bold; /* Important for AAA compliance while keeping color distinct */
+}
+
+code a:visited,
+a:visited code {
+  color: var(--code-link-color);
+}
+
+pre {
+  margin: 36px 0;
+  padding: 8px 16px;
+  box-sizing: border-box;
+  background-color: var(--code-bg);
+  position: relative;
+  word-wrap: normal;
+}
+
+pre>samp {
+    overflow-x: auto;
+    display: block;
+    scrollbar-color: var(--violet) var(--code-bg);
+    scrollbar-width: thin;
+    scrollbar-gutter: stable;
+}
+
+.hidden {
+  /* Use !important to win all specificity fights. */
+  display: none !important;
+}
+
+#module-search-form {
+  display: flex;
+  align-items: center;
+  align-content: center;
+  height: 100%;
+  position: sticky;
+  flex-grow: 1;
+  box-sizing: border-box;
+  padding-block: 16px;
+  background-color: var(--body-bg-color);
+  top: 0;
+  z-index: 1;
+}
+
+.menu-toggle {
+    display: none;
+    margin-right: 8px;
+    appearance: none;
+    background-color: transparent;
+    outline: none;
+    border: none;
+    color: var(--violet);
+    padding: 0;
+    cursor: pointer;
+}
+
+.menu-toggle svg {
+    height: 48px;
+    width: 48px;
+}
+
+
+#module-search,
+#module-search:focus {
+  opacity: 1;
+  padding: 12px 16px;
+  height: var(--module-search-height);
+}
+
+#module-search {
+  border-radius: 8px;
+  display: block;
+  position: relative;
+  box-sizing: border-box;
+  width: 100%;
+  box-sizing: border-box;
+  font-size: 16px;
+  line-height: 18px;
+  border: none;
+  color: var(--faded-color);
+  background-color: var(--body-bg-color);
+  font-family: var(--font-serif);
+  border: 2px solid var(--violet-bg);
+}
+
+@media (prefers-color-scheme: light) {
+    #module-search {
+        outline: 1px solid var(--gray);
+    }
+}
+
+#module-search::placeholder {
+  color: var(--faded-color);
+  opacity: 1;
+}
+
+#module-search:focus, #module-search:hover {
+  outline: 2px solid var(--violet);
+}
+
+#search-type-ahead {
+  font-family: var(--font-mono);
+  display: flex;
+  gap: 0px;
+  flex-direction: column;
+  position: absolute;
+  top: calc(var(--module-search-padding-height) + var(--module-search-height));
+  left: var(--module-search-form-padding-width);
+  width: calc(100% - 2 * var(--module-search-form-padding-width));
+  box-sizing: border-box;
+  z-index: 100;
+  background-color: var(--body-bg-color);
+  border-width: 1px;
+  border-style: solid;
+  border-color: var(--border-color);
+  list-style-type: none;
+  margin: 0;
+  padding: 0;
+}
+
+.search-icon {
+    fill: var(--faded-color);
+    pointer-events: none;
+    opacity: 0.6;
+    position: absolute;
+    right: 32px;
+}
+
+#search-type-ahead .type-ahead-link {
+  font-size: 1rem;
+  color: var(--text-color);
+  line-height: 2em;
+  position: relative;
+  box-sizing: border-box;
+  width: 100%;
+  height: 100%;
+  padding: 4px 8px;
+
+  max-height: 6em;
+  text-overflow: ellipsis;
+  overflow: hidden;
+  display: -webkit-box;
+  -webkit-line-clamp: 3;
+  -webkit-box-orient: vertical;
+
+  /* if it wraps, indent after the first line */
+  padding-left: calc(2em + 8px);
+  text-indent: -2em;
+
+
+  span {
+    margin: 0px;
+  }
+
+  .type-ahead-module-name, .type-ahead-def-name {
+    color: var(--violet);
+    font-size: 1rem;
+  }
+}
+
+#search-type-ahead li {
+  box-sizing: border-box;
+  position: relative;
+}
+
+#search-type-ahead a:focus {
+  outline: none;
+  background: var(--violet-bg);
+}
+
+#module-search-form:focus-within #search-label, #module-search-form:focus-within .search-icon {
+  display: none;
+}
+
+#search-label {
+  color: var(--faded-color);
+  box-sizing: border-box;
+  align-items: center;
+  font-size: 18px;
+  pointer-events: none;
+  position: absolute;
+  right: 72px;
+}
+
+#search-shortcut-key {
+  font-family: var(--font-mono);
+  border: 1px solid #666;
+  border-radius: 5px;
+  padding: 1px 3px 3px;
+  font-style: normal;
+  line-height: 15px;
+  pointer-events: none;
+}
+
+.builtins-tip {
+  padding: 1em;
+  font-style: italic;
+  line-height: 1.3em;
+}
+
+.module-header-container {
+  display: flex;
+  justify-content: space-between;
+  align-items: flex-end;
+  margin-bottom: 48px;
+}
+
+.llm-prompt-link {
+  flex-shrink: 0;
+}
+
+.module-name {
+    flex-grow: 1;
+}
+
+@media (prefers-color-scheme: dark) {
+  :root {
+      /* WCAG AAA Compliant colors */
+    --code-bg: hsl(228.95deg 37.25% 15%);
+    --gray: hsl(0 0% 70% / 1);
+    --orange: hsl(25 98% 70% / 1);
+    --green: hsl(115 40% 70% / 1);
+    --cyan: hsl(176 84% 70% / 1);
+    --blue: hsl(243 43% 80% / 1);
+    --violet: #caadfb;
+    --violet-bg: hsl(262 25% 15% / 1);
+    --magenta: hsl(348 79% 80% / 1);
+      --link-hover-color: #fff;
+
+      --link-color: var(--violet);
+      --code-link-color: var(--violet);
+      --text-color: #eaeaea;
+      --body-bg-color: hsl(from var(--violet-bg) h s calc(l * .5));
+      --border-color: var(--gray);
+      --code-color: #eeeeee;
+      --logo-solid: #8f8f8f;
+      --faded-color: #bbbbbb;
+      --sidebar-bg-color: hsl(from var(--violet-bg) h calc(s * 1.1) calc(l * 0.75));
+  }
+
+  html {
+      scrollbar-color: #8f8f8f #2f2f2f;
+  }
+}
+
+@media only screen and (max-width: 768px) {
+    :root {
+        --sidebar-width: clamp(280px, 50dvw, 385px);
+    }
+    body {
+        display: block;
+        overflow-y: auto;
+        overflow-x: hidden;
+    }
+
+    #sidebar-nav {
+        left: calc(-1 * var(--sidebar-width));
+        top: 0;
+        bottom: 0;
+        position: fixed;
+        z-index: 2;
+        transition: all .2s linear;
+    }
+
+    .entry-toggle {
+        height: 48px;
+        width: 48px;
+    }
+
+    body.sidebar-open #sidebar-nav {
+        left: 0;
+    }
+
+    main {
+        display: block;
+        margin: 0 16px;
+        --main-content-width: minmax(calc(100% - 32px), 60ch);
+    }
+
+    :root {
+        --top-header-height: 160px;
+    }
+
+    #search-shortcut-key, .header-start-extension, .header-end-extension, #search-label {
+        display: none;
+    }
+
+    #module-search-form {
+        padding: 16px 16px;
+        height: auto;
+        margin-bottom: 16px;
+        grid-column-start: main-content-start;
+        grid-column-end: main-content-end;
+        position: fixed;
+        left: 0;
+        right: 0;
+    }
+
+    .menu-toggle {
+        display: inline-block;
+        margin-right: 8px;
+        appearance: none;
+        background-color: transparent;
+        outline: none;
+        border: none;
+        color: var(--text-color);
+        padding: 0;
+    }
+
+    .menu-toggle svg {
+        height: 48px;
+        width: 48px;
+    }
+
+    /* Hide the Copy Link button on mobile. */
+    .entry-name a:first-of-type {
+        display: none;
+    }
+
+    .search-icon {
+        display: block; /* This is only visible in mobile. */
+        top: calc(1lh / 2 + 16px);
+    }
+
+    .top-header {
+        flex-direction: column;
+        height: auto;
+        justify-content: space-between;
+        /* min-width must be set to something (even 0) for text-overflow: ellipsis to work in descendants. */
+        min-width: 0;
+    }
+
+    .pkg-full-name {
+        font-size: 20px;
+        padding-bottom: 14px;
+    }
+
+    .pkg-full-name a {
+        vertical-align: middle;
+        padding: 18px 0;
+    }
+
+    .logo {
+        width: 50px;
+        height: 54px;
+    }
+
+    .version {
+        margin: 0;
+        font-weight: normal;
+        font-size: 18px;
+        padding-bottom: 16px;
+    }
+
+    .module-name {
+        font-size: 24px;
+        margin-top: 8px;
+        margin-bottom: 8px;
+        overflow: hidden;
+        text-overflow: ellipsis;
+    }
+
+    main {
+        padding: 18px;
+        font-size: 16px;
+        padding-top: calc(16px + 16px + 1lh + 16px);
+    }
+
+    #sidebar-nav {
+        margin-top: 0;
+        padding-left: 0;
+        width: var(--sidebar-width);
+    }
+
+    #sidebar-heading {
+        font-size: 24px;
+        margin: 16px;
+    }
+
+    h3 {
+        font-size: 18px;
+        margin: 0;
+        padding: 0;
+    }
+
+    h4 {
+        font-size: 16px;
+    }
+
+    body {
+        margin: 0;
+        min-width: 320px;
+        max-width: 100dvw;
+    }
+
+    .top-header-triangle {
+        display: none;
+    }
+
+    .pkg-and-logo {
+        padding-block: 4px;
+    }
+
+    .pkg-full-name {
+        flex-grow: 1;
+    }
+
+    .pkg-full-name a {
+        padding-top: 24px;
+        padding-bottom: 12px;
+    }
+}
+
+/* Comments `#` and Documentation comments `##` */
+samp .comment,
+code .comment {
+  color: var(--green);
+}
+
+/* Number, String, Tag literals */
+samp .storage.type,
+code .storage.type,
+samp .string,
+code .string,
+samp .string.begin,
+code .string.begin,
+samp .string.end,
+code .string.end,
+samp .constant,
+code .constant,
+samp .literal,
+code .literal {
+  color: var(--cyan);
+}
+
+/* Keywords and punctuation */
+samp .keyword,
+code .keyword,
+samp .punctuation.section,
+code .punctuation.section,
+samp .punctuation.separator,
+code .punctuation.separator,
+samp .punctuation.terminator,
+code .punctuation.terminator,
+samp .kw,
+code .kw {
+    color: var(--magenta);
+}
+
+/* Operators */
+samp .op,
+code .op,
+samp .keyword.operator,
+code .keyword.operator {
+  color: var(--orange);
+}
+
+/* Delimieters */
+samp .delimeter,
+code .delimeter {
+  color: var(--gray);
+}
+
+/* Variables modules and field names */
+samp .function,
+code .function,
+samp .meta.group,
+code .meta.group,
+samp .meta.block,
+code .meta.block,
+samp .lowerident,
+code .lowerident {
+  color: var(--blue);
+}
+
+/* Types, Tags, and Modules */
+samp .type,
+code .type,
+samp .meta.path,
+code .meta.path,
+samp .upperident,
+code .upperident {
+  color: var(--green);
+}
+
+samp .dim,
+code .dim {
+  opacity: 0.55;
+}
+
+.button-container {
+  position: absolute;
+  top: 0;
+  right: 0;
+}
+
+.copy-button {
+  background: var(--code-bg);
+  border: 1px solid var(--magenta);
+  color: var(--magenta);
+  display: inline-block;
+  cursor: pointer;
+  margin: 8px;
+}
+
+.copy-button:hover {
+  border-color: var(--link-hover-color);
+  color: var(--link-hover-color);
+}
diff --git a/docs/index.html b/docs/index.html
new file mode 100644
index 00000000..46527352
--- /dev/null
+++ b/docs/index.html
@@ -0,0 +1,19 @@
+
+
+    
+        
+        
+        Redirecting...
+        
+    
+    
+        
+    
+

From fa88d5911f96138541a9ec2e0f7dabd07ec54ede Mon Sep 17 00:00:00 2001
From: Luke Boswell 
Date: Wed, 8 Jan 2025 14:13:18 +1100
Subject: [PATCH 2/4] add gitattributes to default collapse docs/ in PR

---
 .gitattributes | 2 ++
 1 file changed, 2 insertions(+)
 create mode 100644 .gitattributes

diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 00000000..1fa51f5d
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,2 @@
+# Static site changes are changed files in a PR, but their diffs will be collapsed by default
+docs/**/* linguist-documentation=true

From b160359e6929d2596e747a5aa4b9d2f799ff4d8b Mon Sep 17 00:00:00 2001
From: Luke Boswell 
Date: Wed, 8 Jan 2025 14:14:25 +1100
Subject: [PATCH 3/4] try with each file extenstion

---
 .gitattributes | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/.gitattributes b/.gitattributes
index 1fa51f5d..fe7d16ac 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,2 +1,4 @@
 # Static site changes are changed files in a PR, but their diffs will be collapsed by default
-docs/**/* linguist-documentation=true
+docs/**/*.html linguist-documentation=true
+docs/**/*.js linguist-documentation=true
+docs/**/*.txt linguist-documentation=true

From d5c7e994008d676bee76c035abf4312feaa82dfe Mon Sep 17 00:00:00 2001
From: Luke Boswell 
Date: Wed, 8 Jan 2025 14:16:17 +1100
Subject: [PATCH 4/4] use linguist-generated instead

---
 .gitattributes | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/.gitattributes b/.gitattributes
index fe7d16ac..a8f31660 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,4 +1,4 @@
 # Static site changes are changed files in a PR, but their diffs will be collapsed by default
-docs/**/*.html linguist-documentation=true
-docs/**/*.js linguist-documentation=true
-docs/**/*.txt linguist-documentation=true
+docs/**/*.html linguist-generated=true
+docs/**/*.js linguist-generated=true
+docs/**/*.txt linguist-generated=true