# `nupp.io.files`
`nupp.io.files` reads what the filesystem knows about a name: whether it
resolves, what it refers to, what a directory contains, and where the platform
keeps a user's folders. It also moves and removes names.
```nupp:playground
local files = nupp.io.files
for _, entry in ipairs(assert(files.list("src"))) do
print(entry.kind, entry.name)
end
```
A file's bytes move through the same `Reader` and `Writer` contracts a buffer
uses, so a parser written against byte I/O works over a file without
knowing one is there.
A path argument is a string or a `nupp.io.path.Path`; a path result is a
string. An operation that fails because of the environment answers nil or false
with a reason, and a malformed argument raises at the call site.
## Querying a name
`info` answers one record of attributes:
```nupp
local info, reason = files.info("nupp.lua")
assert(info, reason)
assert(info.kind == "file")
print(info.size, info.modified, info.readOnly)
```
`info` follows symbolic links, so it describes what a name refers to rather than
the name itself. `size` is a byte count, `modified` is seconds since the Unix
epoch with whatever fractional part the platform records, and `readOnly` is the
platform's write refusal rather than a permission model.
`exists`, `isFile` and `isDirectory` answer the same question without the record
and without a reason. A missing path is `false` rather than an error:
```nupp
if files.isDirectory(candidate) then
return files.list(candidate)
end
```
`isSymlink` is the one query that does not follow, because following is what
would hide the answer:
```nupp
assert(files.createSymlink("target.txt", "alias"))
assert(files.isSymlink("alias"))
assert(files.isFile("alias"))
assert(files.readLink("alias") == "target.txt")
```
`createSymlink` takes an optional third argument, `"file"` or `"directory"`.
Only Windows distinguishes the two; elsewhere it is ignored.
## Listing a directory
`list` answers every entry in a directory, in one call:
```nupp
local entries, reason = files.list("src/nupp")
assert(entries, reason)
```
Each entry has a `name` without any directory part and a `kind` describing the
entry *itself*, so a link inside a listing reads as `symlink` rather than as
what it points at. The order is the platform's, which is not sorted.
The kind comes from the directory itself rather than a second query per name, so
listing a large directory costs one call.
## Matching paths
`glob` expands a pattern into sorted path strings. `*` and `?` stay within one
path component, `[abc]` and `[!abc]` select characters, and `**` crosses
directory boundaries. No matches is an empty list; an invalid pattern or a
filesystem error answers a reason.
```nupp
for _, path in ipairs(assert(files.glob("src/**/*.nupp"))) do
print(path)
end
```
## Creating, moving and removing
These three operations move names rather than bytes, so each answers now:
```nupp
assert(files.createDirectory("out/lib/native"))
assert(files.rename("out/report.tmp", "out/report.json"))
assert(files.remove("out/stale", true))
```
`createDirectory` creates every missing parent, and an existing directory
succeeds, which is what a caller building a tree wants rather than a race with
its own earlier call. `rename` replaces an existing destination. `remove` takes
a file, a symbolic link, or an empty directory; the second argument removes a
directory's contents with it, and without it a populated directory answers a
reason.
`setReadOnly` sets or clears the write refusal that `info` reports.
## Temporary names
`createTemporaryFile` and `createTemporaryDirectory` both take options and
answer a path:
```nupp
local scratch, reason = files.createTemporaryDirectory({prefix = "build-"})
assert(scratch, reason)
```
The file or directory is *created*, not proposed, so no second caller can take
the name between the answer and the use. `directory` selects where, defaulting
to the platform's temporary directory; `prefix` and `suffix` bracket the
generated part, which is what puts an extension on a temporary file.
A temporary is an [owner](../../../../learn/runtime/ownership/index.html): closing it removes
what it created, and the checker runs that cleanup at the end of the scope
whether the block falls through, returns early, or raises.
```nupp
do
local scratch = assert(files.createTemporaryFile({suffix = ".json"}))
assert(files.write(scratch:toString(), encoded))
assert(scratch:persist("out/report.json"))
end
```
`persist` moves it somewhere permanent and discharges the obligation, so the
close that follows does nothing. That pair is the reason to make one: write to a
name nobody else can take, then put it where it belongs, so a reader never sees
a half-written file under the final name.
## Reading and writing a whole file
`read` and `write` move a file's complete contents in one call:
```nupp
local text, reason = files.read("nupp.lua")
assert(text, reason)
assert(files.write("out/report.json", encoded))
```
`append` adds to the end and creates a missing file. `copy` duplicates one path
over another. `writeAtomic` writes through a temporary beside the destination
and renames over it, so an interrupted write leaves the destination as it was
rather than half replaced. A failed write removes the temporary rather than
leaving it behind.
A NUL byte is content, not a terminator, in every direction.
```nupp
for line in assert(files.lines("access.log")) do
print(line)
end
```
`lines` closes the file when it reaches the end. A trailing carriage return is
removed, so a file written on either platform reads the same. Abandoning the
iterator early leaves the file open until it is collected; open it yourself when
you mean to stop. A read that fails once iteration has begun raises, because the
iterator has no failure channel to answer through.
## Reading and writing through a cursor
`open` hands over a `File` and the obligation to close it:
```nupp
do
local file = assert(files.open("image.png"))
local reader = file:newReader()
print(reader:read(8))
end
```
The reader and writer satisfy `nupp.io.Reader` and `nupp.io.Writer`, so `read`,
`readSpan`, `readInto`, `transferTo`, `write`, `writeSpan` and `flush` mean
what they mean over a buffer. Native callers can read and write through checked
spans without separating a pointer from its bound. `readInto` commits the
destination buffer only after a successful read, and `transferTo` streams a
file of any size through a fixed window:
```nupp
do
local source = assert(files.open("input.bin"))
local sink = assert(files.open("output.bin", "w"))
print(source:newReader():transferTo(sink:newWriter()))
end
```
`mode` is `r`, `w`, `a`, or the update modes `r+`, `w+` and `a+`, matching C and
Lua. `seek(offset, origin)` moves the cursor, with `origin` one of `set`,
`current` or `end`; `position` and `size` answer where it is and how long the
file is. A reader or writer over a closed file answers a reason rather than
raising.
## Platform directories
Two queries answer where the process is and where the platform keeps a user's
folders:
```nupp
print(assert(files.currentDirectory()))
print(assert(files.userFolder("documents")))
```
`userFolder` takes `home`, `documents`, `downloads`, `desktop`, `pictures`,
`music` or `videos`. It resolves from the environment: the `XDG_*` variables
where they are set, and the platform's conventional names under the home
directory otherwise. A desktop that records its folders somewhere else is not
consulted, and a folder that does not exist answers a reason rather than a path
that is not there.
## Waiting
A whole-file `read`, `write`, `append`, `writeAtomic` or `copy` settles on a
worker thread rather than on yours, and the call waits for it by suspending.
What that means depends on where the call runs, and on nothing the call says:
| Where it runs | Effect |
| --- | --- |
| an ordinary program | it sleeps, driving the readiness pump |
| under an installed handler | it parks, and the handler resumes it |
| inside a `nosuspend` region | it is refused, at compile time |
One call site covers all three. A library that reads a file works inside a game
frame and inside a command-line program without knowing which it is in, and a
transfer that settled before it was observed never reaches any of this.
::: deepdive Names answer now, bytes go to a worker lane
Anything that moves names rather than bytes answers now, because the platform
answers it now. Anything that moves a whole file's bytes is submitted to the
native library's worker lane and waited for, so a program inside a scheduler
yields instead of blocking its host. Making both suspend would tax every `stat`
for a wait that never happens; making neither suspend would block a host on a
transfer it cannot see. See [Suspension](../../../../learn/runtime/concurrency/suspension/index.html)
for how one call takes either path.
:::
The immediate operations cannot suspend, so a region that forbids waiting still
permits asking what a path is, listing a directory, or renaming one:
```nupp
nosuspend do
if files.isDirectory(candidate) then
print(#assert(files.list(candidate)))
end
print(files.read(candidate)) -- NUPP2701: `read` may suspend
end
```
## Cost
Reaching `nupp.io.files` selects the compiler-owned Rust provider, whose filesystem
operations use the platform standard library and whose blocking transfers run through
the shared native executor, and loads it on first use. A
program that never reaches it links nothing and initializes nothing, which is
the rule for every facility in the [standard
library](../../../../learn/runtime/data/standard-library/index.html). A target that uses it also carries
the suspension runtime, because that is what answers the wait above.
The lane those workers run is bounded three ways: how many transfers may be
live, how many bytes they may hold between them, and how large one may be. Past
any of the three a submission answers a reason rather than queueing, because a
queue that grows with its callers eventually takes the process with it.
`pendingTransfers` answers what the lane is holding.
Metadata, listings and cursor reads through an open `File` do not use the lane.
Scheduling a transfer costs more than those cost to run.
::: seealso
- `nupp.io.path` for building and taking apart the names these operations
read
- `nupp.io` for the buffers, readers and writers a file's bytes move through
- [ownership.md](../../../../learn/runtime/ownership/borrowing/index.html) for the complete contract an
open file and a temporary path are written against
- [Suspension](../../../../learn/runtime/concurrency/suspension/index.html) for what a wait does under a
scheduler, and how several compose
:::
## Types
### `Entry` _record_
```nupp
record Entry
name: string
kind: Kind
end
```
One directory child, as the directory itself describes it.
#### Fields
##### `name`
```nupp
name: string
```
The child's name, without any directory part.
##### `kind`
```nupp
kind: Kind
```
What the entry itself is, without following a symbolic link.
### `File` _record_
```nupp
record File
function newReader(self): FileReader end
function newWriter(self): FileWriter end
function size(self): (integer?, string?) end
function seek(self, offset: integer?, origin: Origin?): (integer?, string?) end
function position(self): (integer?, string?) end
function flush(self): (boolean, string?) end
function isReleased(self): boolean end
drop: nosuspend function(takes self: File): nil
function close(takes self): boolean end
end
```
An open file, and the obligation to close it.
#### Methods
##### `newReader`
```nupp
newReader: function newReader(self): FileReader
```
Opens a forward-only reader at the file's current position.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `any` | this file |
###### Returns
| Type | Description |
| --- | --- |
| `FileReader` | the new reader |
###### Raises
- when the file is closed
##### `newWriter`
```nupp
newWriter: function newWriter(self): FileWriter
```
Opens a forward-only writer at the file's current position.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `any` | this file |
###### Returns
| Type | Description |
| --- | --- |
| `FileWriter` | the new writer |
###### Raises
- when the file is closed
##### `size`
```nupp
size: function size(self): integer?, string?
```
Answers the file's byte length without moving the cursor.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `any` | this file |
###### Returns
| Type | Description |
| --- | --- |
| `integer?` | the byte length, or nil on failure |
| `string?` | a failure reason, when unsuccessful |
###### Raises
- when the file is closed
##### `seek`
```nupp
seek: function seek(self, offset: integer?, origin: Origin?): integer?, string?
```
Moves the cursor and answers where it landed.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `any` | this file |
| `offset` | `integer?` | the distance to move |
| `origin` | `Origin?` | what the offset is measured from, defaulting to the start |
###### Returns
| Type | Description |
| --- | --- |
| `integer?` | the new position, or nil on failure |
| `string?` | a failure reason, when unsuccessful |
###### Raises
- when origin names no seek origin
##### `position`
```nupp
position: function position(self): integer?, string?
```
Answers the cursor's current position.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `any` | this file |
###### Returns
| Type | Description |
| --- | --- |
| `integer?` | the position, or nil on failure |
| `string?` | a failure reason, when unsuccessful |
##### `flush`
```nupp
flush: function flush(self): boolean, string?
```
Pushes buffered writes at the operating system.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `any` | this file |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | whether the flush succeeded |
| `string?` | a failure reason, when unsuccessful |
###### Raises
- when the file is closed
##### `isReleased`
```nupp
isReleased: function isReleased(self): boolean
```
Whether this file has been closed.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `any` | this file |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | whether it is closed |
##### `drop`
```nupp
drop: nosuspend function(takes self: File): nil
```
Closes the file as an ownership terminal.
Repeated calls are safe. Declared rather than written inline because the
cleanup contract needs the `nosuspend`, and an inline method cannot carry
one.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `takes self` | `File` | this file, spent by the call |
###### Returns
| Type | Description |
| --- | --- |
| `nil` | |
##### `close`
```nupp
close: function close(takes self): boolean
```
Closes the file and reports whether it succeeded.
Repeated calls are safe.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `takes self` | `any` | this file, spent by the call |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | whether the close succeeded |
### `FileReader` _record_
```nupp
record FileReader is io.Reader
drop: nosuspend function(takes self: FileReader): nil
function read(self, count: integer): (string?, string?) end
function readSpan(self, exclusive destination: span.Writable): (integer?, string?) end
function readInto(self, exclusive destination: io.Buffer, offset: integer?, count: integer?): (integer?, string?) end
function transferTo(self, exclusive destination: io.Writer): (integer?, string?) end
function close(takes self): nil end
end
```
A forward-only reader over an open file.
#### Methods
##### `drop`
```nupp
drop: nosuspend function(takes self: FileReader): nil
```
Releases it as an ownership terminal.
Repeated calls are safe. Declared rather than written inline: a cleanup
contract is `nosuspend`, and an inline method cannot carry one.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `takes self` | `FileReader` | this owner, spent by the call |
###### Returns
| Type | Description |
| --- | --- |
| `nil` | |
##### `read`
```nupp
read: function read(self, count: integer): string?, string?
```
Reads up to `count` bytes.
An empty answer is the end of the file.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `any` | this reader |
| `count` | `integer` | the most bytes to read |
###### Returns
| Type | Description |
| --- | --- |
| `string?` | the bytes, or nil when the reader or its file is closed |
| `string?` | why it could not read, when unsuccessful |
###### Raises
- when count is not a positive integer
##### `readSpan`
```nupp
readSpan: function readSpan(self, exclusive destination: span.Writable): integer?, string?
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `any` | |
| `exclusive destination` | `span.Writable\` | |
###### Returns
| Type | Description |
| --- | --- |
| `integer?` | |
| `string?` | |
##### `readInto`
```nupp
readInto: function readInto(self, exclusive destination: io.Buffer, offset: integer?, count: integer?): integer?, string?
```
Reads into a buffer.
A zero answer is the end of the file.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `any` | this reader |
| `exclusive destination` | `io.Buffer` | the buffer to write into |
| `offset` | `integer?` | where in the destination to start, or the beginning |
| `count` | `integer?` | the most bytes to read |
###### Returns
| Type | Description |
| --- | --- |
| `integer?` | how many bytes were read, or nil on failure |
| `string?` | why it could not read, when unsuccessful |
##### `transferTo`
```nupp
transferTo: function transferTo(self, exclusive destination: io.Writer): integer?, string?
```
Reads the rest of the file into a writer.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `any` | this reader |
| `exclusive destination` | `io.Writer` | the writer to fill |
###### Returns
| Type | Description |
| --- | --- |
| `integer?` | how many bytes moved, or nil on failure |
| `string?` | why it could not, when unsuccessful |
##### `close`
```nupp
close: function close(takes self): nil
```
Closes the reader without closing the file behind it.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `takes self` | `any` | this reader, spent by the call |
###### Returns
| Type | Description |
| --- | --- |
| `nil` | |
### `FileWriter` _record_
```nupp
record FileWriter is io.Writer
drop: nosuspend function(takes self: FileWriter): nil
function write(exclusive self, bytes: string): (boolean, string?) end
function writeSpan(exclusive self, borrows source: span.ByteSpan): (integer?, string?) end
function flush(self): (boolean, string?) end
function close(takes self): nil end
end
```
An appending writer over an open file.
#### Methods
##### `drop`
```nupp
drop: nosuspend function(takes self: FileWriter): nil
```
Releases it as an ownership terminal.
Repeated calls are safe. Declared rather than written inline: a cleanup
contract is `nosuspend`, and an inline method cannot carry one.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `takes self` | `FileWriter` | this owner, spent by the call |
###### Returns
| Type | Description |
| --- | --- |
| `nil` | |
##### `write`
```nupp
write: function write(exclusive self, bytes: string): boolean, string?
```
Writes bytes at the file's current position.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `exclusive self` | `any` | this writer |
| `bytes` | `string` | the bytes to write |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | whether they were written |
| `string?` | why they were not, when unsuccessful |
###### Raises
- when bytes is not a string
##### `writeSpan`
```nupp
writeSpan: function writeSpan(exclusive self, borrows source: span.ByteSpan): integer?, string?
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `exclusive self` | `any` | |
| `borrows source` | `span.ByteSpan` | |
###### Returns
| Type | Description |
| --- | --- |
| `integer?` | |
| `string?` | |
##### `flush`
```nupp
flush: function flush(self): boolean, string?
```
Pushes the file's buffered writes at the operating system.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `any` | this writer |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | whether the flush succeeded |
| `string?` | why it did not, when it did not |
##### `close`
```nupp
close: function close(takes self): nil
```
Closes the writer without closing the file behind it.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `takes self` | `any` | this writer, spent by the call |
###### Returns
| Type | Description |
| --- | --- |
| `nil` | |
### `Info` _record_
```nupp
record Info
kind: Kind
size: integer
modified: number
readOnly: boolean
end
```
One resolved path's attributes.
#### Fields
##### `kind`
```nupp
kind: Kind
```
What the path refers to, after following symbolic links.
##### `size`
```nupp
size: integer
```
The byte length of a file's contents.
##### `modified`
```nupp
modified: number
```
Seconds since the Unix epoch, with a fractional part where the platform
records one.
##### `readOnly`
```nupp
readOnly: boolean
```
Whether the platform refuses writes to this path.
### `Kind` _type_
```nupp
type Kind = "file" | "directory" | "symlink" | "other"
```
What a resolved path refers to. A `symlink` answer only ever comes from
`isSymlink`, since every other operation follows the link first.
### `LineIterator` _type_
```nupp
type LineIterator = function(): string?
```
Answers each line in turn, and nil at the end of the file.
### `Mode` _type_
```nupp
type Mode = "r" | "w" | "a" | "r+" | "w+" | "a+"
```
How an open file may be used. The three update modes read and write: `r+` needs an
existing file, `w+` truncates one, and `a+` appends.
### `Origin` _type_
```nupp
type Origin = "set" | "current" | "end"
```
What a seek offset is measured from.
### `TemporaryOptions` _record_
```nupp
record TemporaryOptions
directory: (string | paths.Path)?
prefix: string?
suffix: string?
end
```
Where a temporary is created, and what surrounds its generated name.
#### Fields
##### `directory`
```nupp
directory: (string | paths.Path)?
```
Where to create it, or the platform's temporary directory when omitted.
##### `prefix`
```nupp
prefix: string?
```
Text before the generated part.
##### `suffix`
```nupp
suffix: string?
```
Text after the generated part, such as an extension.
### `TemporaryPath` _record_
```nupp
record TemporaryPath
function toString(self): string end
function persist(self, destination: string | paths.Path): (boolean, string?) end
function isReleased(self): boolean end
drop: nosuspend function(takes self: TemporaryPath): nil
function close(takes self): boolean end
metamethod __tostring: function(self): string
end
```
A created temporary path, and the obligation to settle it.
Closing removes it. `persist` moves it somewhere permanent instead and discharges
the obligation, which is the whole reason to make one: write to a name nobody else
can take, then put it where it belongs.
#### Methods
##### `toString`
```nupp
toString: function toString(self): string
```
Returns the created path.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `any` | this temporary path |
###### Returns
| Type | Description |
| --- | --- |
| `string` | the path text |
##### `persist`
```nupp
persist: function persist(self, destination: string | paths.Path): boolean, string?
```
Moves this path to a permanent destination, replacing what is there.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `any` | this temporary path |
| `destination` | `string | paths.Path` | where to move it |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | whether the move happened |
| `string?` | a failure reason, when unsuccessful |
##### `isReleased`
```nupp
isReleased: function isReleased(self): boolean
```
Whether this path has been removed or persisted.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `any` | this temporary path |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | whether it is settled |
##### `drop`
```nupp
drop: nosuspend function(takes self: TemporaryPath): nil
```
Removes the path as an ownership terminal.
Repeated calls, and a call after `persist`, are safe and do nothing. Declared
rather than written inline because the cleanup contract needs the `nosuspend`,
and an inline method cannot carry one.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `takes self` | `TemporaryPath` | this temporary path, spent by the call |
###### Returns
| Type | Description |
| --- | --- |
| `nil` | |
##### `close`
```nupp
close: function close(takes self): boolean
```
Removes the path and reports whether it succeeded.
Repeated calls, and a call after `persist`, are safe and do nothing.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `takes self` | `any` | this temporary path, spent by the call |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | whether the removal succeeded |
##### `__tostring`
```nupp
__tostring: function(self): string
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `?` | `self` | |
###### Returns
| Type | Description |
| --- | --- |
| `string` | |
### `UserFolder` _type_
```nupp
type UserFolder = "home" | "documents" | "downloads" | "desktop" | "pictures" | "music" | "videos"
```
A well-known user folder. Resolved from the environment, so a desktop that records
its folders elsewhere is not consulted.
## Functions
### `append` _function_
```nupp
function append(path: string | paths.Path, bytes: string | io.ByteView): boolean, string?
```
Adds to the end of a file, creating it when it does not exist.
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `path` | `string | paths.Path` | the file to extend |
| `bytes` | `string | io.ByteView` | what to add |
#### Returns
| Type | Description |
| --- | --- |
| `boolean` | whether the write succeeded |
| `string?` | a failure reason, when unsuccessful |
### `copy` _function_
```nupp
function copy(from: string | paths.Path, to: string | paths.Path): boolean, string?
```
Copies a file's contents over a destination.
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `from` | `string | paths.Path` | the file to copy |
| `to` | `string | paths.Path` | where to copy it |
#### Returns
| Type | Description |
| --- | --- |
| `boolean` | whether the copy succeeded |
| `string?` | a failure reason, when unsuccessful |
### `createDirectory` _function_
```nupp
function createDirectory(path: string | paths.Path): boolean, string?
```
Creates a directory and every missing parent.
An existing directory succeeds.
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `path` | `string | paths.Path` | the directory to create |
#### Returns
| Type | Description |
| --- | --- |
| `boolean` | whether the directory exists afterwards |
| `string?` | a failure reason, when unsuccessful |
### `createSymlink` _function_
```nupp
function createSymlink(target: string | paths.Path, link: string | paths.Path, kind: ("file" | "directory")?): boolean, string?
```
Creates a symbolic link.
`kind` selects Windows's directory link and is ignored elsewhere, because only
Windows distinguishes the two.
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `target` | `string | paths.Path` | what the link points at |
| `link` | `string | paths.Path` | where to create the link |
| `kind` | `("file" | "directory")?` | the link kind, defaulting to a file link |
#### Returns
| Type | Description |
| --- | --- |
| `boolean` | whether the link was created |
| `string?` | a failure reason, when unsuccessful |
#### Raises
- when kind names neither a file nor a directory link
### `createTemporaryDirectory` _function_
```nupp
function createTemporaryDirectory(options: TemporaryOptions?): affine(TemporaryPath?, destroyOwner), string?
```
Creates a uniquely named empty directory and hands over the obligation to remove or
persist it.
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `options` | `TemporaryOptions?` | where to create it and how to name it |
#### Returns
| Type | Description |
| --- | --- |
| `affine(TemporaryPath?, destroyOwner)` | the created path, or nil on failure |
| `string?` | a failure reason, when unsuccessful |
### `createTemporaryFile` _function_
```nupp
function createTemporaryFile(options: TemporaryOptions?): affine(TemporaryPath?, destroyOwner), string?
```
Creates a uniquely named empty file and hands over the obligation to remove or
persist it.
The file is created rather than merely proposed, so no second caller can take the
name in between.
Closing the returned path removes it. `persist` settles it instead, which is
what makes the pair a replacement for writing over a destination in place.
#### Examples
Write somewhere nobody else can take, then move it into place:
```nupp
do
local scratch = assert(nupp.io.files.createTemporaryFile({directory = "build"}))
assert(nupp.io.files.write(scratch:toString(), bytes))
assert(scratch:persist("build/output.bin"))
end
```
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `options` | `TemporaryOptions?` | where to create it and how to name it |
#### Returns
| Type | Description |
| --- | --- |
| `affine(TemporaryPath?, destroyOwner)` | the created path, or nil on failure |
| `string?` | a failure reason, when unsuccessful |
### `currentDirectory` _function_
```nupp
function currentDirectory(): string?, string?
```
Reads the process's current working directory.
#### Returns
| Type | Description |
| --- | --- |
| `string?` | the current directory, or nil on failure |
| `string?` | a failure reason, when unsuccessful |
### `exists` _function_
```nupp
function exists(path: string | paths.Path): boolean
```
Whether a path resolves to anything at all.
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `path` | `string | paths.Path` | the path to test |
#### Returns
| Type | Description |
| --- | --- |
| `boolean` | whether it resolves |
### `glob` _function_
```nupp
function glob(pattern: string | paths.Path): {string}?, string?
```
Expands a filesystem pattern into matching paths.
`*` and `?` match within one path component, character classes use `[abc]` or
`[!abc]`, and `**` crosses directory boundaries. The returned paths are sorted, and
no matches is an empty list.
#### Examples
Walk every source file under a directory:
```nupp
for _, match in ipairs(assert(nupp.io.files.glob("src/**/*.nupp"))) do
print(match)
end
```
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `pattern` | `string | paths.Path` | the filesystem pattern to expand |
#### Returns
| Type | Description |
| --- | --- |
| `{string}?` | the matching paths, or nil when the pattern or walk fails |
| `string?` | a failure reason, when unsuccessful |
### `info` _function_
```nupp
function info(path: string | paths.Path): Info?, string?
```
Describes one path, following symbolic links.
#### Examples
Read a file's size:
```nupp
local info = assert(nupp.io.files.info("nupp.lua"))
assert(info.kind == "file" and info.size > 0)
```
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `path` | `string | paths.Path` | the path to describe |
#### Returns
| Type | Description |
| --- | --- |
| `Info?` | the attributes, or nil when the path cannot be read |
| `string?` | a failure reason, when unsuccessful |
### `isDirectory` _function_
```nupp
function isDirectory(path: string | paths.Path): boolean
```
Whether a path resolves to a directory.
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `path` | `string | paths.Path` | the path to test |
#### Returns
| Type | Description |
| --- | --- |
| `boolean` | whether it is a directory |
### `isFile` _function_
```nupp
function isFile(path: string | paths.Path): boolean
```
Whether a path resolves to a regular file.
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `path` | `string | paths.Path` | the path to test |
#### Returns
| Type | Description |
| --- | --- |
| `boolean` | whether it is a file |
### `isSymlink` _function_
```nupp
function isSymlink(path: string | paths.Path): boolean
```
Whether a path is itself a symbolic link, without following it.
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `path` | `string | paths.Path` | the path to test |
#### Returns
| Type | Description |
| --- | --- |
| `boolean` | whether it is a symbolic link |
### `lines` _function_
```nupp
function lines(path: string | paths.Path): LineIterator?, string?
```
Iterates a file's lines, closing it at the end.
A trailing carriage return is removed, so a file written on either platform reads
the same. The iterator stops at the end of the file; abandoning it early leaves the
file open until it is collected.
failure channel to answer through
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `path` | `string | paths.Path` | the file to read |
#### Returns
| Type | Description |
| --- | --- |
| `LineIterator?` | an iterator answering each line, or nil on failure |
| `string?` | a failure reason, when unsuccessful |
#### Raises
- when a read fails once iteration has begun, since the iterator has no
### `list` _function_
```nupp
function list(path: string | paths.Path): {Entry}?, string?
```
Lists a directory's immediate children.
#### Examples
Count the modules beside a file:
```nupp
local entries = assert(nupp.io.files.list("src"))
for _, entry in ipairs(entries) do
print(entry.kind, entry.name)
end
```
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `path` | `string | paths.Path` | the directory to list |
#### Returns
| Type | Description |
| --- | --- |
| `{Entry}?` | the children in the platform's order, or nil on failure |
| `string?` | a failure reason, when unsuccessful |
### `open` _function_
```nupp
function open(path: string | paths.Path, mode: Mode?): affine(File?, destroyOwner), string?
```
Opens a file and hands over the obligation to close it.
#### Examples
Read a header without holding the whole file:
```nupp
do
local file = assert(nupp.io.files.open("image.png"))
print(file:newReader():read(8))
end
```
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `path` | `string | paths.Path` | the file to open |
| `mode` | `Mode?` | how it may be used, defaulting to reading |
#### Returns
| Type | Description |
| --- | --- |
| `affine(File?, destroyOwner)` | the open file, or nil on failure |
| `string?` | a failure reason, when unsuccessful |
#### Raises
- when mode names no open mode
### `pendingTransfers` _function_
```nupp
function pendingTransfers(): integer
```
How many whole-file transfers this program is still holding.
Whole-file reads, writes and copies settle on worker threads, and the lane that
runs them is bounded. This answers what it holds, which is what a program that
submitted more than it consumed needs to see.
#### Returns
| Type | Description |
| --- | --- |
| `integer` | the number of live transfers |
### `read` _function_
```nupp
function read(path: string | paths.Path): string?, string?
```
Reads a whole file.
#### Examples
Read a file's bytes, or report why not:
```nupp
local text, reason = nupp.io.files.read("nupp.lua")
assert(text, reason)
```
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `path` | `string | paths.Path` | the file to read |
#### Returns
| Type | Description |
| --- | --- |
| `string?` | the contents, or nil on failure |
| `string?` | a failure reason, when unsuccessful |
### `readLink` _function_
```nupp
function readLink(path: string | paths.Path): string?, string?
```
Reads a symbolic link's target without resolving it.
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `path` | `string | paths.Path` | the link to read |
#### Returns
| Type | Description |
| --- | --- |
| `string?` | the target text, or nil on failure |
| `string?` | a failure reason, when unsuccessful |
### `remove` _function_
```nupp
function remove(path: string | paths.Path, recursive: boolean?): boolean, string?
```
Removes a file, a symbolic link, or a directory.
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `path` | `string | paths.Path` | the path to remove |
| `recursive` | `boolean?` | whether to remove a directory's contents with it |
#### Returns
| Type | Description |
| --- | --- |
| `boolean` | whether the path was removed |
| `string?` | a failure reason, when unsuccessful |
### `rename` _function_
```nupp
function rename(from: string | paths.Path, to: string | paths.Path): boolean, string?
```
Renames a path, replacing an existing destination.
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `from` | `string | paths.Path` | the path to rename |
| `to` | `string | paths.Path` | the new path |
#### Returns
| Type | Description |
| --- | --- |
| `boolean` | whether the rename happened |
| `string?` | a failure reason, when unsuccessful |
### `setReadOnly` _function_
```nupp
function setReadOnly(path: string | paths.Path, readOnly: boolean): boolean, string?
```
Sets or clears a path's read-only attribute.
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `path` | `string | paths.Path` | the path to change |
| `readOnly` | `boolean` | whether to refuse writes |
#### Returns
| Type | Description |
| --- | --- |
| `boolean` | whether the attribute was set |
| `string?` | a failure reason, when unsuccessful |
### `userFolder` _function_
```nupp
function userFolder(which: UserFolder): string?, string?
```
Answers a well-known user folder.
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `which` | `UserFolder` | the folder to locate |
#### Returns
| Type | Description |
| --- | --- |
| `string?` | the folder, or nil when the platform has no such folder |
| `string?` | a failure reason, when unsuccessful |
#### Raises
- when which names no user folder
### `write` _function_
```nupp
function write(path: string | paths.Path, bytes: string | io.ByteView): boolean, string?
```
Writes a whole file, replacing its contents.
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `path` | `string | paths.Path` | the file to write |
| `bytes` | `string | io.ByteView` | the contents |
#### Returns
| Type | Description |
| --- | --- |
| `boolean` | whether the write succeeded |
| `string?` | a failure reason, when unsuccessful |
### `writeAtomic` _function_
```nupp
function writeAtomic(path: string | paths.Path, bytes: string | io.ByteView): boolean, string?
```
Writes a whole file through a temporary beside it, so an interrupted write leaves
the destination as it was rather than half replaced.
Prefer this to `write` wherever a reader may be looking at the destination
while it is being replaced, since `write` truncates in place.
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `path` | `string | paths.Path` | the file to write |
| `bytes` | `string | io.ByteView` | the contents |
#### Returns
| Type | Description |
| --- | --- |
| `boolean` | whether the write succeeded |
| `string?` | a failure reason, when unsuccessful |