---
package: prelude
version: "0.4.0-alpha.2"
source: fs.kex
title: FS
entities:
  - { kind: module, name: "FS" }
---

# FS

## module `FS`

The filesystem: reading and writing files, walking directories, and manipulating paths.

`FS` is not in the prelude — start with `using FS`.

```kex
using FS

main do
  match FS.File.read("config.txt") do
    Just(text) => IO.printLine(text.lines.count)
    None       => IO.printError("config.txt is missing")
  end
end
```

It is organised in three parts:

```kex
FS.File        reading, writing, copying and deleting files
FS.Directory   creating, listing and removing directories
FS.Path        pure path arithmetic, with no filesystem access at all
```

The first two are capabilities: everything in them touches the real filesystem, so they can only be called from `foul` code, and a test can replace the whole of `FS.File` for a lexical region rather than mutating global state. `FS.Path` is ordinary pure code.

Most read operations answer with an `Optional` and most write operations with a `Bool`, so a missing file or a failed write is an ordinary value you handle rather than an exception you catch.



## type `FilePath`

A filesystem path. An alias for `String`, so every `String` method applies to one; `FS.Path` adds the path-aware operations.

**Variants**

  - `String`



## type `FileModes`

How a file should be opened: `Read` to read it, `Write` to replace it, `Append` to add to its end, `ReadWrite` for both. The mode chosen decides what type the resulting `FileHandle` has, and therefore which operations the compiler will let you call on it.

**Variants**

  - `Read`
  - `Write`
  - `Append`
  - `ReadWrite`



## type `ReadPermission`

Whether a `FileHandle` may be read from. Part of the handle's type rather than a runtime flag, so reading from a write-only handle is a compile error.

**Variants**

  - `CanRead`
  - `CannotRead`



## type `WritePermission`

Whether a `FileHandle` may be written to. Part of the handle's type, so writing to a read-only handle is a compile error.

**Variants**

  - `CanWrite`
  - `CannotWrite`



## type `FileError`

A file operation that failed, carrying the path it failed on.

`OpenFailed` and `ReadFailed` mean the filesystem refused the operation. `InvalidUtf8` means the bytes were read but are not valid UTF-8, and carries the byte offset of the first malformed sequence.

**Variants**

  - `OpenFailed(FilePath)`
  - `ReadFailed(FilePath)`
  - `InvalidUtf8(FilePath, Integer)`



## module `FS.File`

Reading and writing files.

A capability: every member reaches the real filesystem, so a test can replace the whole thing for a lexical region with `with FS.File = ...` instead of mutating global mock state (kexhq/kex#143). The boundary is here rather than on `FS`, because `FS.Path` below is pure string work with no implementation to substitute.

### `open`

```kex
open(path: FilePath, mode: Read) -> Result<FileHandle<CanRead, CannotWrite>, FileError>
open(path: FilePath, mode: Write) -> Result<FileHandle<CannotRead, CanWrite>, FileError>
open(path: FilePath, mode: Append) -> Result<FileHandle<CannotRead, CanWrite>, FileError>
open(path: FilePath, mode: ReadWrite) -> Result<FileHandle<CanRead, CanWrite>, FileError>
open(path: FilePath, mode: Read) -> (FileHandle<CanRead, CannotWrite> -> A) -> Result<A, FileError>
open(path: FilePath, mode: Write) -> (FileHandle<CannotRead, CanWrite> -> A) -> Result<A, FileError>
open(path: FilePath, mode: Append) -> (FileHandle<CannotRead, CanWrite> -> A) -> Result<A, FileError>
open(path: FilePath, mode: ReadWrite) -> (FileHandle<CanRead, CanWrite> -> A) -> Result<A, FileError>
```

Opens `path` and returns a `FileHandle` for it.

The mode decides the handle's type, and the type decides what you may do with it: a handle opened `Read` has no `write`, a handle opened `Write` has no `read`. That is checked at compile time, not at run time.

Use the whole-file `read` and `write` below when a file fits in memory and you have no reason to hold it open; reach for a handle when you want to stream through a large file or make many small writes.

Close the handle when you are done with it.

**Parameters**

  - `path` — the file to open
  - `mode` — `Read`, `Write`, `Append` or `ReadWrite`

**Returns**: the handle, or `OpenFailed`

**Examples**

_Reading a file line by line_

```kex
match FS.File.open("log.txt", Read) do
  Ok(handle) => do
    IO.printLine(handle.readLine.or(""))
    handle.close
  end
  Error(e) => IO.printError("cannot open: ${e}")
end
```

_Writing a report_

```kex
match FS.File.open("report.txt", Write) do
  Ok(handle) => do
    rows.each { |row| handle.printLine(row) }
    handle.close
  end
  Error(_) => IO.printError("cannot write report")
end
```

_A failure names the path_

```kex
FS.File.open("missing/x.txt", Read)   # => Error(OpenFailed("missing/x.txt"))
```

### `read`

```kex
read(path: FilePath) -> Result<String, FileError>
```

Reads the whole file and decodes it as UTF-8 text.

Answers `Error(ReadFailed(path))` when the file does not exist or cannot be read, and `Error(InvalidUtf8(path, offset))` when the bytes are not valid UTF-8 — so a missing or non-text file is something you handle rather than something that stops the program. Reach for `FS.File.readBytes` when the contents are not text.

**Parameters**

  - `path` — the file to read

**Returns**: the contents, or the failure

**Examples**

```kex
FS.File.read("hello.txt")     # => Ok("hello\n")
FS.File.read("nowhere.txt")   # => Error(ReadFailed("nowhere.txt"))
```

_Reading with a default_

```kex
let config = FS.File.read("app.conf").or("")
```

_Counting words in a file_

```kex
FS.File.read("essay.txt").or("").split(" ").reject(~empty?).count
```

### `readBytes`

```kex
readBytes(path: FilePath) -> Result<Binary, FileError>
```

Reads the whole file as raw bytes, without decoding it as text.

The byte counterpart of `FS.File.read`: it never validates UTF-8, so it round trips images, archives, and any other non-text payload losslessly. Answers `Error(ReadFailed(path))` when the file cannot be read.

**Parameters**

  - `path` — the file to read

**Returns**: the contents, or the failure

**Examples**

```kex
FS.File.readBytes("logo.png")   # => Ok(#Binary<2048 bytes>)
```

_Hashing a file_

```kex
FS.File.readBytes(path).map { |data| Digest.sha256(data).hex }
```

### `writeBytes`

```kex
writeBytes(path: FilePath, content: Binary) -> Bool
```

Writes `content` to `path` as raw bytes, replacing whatever was there.

The byte counterpart of `FS.File.write`: the payload lands on disk exactly as given, with no encoding step. Answers `false` when the write fails.

**Parameters**

  - `path` — the file to write
  - `content` — the bytes to write

**Returns**: `true` when the write succeeded

**Examples**

```kex
FS.File.writeBytes("out.bin", Binary.fromBytes([0, 255]))   # => true
```

_Copying a file without decoding it_

```kex
FS.File.readBytes(source).map { |data| FS.File.writeBytes(target, data) }
```

### `write`

```kex
write(path: FilePath, content: String) -> Bool
```

Writes `content` to `path`, replacing whatever was there.

Creates the file if it does not exist. The containing directory must already exist — see `FS.Directory.create`. Answers `false` when the write fails.

**Parameters**

  - `path` — the file to write
  - `content` — the text to write

**Returns**: `true` when the write succeeded

**Examples**

```kex
FS.File.write("out.txt", "hello\n")   # => true
```

_Reporting a failed write_

```kex
if !FS.File.write(target, rendered)
  IO.printError("could not write ${target}")
end
```

_Writing a list of lines_

```kex
FS.File.write("names.txt", names.join("\n") + "\n")
```

### `append`

```kex
append(path: FilePath, content: String) -> Bool
```

Adds `content` to the end of `path`, keeping what is already there.

Creates the file if it does not exist, so it is safe to append to a log that has not been started yet.

**Parameters**

  - `path` — the file to append to
  - `content` — the text to add

**Returns**: `true` when the write succeeded

**Examples**

```kex
FS.File.append("app.log", "started\n")   # => true
```

_A simple logger_

```kex
foul log(message: String) -> Void do
  FS.File.append("app.log", "${Time.now.to(String).or("")} ${message}\n")
end
```

### `exists?`

```kex
exists?(path: FilePath) -> Bool
```

Returns `true` when something exists at `path` — a file, a directory, or anything else. Use `file?` or `directory?` when the kind matters.

**Parameters**

  - `path` — the path to test

**Returns**: `true` when the path exists

**Examples**

```kex
FS.File.exists?("README.md")   # => true
FS.File.exists?("nowhere")     # => false
```

_Not overwriting an existing file_

```kex
if FS.File.exists?(target)
  IO.printError("${target} already exists")
else
  FS.File.write(target, content)
end
```

### `file?`

```kex
file?(path: FilePath) -> Bool
```

Returns `true` when `path` exists and is a regular file — not a directory.

**Parameters**

  - `path` — the path to test

**Returns**: `true` for a regular file

**Examples**

```kex
FS.File.file?("README.md")   # => true
FS.File.file?("src")         # => false
```

### `directory?`

```kex
directory?(path: FilePath) -> Bool
```

Returns `true` when `path` exists and is a directory.

**Parameters**

  - `path` — the path to test

**Returns**: `true` for a directory

**Examples**

```kex
FS.File.directory?("src")        # => true
FS.File.directory?("README.md")  # => false
```

_Separating files from directories in a listing_

```kex
entries.partition { |e| FS.File.directory?(e) }
```

### `delete`

```kex
delete(path: FilePath) -> Bool
```

Deletes the file at `path`.

Answers `false` when the file does not exist or cannot be removed. Use `FS.Directory.delete` for a directory.

**Parameters**

  - `path` — the file to delete

**Returns**: `true` when the file was deleted

**Examples**

```kex
FS.File.delete("tmp.txt")   # => true
```

_Cleaning up after a temporary file_

```kex
FS.File.write(tmp, data)
process(tmp)
FS.File.delete(tmp)
```

### `copy`

```kex
copy(src: FilePath, dst: FilePath) -> Bool
```

Copies the file at `src` to `dst`, replacing `dst` if it exists.

**Parameters**

  - `src` — the file to copy
  - `dst` — where to copy it

**Returns**: `true` when the copy succeeded

**Examples**

```kex
FS.File.copy("app.conf", "app.conf.bak")   # => true
```

_Backing a file up before rewriting it_

```kex
FS.File.copy(path, "${path}.bak")
FS.File.write(path, updated)
```

### `rename`

```kex
rename(src: FilePath, dst: FilePath) -> Bool
```

Renames — or moves — the file at `src` to `dst`.

**Parameters**

  - `src` — the current path
  - `dst` — the new path

**Returns**: `true` when the rename succeeded

**Examples**

```kex
FS.File.rename("draft.txt", "final.txt")   # => true
```

_Writing to a temporary file, then swapping it in_

```kex
FS.File.write("${path}.tmp", content)
FS.File.rename("${path}.tmp", path)
```

### `readLines`

```kex
readLines(path: FilePath) -> [String]?
```

Reads the file and returns its lines, without their newlines.

Answers `None` when the file cannot be read. A trailing newline does not produce a final empty line, so the count is the number of lines you would see in an editor.

NOT `lines`: FilePath is an alias for String, so a receiver function named `lines` here is indistinguishable from String's own `lines` at every call site, and merely saying `using FS` made `text.lines` ambiguous. `readLines` also says what it does — it reads the file.

**Parameters**

  - `path` — the file to read

**Returns**: the lines, or `None`

**Examples**

```kex
FS.File.readLines("names.txt")   # => Just(["ada", "grace"])
FS.File.readLines("nowhere")     # => None
```

_Ignoring blank lines and comments_

```kex
FS.File.readLines("hosts")
  .or([])
  .map(~trim)
  .reject { |line| line.empty? || line.startsWith?("#") }
```

### `feed`

```kex
feed(path: FilePath) -> Feed<String>?
```

Returns a lazy `Feed` of the file's lines, or `None` when it cannot be read.

Unlike `readLines`, the file is read a line at a time off a handle held open for the feed's lifetime, so this is the way to walk a file too large to hold in memory — nothing but the current line is retained.

A `Feed` rather than a `Stream` because that is what a file honestly is: reading consumes, and there is no rewinding. Taking twice walks forward rather than answering the same lines again. On a file small enough to replay, `toStream` buys that back.

The feed ends at the last line, so asking for more lines than the file has answers just the lines there are — unlike `Stream.Sequence`, which is deliberately infinite.

```kex
FS.File.feed("two-lines.txt").map { |lines| lines.take(5) }.or([])
# => ["one", "two"]
```

**Parameters**

  - `path` — the file to read

**Returns**: the lines as a feed, or `None`

**Examples**

_Peeking at the head of a large file_

```kex
match FS.File.feed("huge.log") do
  Just(lines) => lines.take(10).each { |line| IO.printLine(line) }
  None        => IO.printError("cannot read huge.log")
end
```

_The first ten errors in a long log, in one pass_

```kex
FS.File.feed("app.log")
  .map { |lines| lines.filter { |line| line.contains?("ERROR") }.take(10) }
  .or([])
```

### `size`

```kex
size(path: FilePath) -> Integer?
```

Returns the file's size in bytes, or `None` when it cannot be read.

Bytes, not characters — a file of non-ASCII text has more bytes than it has characters.

**Parameters**

  - `path` — the file to measure

**Returns**: the size in bytes, or `None`

**Examples**

```kex
FS.File.size("hello.txt")   # => Just(6)
FS.File.size("nowhere")     # => None
```

_Skipping files that are too large_

```kex
paths.filter { |p| FS.File.size(p).or(0) < 1048576 }
```

### `absolute`

```kex
absolute(path: FilePath) -> String?
```

Resolves `path` against the process's current directory and returns the absolute form, or `None` when it cannot be resolved.

This is the one path operation that is not in `FS.Path`, because it is not lexical: it asks the process where it is.

Path manipulation lives in FS.Path — `basename`, `dirname`, `extension` and `join` used to be here too, but a name cannot sit in both modules: FilePath IS String, so two same-named receiver functions on it are indistinguishable at every call site. `absolute` stays because it is not lexical — it asks the process where it is.

**Parameters**

  - `path` — the path to resolve

**Returns**: the absolute path, or `None`

**Examples**

```kex
FS.File.absolute("src/main.kex")   # => Just("/home/ada/proj/src/main.kex")
```

_Reporting a file unambiguously_

```kex
IO.printError("failed: ${FS.File.absolute(path).or(path)}")
```

## module `FS.Path`

Path arithmetic: joining, splitting, normalising and comparing paths.

Everything here is pure string manipulation with no filesystem access, so the answer is the same whether or not the path exists — which also means these functions can be called from ordinary pure code, unlike `FS.File`. POSIX separators only for now.

```kex
FS.Path.join("src", "main.kex")            # => "src/main.kex"
FS.Path.extension("src/main.kex")          # => ".kex"
FS.Path.withExtension("src/main.kex", "beam")   # => "src/main.beam"
```

### `separator` (constant)

```kex
separator : String
```

The path separator, `"/"`.

**Examples**

```kex
FS.Path.separator   # => "/"
```



### `join`

```kex
join(a: FilePath, b: FilePath) -> String
join(a: FilePath, b: FilePath) -> FilePath -> String
```

Joins two path parts with a single separator and normalises the result.

Repeated separators collapse, so `join("a/", "/b")` is `"a/b"` and not `"a//b"`. An absolute second part does NOT restart the path, the way Ruby's `Pathname#join` would — use that part on its own if that is what you mean.

**Parameters**

  - `a` — the first part
  - `b` — the second part

**Returns**: the joined, normalised path

**Examples**

```kex
FS.Path.join("src", "main.kex")   # => "src/main.kex"
FS.Path.join("a/", "/b")          # => "a/b"
FS.Path.join("a/b", "../c")       # => "a/c"
```

_Building a path under a base directory_

```kex
FS.Path.join(outputDir, FS.Path.basename(source))
```

### `joinAll`

```kex
joinAll(parts: [FilePath]) -> String
```

Joins any number of path parts, skipping empty ones, and normalises the result. An empty list gives `"."`.

The list form is `joinAll`, not another `join` overload: a list receiver already has `List.join`, and a second one-argument `join` on the same receiver would be indistinguishable from it.

**Parameters**

  - `parts` — the parts to join

**Returns**: the joined, normalised path

**Examples**

```kex
FS.Path.joinAll(["a", "b", "c.txt"])   # => "a/b/c.txt"
FS.Path.joinAll(["a", "", "b"])        # => "a/b"
FS.Path.joinAll([])                    # => "."
```

_Rebuilding a path from its segments_

```kex
FS.Path.joinAll(FS.Path.segments(path).drop(1))
```

### `normalize`

```kex
normalize(path: FilePath) -> String
```

Resolves `.` and `..` in a path, lexically.

Because it is lexical it never follows a symlink and never touches the disk. A leading `..` in a RELATIVE path is kept — there is no way to know what it escapes to — while one in an absolute path is dropped, since `/` has no parent.

**Parameters**

  - `path` — the path to normalise

**Returns**: the normalised path

**Examples**

```kex
FS.Path.normalize("a/./b/../c")   # => "a/c"
FS.Path.normalize("../a")         # => "../a"
FS.Path.normalize("/a/../../b")   # => "/b"
```

_Comparing two paths for equality_

```kex
FS.Path.normalize(a) == FS.Path.normalize(b)
```

### `segments`

```kex
segments(path: FilePath) -> [String]
```

Returns the non-empty parts of the normalised path. The root `/` and the current directory `.` have none.

**Parameters**

  - `path` — the path to split

**Returns**: the segments, outermost first

**Examples**

```kex
FS.Path.segments("/a/b/c")   # => ["a", "b", "c"]
FS.Path.segments("a/./b")    # => ["a", "b"]
FS.Path.segments("/")        # => []
```

_How deep a path is_

```kex
FS.Path.segments(path).count
```

### `absolute?`

```kex
absolute?(path: FilePath) -> Bool
```

Returns `true` when the path starts at the root.

**Parameters**

  - `path` — the path to test

**Returns**: `true` for an absolute path

**Examples**

```kex
FS.Path.absolute?("/etc/hosts")   # => true
FS.Path.absolute?("src/main")     # => false
```

### `relative?`

```kex
relative?(path: FilePath) -> Bool
```

Returns `true` when the path does not start at the root. The opposite of `absolute?`.

**Parameters**

  - `path` — the path to test

**Returns**: `true` for a relative path

**Examples**

```kex
FS.Path.relative?("src/main")     # => true
FS.Path.relative?("/etc/hosts")   # => false
```

### `dirname`

```kex
dirname(path: FilePath) -> String
```

Returns the path's parent directory.

A child of the root has `"/"` as its parent; a bare name has `"."`, the current directory.

**Parameters**

  - `path` — the path to take the parent of

**Returns**: the parent directory

**Examples**

```kex
FS.Path.dirname("/a/b/c.txt")   # => "/a/b"
FS.Path.dirname("/c.txt")       # => "/"
FS.Path.dirname("c.txt")        # => "."
```

_Making sure a file's directory exists before writing_

```kex
FS.Directory.create(FS.Path.dirname(target))
```

### `basename`

```kex
basename(path: FilePath) -> String
```

Returns the last segment of the path — the file or directory name.

The root itself answers `"/"`, and an empty path answers `"."`.

**Parameters**

  - `path` — the path to take the name of

**Returns**: the last segment

**Examples**

```kex
FS.Path.basename("/a/b/c.txt")   # => "c.txt"
FS.Path.basename("/a/b")         # => "b"
FS.Path.basename("/")            # => "/"
```

_Listing names rather than paths_

```kex
paths.map { |p| FS.Path.basename(p) }
```

### `extension`

```kex
extension(path: FilePath) -> String
```

Returns the file extension, including its leading dot, or `""` when there is none.

Only the last extension counts, so `"a.tar.gz"` has `".gz"`. A leading dot is part of the NAME rather than an extension, so `".gitignore"` has none.

**Parameters**

  - `path` — the path to inspect

**Returns**: the extension, or `""`

**Examples**

```kex
FS.Path.extension("src/main.kex")   # => ".kex"
FS.Path.extension("a/b.tar.gz")     # => ".gz"
FS.Path.extension("README")         # => ""
FS.Path.extension(".gitignore")     # => ""
```

_Selecting sources by extension_

```kex
files.filter { |f| FS.Path.extension(f) == ".kex" }
```

### `stem`

```kex
stem(path: FilePath) -> String
```

Returns the basename with its extension removed.

A name that IS its extension — `".gitignore"` — keeps it, because it has none to drop.

**Parameters**

  - `path` — the path to inspect

**Returns**: the name without its extension

**Examples**

```kex
FS.Path.stem("src/main.kex")   # => "main"
FS.Path.stem("a/b.tar.gz")     # => "b.tar"
FS.Path.stem(".gitignore")     # => ".gitignore"
```

_Deriving a module name from a filename_

```kex
FS.Path.stem(source).capitalize
```

### `withExtension`

```kex
withExtension(path: FilePath, wanted: String) -> String
```

Returns the path with its extension replaced by `wanted`.

The new extension may be written with or without its leading dot; an empty one removes the extension entirely.

**Parameters**

  - `path` — the path to rewrite
  - `wanted` — the new extension, with or without its dot

**Returns**: the rewritten path

**Examples**

```kex
FS.Path.withExtension("src/main.kex", "beam")    # => "src/main.beam"
FS.Path.withExtension("src/main.kex", ".beam")   # => "src/main.beam"
FS.Path.withExtension("src/main.kex", "")        # => "src/main"
```

_Deriving an output path from a source path_

```kex
FS.Path.withExtension(FS.Path.join(outDir, FS.Path.basename(src)), "o")
```

### `relativeTo`

```kex
relativeTo(path: FilePath, base: FilePath) -> String
```

Expresses `path` relative to `base`, walking up with `..` as needed.

Purely lexical, so it answers only when both sides are anchored the same way; a relative path against an absolute base, or the reverse, comes back unchanged. A path that IS the base answers `"."`.

**Parameters**

  - `path` — the path to re-express
  - `base` — the directory to express it against

**Returns**: the relative path

**Examples**

```kex
FS.Path.relativeTo("/a/b/c.txt", "/a/d")   # => "../b/c.txt"
FS.Path.relativeTo("/a/b/c.txt", "/a")     # => "b/c.txt"
FS.Path.relativeTo("/a/b", "/a/b")         # => "."
```

_Printing project-relative paths in a report_

```kex
matches.each { |p| IO.printLine(FS.Path.relativeTo(p, projectRoot)) }
```

## module `FS.Directory`

Creating, listing and removing directories.

Like `FS.File`, everything here reaches the real filesystem and so is `foul`. Listings answer with an `Optional`, so a directory that cannot be read is a value you handle rather than an exception.

### `exists?`

```kex
exists?(path: FilePath) -> Bool
```

Returns `true` when something exists at `path`.

**Parameters**

  - `path` — the path to test

**Returns**: `true` when the path exists

**Examples**

```kex
FS.Directory.exists?("src")   # => true
```

### `directory?`

```kex
directory?(path: FilePath) -> Bool
```

Returns `true` when `path` exists and is a directory.

**Parameters**

  - `path` — the path to test

**Returns**: `true` for a directory

**Examples**

```kex
FS.Directory.directory?("src")        # => true
FS.Directory.directory?("README.md")  # => false
```

### `file?`

```kex
file?(path: FilePath) -> Bool
```

Returns `true` when `path` exists and is a regular file.

**Parameters**

  - `path` — the path to test

**Returns**: `true` for a regular file

**Examples**

```kex
FS.Directory.file?("README.md")   # => true
```

### `create`

```kex
create(path: FilePath) -> Bool
```

Creates the directory at `path`.

**Parameters**

  - `path` — the directory to create

**Returns**: `true` when the directory was created

**Examples**

```kex
FS.Directory.create("build")   # => true
```

_Making sure an output directory is there_

```kex
FS.Directory.create(outDir) if !FS.Directory.exists?(outDir)
```

### `delete`

```kex
delete(path: FilePath) -> Bool
```

Removes the directory at `path`, which must be empty.

Use `deleteAll` to remove a directory together with its contents.

**Parameters**

  - `path` — the directory to remove

**Returns**: `true` when the directory was removed

**Examples**

```kex
FS.Directory.delete("empty")   # => true
```

### `deleteAll`

```kex
deleteAll(path: FilePath) -> Bool
```

Removes the directory at `path` and everything inside it, recursively.

This deletes data and cannot be undone — check the path before calling it, particularly when it was computed or came from user input.

**Parameters**

  - `path` — the directory tree to remove

**Returns**: `true` when the tree was removed

**Examples**

```kex
FS.Directory.deleteAll("build")   # => true
```

_Rebuilding a scratch directory from clean_

```kex
FS.Directory.deleteAll(tmpDir)
FS.Directory.create(tmpDir)
```

### `list`

```kex
list(path: FilePath) -> [String]?
```

Lists the names in `path` — both files and directories, one level deep.

The results are bare names, not paths; join them with `path` to get something you can open. Answers `None` when the directory cannot be read.

**Parameters**

  - `path` — the directory to list

**Returns**: the entry names, or `None`

**Examples**

```kex
FS.Directory.list("src")   # => Just(["main.kex", "lexer"])
```

_Turning names into usable paths_

```kex
FS.Directory.list(dir).or([]).map { |name| FS.Path.join(dir, name) }
```

### `files`

```kex
files(path: FilePath) -> [String]?
```

Lists only the regular files in `path`, one level deep.

**Parameters**

  - `path` — the directory to list

**Returns**: the file names, or `None`

**Examples**

```kex
FS.Directory.files("src")   # => Just(["main.kex"])
```

_Every Kex source in a directory_

```kex
FS.Directory.files(dir)
  .or([])
  .filter { |f| FS.Path.extension(f) == ".kex" }
```

### `directories`

```kex
directories(path: FilePath) -> [String]?
```

Lists only the subdirectories of `path`, one level deep.

**Parameters**

  - `path` — the directory to list

**Returns**: the directory names, or `None`

**Examples**

```kex
FS.Directory.directories("src")   # => Just(["lexer", "parser"])
```

_Walking one level down_

```kex
FS.Directory.directories(root).or([]).each do |name|
  IO.printLine(FS.Path.join(root, name))
end
```

### `current`

```kex
current : String
```

Returns the process's current working directory, as an absolute path.

**Returns**: the current directory

**Examples**

```kex
FS.Directory.current   # => "/home/ada/project"
```

_Resolving a relative path by hand_

```kex
FS.Path.join(FS.Directory.current, "build")
```

### `home`

```kex
home : String?
```

Returns the current user's home directory, or `None` when it cannot be determined.

**Returns**: the home directory, or `None`

**Examples**

```kex
FS.Directory.home   # => Just("/home/ada")
```

_A per-user config path_

```kex
FS.Path.join(FS.Directory.home.or("."), ".apprc")
```

### `temporary`

```kex
temporary : String
```

Returns the directory this system puts temporary files in.

Total, unlike `home`: it answers `TMPDIR` when the environment sets one (`TEMP` or `TMP` on Windows) and falls back to `/tmp`, so there is always somewhere to write. The path never ends in a separator, so it composes with `FS.Path.join` directly.

The directory is shared with every other process on the machine, so pick a name unlikely to collide and delete it when you are done.

**Returns**: the temporary directory

**Examples**

```kex
FS.Directory.temporary   # => "/tmp"
```

_A scratch file that cleans up after itself_

```kex
let scratch = FS.Path.join(FS.Directory.temporary, "report.bin")
FS.File.writeBytes(scratch, payload)
FS.File.delete(scratch)
```
