# Module: `nupp.io`
# Byte buffers, readers and writers
`nupp.io` supplies in-memory byte I/O without requiring a stream framework. A buffer
holds its bytes in a LuaJIT FFI array, so using it adds no native dependency. Files and
processes build on the same reader and writer contracts when their native features are
selected; sockets and general asynchronous streams remain separate future layers.
## Buffers
`newBuffer()` creates an empty growable buffer. A string supplies initial bytes; an
integer reserves capacity without changing the length.
```nupp
local bytes = nupp.io.newBuffer("hello")
bytes:setString("!", bytes:length())
assert(bytes:getString() == "hello!")
bytes:ensureCapacity(4096)
assert(bytes:capacity() >= 4096)
bytes:resize(3)
assert(bytes:getString() == "hel")
```
Buffer offsets are zero-based. `getString(offset, count)` copies a range. `setString`
overwrites from an offset and grows the buffer as needed; a gap is filled with zero
bytes. `clear` sets the length to zero without discarding capacity. `resize` truncates or
zero-fills.
Capacity is the allocation, not a recorded number. Growing at least doubles it, so
appending through a writer costs amortized constant time per byte and `capacity()`
reports bytes that are actually held. `ensureCapacity` reserves at least the minimum
asked for.
`close()` releases the buffer, is safe to call repeatedly, and makes later operations
raise. `isReleased()` reports that state.
| Member | Purpose |
| --- | --- |
| `length()`, `capacity()` | Inspect logical and reserved byte counts. |
| `clear()`, `resize(length)` | Remove bytes, truncate, or zero-extend. |
| `ensureCapacity(minimum)` | Reserve without changing logical length. |
| `getString(offset?, count?)` | Copy all bytes or a zero-based range. |
| `setString(bytes, offset?)` | Overwrite and grow from a zero-based offset. |
| `view(offset?, count?)` | Retain an immutable snapshot range. |
| `newReader()`, `newWriter()` | Open directional in-memory I/O. |
| `isReleased()`, `close()` | Inspect or release ownership. |
## Byte views
`view(offset, count)` returns an immutable snapshot, not a mutable alias into the
buffer. It remains valid if the source buffer changes or closes:
```nupp
local buffer = nupp.io.newBuffer("header-body")
local header = buffer:view(0, 6)
buffer:clear()
assert(header:getString() == "header")
header:close()
```
A view can make a smaller view, open its own snapshot reader with `newReader`, report
its byte length, copy to a string, and be closed. Data and UTF-8 functions accept views
so callers can avoid coupling those APIs to a mutable buffer.
`ByteView` provides `newReader`, `length`, `getString`, `view`, `isReleased`,
and `close`. Its `view` offsets are zero-based like Buffer offsets.
## Readers
A `nupp.io.Reader` is a forward-only byte source. `newStringReader(text)` reads a string;
`buffer:newReader()` reads a snapshot of the buffer's current contents.
```nupp
local reader = nupp.io.newStringReader("abcdef")
assert(reader:read(2) == "ab")
local destination = nupp.io.newBuffer()
assert(reader:readInto(destination, 0, 3) == 3)
assert(destination:getString() == "cde")
assert(reader:read(8) == "f")
assert(reader:read(8) == "") -- EOF
```
`read(count)` returns at most `max(1, count)` bytes, so zero and negative counts still
make progress. It returns an empty string at EOF and `nil, reason` after close.
`readInto(buffer, offset, count)` returns zero at EOF. Its default count is 64 KiB.
`transferTo(writer)` copies the entire remaining source and returns the byte count.
| Reader member | Result |
| --- | --- |
| `read(count)` | `string?, reason?` |
| `readInto(buffer, offset?, count?)` | `integer?, reason?` |
| `transferTo(writer)` | `integer?, reason?` |
| `close()` | `boolean, reason?` |
## Writers
`buffer:newWriter()` clears the buffer and returns a forward-only writer targeting it.
```nupp
local destination = nupp.io.newBuffer()
local writer = destination:newWriter()
assert(writer:write("prefix:"))
local payload = nupp.io.newBuffer("body")
assert(writer:writeFrom(payload) == 4)
assert(writer:flush())
assert(destination:getString() == "prefix:body")
```
`write` returns a boolean. `writeFrom` and `writeView` return the byte count. All return
a reason when closed or when the destination was released. Writing a buffer into itself
is rejected. `flush` is a no-op for memory but is part of the common writer contract,
allowing a later file or socket writer to implement the same interface.
| Writer member | Result |
| --- | --- |
| `write(bytes)` | `boolean, reason?` |
| `writeFrom(buffer, offset?, count?)` | `integer?, reason?` |
| `writeView(view, offset?, count?)` | `integer?, reason?` |
| `flush()` | `boolean, reason?` |
| `close()` | `boolean, reason?` |
For filesystem names rather than file contents, see [`nupp.io.Path`](path-uri.md#paths).
## Child processes
`nupp.io.process` starts a child without exposing descriptors, platform handles, or
signals. Reaching the module selects the native process provider and the suspension
runtime it needs.
```nupp
local process = require("nupp.io.process")
local child, spawnReason = process.new({
args = {"cc", "--version"},
stdin = "null",
})
assert(child, spawnReason)
local result, communicateReason = child:communicate()
assert(result, communicateReason)
assert(result:succeeded(), result.errorOutput)
print(result.output)
assert(child:close())
```
`args[1]` is the program and the remaining entries are its arguments. `cwd` changes
the child's directory. `env` overlays inherited variables unless `clearEnv` starts
from an empty environment. Standard streams default to `"pipe"` and may instead be
`"inherit"` or `"null"`; stderr alone may be `"stdout"` to share stdout's actual
destination. `timeoutMs` is measured from spawn, not from the first wait.
`communicate({input?, maxOutputBytes?})` is the safe whole-process operation: it feeds stdin while
draining stdout and stderr together, closes stdin to deliver EOF, and waits for the
exit. Doing those operations sequentially can deadlock when a child fills one output
pipe while waiting for more input.
The concrete `Reader` and `Writer` satisfy the shared completion-oriented
`nupp.io.Reader` and `nupp.io.Writer` contracts and also expose the nonblocking `poll` and
`offer` operations needed by that combined drain. `asReader` and `asWriter` borrow the
same records through their shared interfaces; they do not allocate or duplicate the
native handle. The owning `Process` retains and eventually destroys that handle, so a
borrow may not outlive it.
Every wait is contextual. With no
[suspension handler](start/suspension-handlers.md) installed, it sleeps in the
platform readiness wait. Under a scheduler handler, the same call parks the
current task while the scheduler keeps running. Ready operations do neither.
[Suspension](start/suspension.md) explains how the same ordinary call takes
those paths and how several waits compose with `all`, `race`, or `batch`.
`Process.close()` is idempotent and is also its lexical `@drop`: it attempts every
stream release, terminates a child still running, waits for it to finish, and releases
the child handle. An `Exit` reports `exitCode`, `killed`, and `timedOut`; `succeeded()`
is true only for an ordinary zero exit.
Buffers, byte views, readers, writers, filesystem paths and URIs.
## Submodules
| Module | Description |
| --- | --- |
| `nupp.io.files` | Filesystem metadata, directory contents, and the operations that move names rather than bytes. |
| `nupp.io.http` | An optional asynchronous HTTP client whose sockets and TLS run in the native Reqwest/Tokio provider. |
| `nupp.io.process` | Running a child process. |
| `nupp.io.processtypes` | What a child process is, and what a platform has to provide to run one. |
## Constructors
### `newBuffer` _constructor_
Creates an owned growable byte buffer.
```nupp
local newBuffer: function(initial: integer | string?): Buffer
```
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `initial` | `integer | string?` | initial bytes, an initial capacity, or nothing for an empty buffer |
#### Returns
| Type | Description |
| --- | --- |
| `Buffer` | the new buffer |
### `newPath` _constructor_
Creates a path by joining one or more components.
#### Examples
Build a path from components:
```nupp
local source = nupp.io.newPath("src", "main.nupp")
assert(source:toString() == "src" .. nupp.io.separator() .. "main.nupp")
```
Normalize the path after joining its components:
```nupp
local source = nupp.io.newPath("src", "app", "..", "main.nupp"):normalize()
assert(source:fileName() == "main.nupp")
```
```nupp
local newPath: function(first: string, ...: string): Path
```
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `first` | `string` | the first path component |
| `...` | `string` | the remaining path components |
#### Returns
| Type | Description |
| --- | --- |
| `Path` | the new path |
### `newStringReader` _constructor_
Creates a forward-only reader over a string.
```nupp
local newStringReader: function(text: string): Reader
```
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `text` | `string` | the bytes to read |
#### Returns
| Type | Description |
| --- | --- |
| `Reader` | the new reader |
### `newURI` _constructor_
Parses and normalizes an absolute URI.
```nupp
local newURI: function(value: string | URI.Components): (URI?, string?)
```
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `value` | `string | URI.Components` | URI text or its individual components |
#### Returns
| Type | Description |
| --- | --- |
| `URI?` | the parsed URI, or nil on failure |
| `string?` | a failure reason, when unsuccessful |
## Types
### `Buffer` _interface_
An owned growable byte sequence. All offsets are zero-based.
```nupp
interface Buffer
newReader: function(self: Buffer): Reader
newWriter: function(self: Buffer): Writer
length: function(self: Buffer): integer
capacity: function(self: Buffer): integer
clear: function(self: Buffer)
ensureCapacity: function(self: Buffer, minimum: integer)
resize: function(self: Buffer, length: integer)
getString: function(self: Buffer, offset: integer?, count: integer?): string
setString: function(self: Buffer, bytes: string, offset: integer?)
view: function(self: Buffer, offset: integer?, count: integer?): ByteView
isReleased: function(self: Buffer): boolean
close: function(self: Buffer): (boolean, string?)
end
```
#### Methods
##### `newReader`
Opens a reader over a snapshot of the current bytes.
```nupp
newReader: function(self: Buffer): Reader
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `Buffer` | this buffer |
###### Returns
| Type | Description |
| --- | --- |
| `Reader` | the new reader |
##### `newWriter`
Clears the buffer and opens a writer that targets it.
```nupp
newWriter: function(self: Buffer): Writer
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `Buffer` | this buffer |
###### Returns
| Type | Description |
| --- | --- |
| `Writer` | the new writer |
##### `length`
Reports the logical byte length.
```nupp
length: function(self: Buffer): integer
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `Buffer` | this buffer |
###### Returns
| Type | Description |
| --- | --- |
| `integer` | the byte length |
##### `capacity`
Reports the reserved byte capacity.
```nupp
capacity: function(self: Buffer): integer
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `Buffer` | this buffer |
###### Returns
| Type | Description |
| --- | --- |
| `integer` | the byte capacity |
##### `clear`
Removes every byte without discarding reserved capacity.
```nupp
clear: function(self: Buffer)
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `Buffer` | this buffer |
##### `ensureCapacity`
Reserves at least a byte capacity without changing the length.
```nupp
ensureCapacity: function(self: Buffer, minimum: integer)
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `Buffer` | this buffer |
| `minimum` | `integer` | the minimum capacity |
##### `resize`
Changes the byte length, truncating or zero-filling as needed.
```nupp
resize: function(self: Buffer, length: integer)
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `Buffer` | this buffer |
| `length` | `integer` | the new byte length |
##### `getString`
Copies a byte range into a string.
```nupp
getString: function(self: Buffer, offset: integer?, count: integer?): string
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `Buffer` | this buffer |
| `offset` | `integer?` | the zero-based start, or zero when omitted |
| `count` | `integer?` | the number of bytes, or the rest of the buffer when omitted |
###### Returns
| Type | Description |
| --- | --- |
| `string` | the copied bytes |
##### `setString`
Overwrites bytes at an offset, growing and zero-filling any gap.
```nupp
setString: function(self: Buffer, bytes: string, offset: integer?)
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `Buffer` | this buffer |
| `bytes` | `string` | the bytes to write |
| `offset` | `integer?` | the zero-based start, or zero when omitted |
##### `view`
Retains an immutable snapshot of a byte range.
```nupp
view: function(self: Buffer, offset: integer?, count: integer?): ByteView
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `Buffer` | this buffer |
| `offset` | `integer?` | the zero-based start, or zero when omitted |
| `count` | `integer?` | the number of bytes, or the rest of the buffer when omitted |
###### Returns
| Type | Description |
| --- | --- |
| `ByteView` | the retained byte view |
##### `isReleased`
Reports whether this buffer has been released.
```nupp
isReleased: function(self: Buffer): boolean
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `Buffer` | this buffer |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | whether the buffer is released |
##### `close`
Releases this buffer. Repeated calls are safe.
```nupp
close: function(self: Buffer): (boolean, string?)
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `Buffer` | this buffer |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | whether the release succeeded |
| `string?` | a failure reason, when unsuccessful |
### `ByteView` _interface_
An immutable snapshot of bytes.
```nupp
interface ByteView
newReader: function(self: ByteView): Reader
length: function(self: ByteView): integer
getString: function(self: ByteView): string
view: function(self: ByteView, offset: integer?, count: integer?): ByteView
isReleased: function(self: ByteView): boolean
close: function(self: ByteView): (boolean, string?)
end
```
#### Methods
##### `newReader`
Opens a reader over this snapshot.
```nupp
newReader: function(self: ByteView): Reader
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `ByteView` | this byte view |
###### Returns
| Type | Description |
| --- | --- |
| `Reader` | the new reader |
##### `length`
Reports the number of bytes in this snapshot.
```nupp
length: function(self: ByteView): integer
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `ByteView` | this byte view |
###### Returns
| Type | Description |
| --- | --- |
| `integer` | the byte length |
##### `getString`
Copies all bytes into a string.
```nupp
getString: function(self: ByteView): string
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `ByteView` | this byte view |
###### Returns
| Type | Description |
| --- | --- |
| `string` | the copied bytes |
##### `view`
Retains an immutable subrange of this snapshot.
```nupp
view: function(self: ByteView, offset: integer?, count: integer?): ByteView
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `ByteView` | this byte view |
| `offset` | `integer?` | the zero-based start, or zero when omitted |
| `count` | `integer?` | the number of bytes, or the rest of the view when omitted |
###### Returns
| Type | Description |
| --- | --- |
| `ByteView` | the retained subrange |
##### `isReleased`
Reports whether this snapshot has been released.
```nupp
isReleased: function(self: ByteView): boolean
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `ByteView` | this byte view |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | whether the view is released |
##### `close`
Releases this snapshot. Repeated calls are safe.
```nupp
close: function(self: ByteView): (boolean, string?)
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `ByteView` | this byte view |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | whether the release succeeded |
| `string?` | a failure reason, when unsuccessful |
### `Files` _record_
Filesystem metadata and directory contents.
Every operation here answers before a transfer could have started: it reads a
name, a listing, or an attribute. Reading and writing a file's bytes is a
separate layer.
```nupp
record Files
type Kind = "file" | "directory" | "symlink" | "other"
type UserFolder = "home" | "documents" | "downloads" | "desktop" | "pictures" | "music" | "videos"
record Info
kind: Kind
size: integer
modified: number
readOnly: boolean
end
type Mode = "r" | "w" | "a" | "r+" | "w+" | "a+"
type Origin = "set" | "current" | "end"
type LineIterator = function(): string?
record File
newReader: nosuspend function(self: File): nupp.io.Reader
newWriter: nosuspend function(self: File): nupp.io.Writer
size: nosuspend function(self: File): (integer?, string?)
seek: nosuspend function(self: File, offset: integer?, origin: Origin?): (integer?, string?)
position: nosuspend function(self: File): (integer?, string?)
flush: nosuspend function(self: File): (boolean, string?)
isReleased: nosuspend function(self: File): boolean
@drop
close: nosuspend function(takes self: File): boolean
end
record TemporaryPath
toString: nosuspend function(self: TemporaryPath): string
persist: nosuspend function(self: TemporaryPath, destination: string | nupp.io.Path): (boolean, string?)
isReleased: nosuspend function(self: TemporaryPath): boolean
@drop
close: nosuspend function(takes self: TemporaryPath): boolean
end
record Entry
name: string
kind: Kind
end
record TemporaryOptions
directory: (string | nupp.io.Path)?
prefix: string?
suffix: string?
end
end
```
#### Types
##### `Info` _record_
One resolved path's attributes.
###### Fields
| Name | Type | Description |
| --- | --- | --- |
| `kind` | `Kind` | What the path refers to, after following symbolic links. |
| `size` | `integer` | The byte length of a file's contents. |
| `modified` | `number` | Seconds since the Unix epoch, with a fractional part where the platform records one. |
| `readOnly` | `boolean` | Whether the platform refuses writes to this path. |
##### `File` _record_
An open file, and the obligation to close it.
Readers and writers opened from it satisfy the same [`nupp.io.Reader`
and `nupp.io.Writer`](../docs/io.md) contracts a buffer's do, so code
written against those works over a file without knowing one is there.
###### Methods
###### `newReader`
Opens a forward-only reader at the file's current position.
```nupp
newReader: nosuspend function(self: File): nupp.io.Reader
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `File` | this file |
###### Returns
| Type | Description |
| --- | --- |
| `nupp.io.Reader` | the new reader |
###### `newWriter`
Opens a forward-only writer at the file's current position.
```nupp
newWriter: nosuspend function(self: File): nupp.io.Writer
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `File` | this file |
###### Returns
| Type | Description |
| --- | --- |
| `nupp.io.Writer` | the new writer |
###### `size`
Answers the file's byte length without moving the cursor.
```nupp
size: nosuspend function(self: File): (integer?, string?)
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `File` | this file |
###### Returns
| Type | Description |
| --- | --- |
| `integer?` | the byte length, or nil on failure |
| `string?` | a failure reason, when unsuccessful |
###### `seek`
Moves the cursor and answers where it landed.
```nupp
seek: nosuspend function(
self: File,
offset: integer?,
origin: Origin?
): (integer?, string?)
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `File` | 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 |
###### `position`
Answers the cursor's current position.
```nupp
position: nosuspend function(self: File): (integer?, string?)
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `File` | this file |
###### Returns
| Type | Description |
| --- | --- |
| `integer?` | the position, or nil on failure |
| `string?` | a failure reason, when unsuccessful |
###### `flush`
Pushes buffered writes at the operating system.
```nupp
flush: nosuspend function(self: File): (boolean, string?)
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `File` | this file |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | whether the flush succeeded |
| `string?` | a failure reason, when unsuccessful |
###### `isReleased`
Whether this file has been closed.
```nupp
isReleased: nosuspend function(self: File): boolean
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `File` | this file |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | whether it is closed |
###### `close`
```nupp
close: nosuspend function(takes self: File): boolean
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `File` | |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | |
##### `TemporaryPath` _record_
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`
Answers the created path.
```nupp
toString: nosuspend function(self: TemporaryPath): string
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `TemporaryPath` | this temporary path |
###### Returns
| Type | Description |
| --- | --- |
| `string` | the path text |
###### `persist`
Moves this path to a permanent destination, replacing what is there.
```nupp
persist: nosuspend function(self: TemporaryPath, destination: string | nupp.io.Path): (boolean, string?)
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `TemporaryPath` | this temporary path |
| `destination` | `string | nupp.io.Path` | where to move it |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | whether the move happened |
| `string?` | a failure reason, when unsuccessful |
###### `isReleased`
Whether this path has been removed or persisted.
```nupp
isReleased: nosuspend function(self: TemporaryPath): boolean
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `TemporaryPath` | this temporary path |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | whether it is settled |
###### `close`
```nupp
close: nosuspend function(takes self: TemporaryPath): boolean
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `TemporaryPath` | |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | |
##### `Entry` _record_
One directory child, as the directory itself describes it.
###### Fields
| Name | Type | Description |
| --- | --- | --- |
| `name` | `string` | The child's name, without any directory part. |
| `kind` | `Kind` | What the entry itself is, without following a symbolic link. |
##### `TemporaryOptions` _record_
The generated part of a temporary name.
###### Fields
| Name | Type | Description |
| --- | --- | --- |
| `directory` | `(string | nupp.io.Path)?` | Where to create it, or the platform's temporary directory when omitted. |
| `prefix` | `string?` | Text before the generated part. |
| `suffix` | `string?` | Text after the generated part, such as an extension. |
#### Fields
| Name | Type | Description |
| --- | --- | --- |
| `Kind` | `typeAlias` | What a resolved path refers to. A `symlink` answer only ever comes from `isSymlink`, since every other operation follows the link first. |
| `UserFolder` | `typeAlias` | A well-known user folder. Resolved from the environment, so a desktop that records its folders elsewhere is not consulted. |
| `Mode` | `typeAlias` | 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` | `typeAlias` | What a seek offset is measured from. |
| `LineIterator` | `typeAlias` | Answers each line in turn, and nil at the end of the file. |
### `Path` _record_
An immutable platform-native UTF-8 filesystem path.
```nupp
record Path
toString: function(self: Path): string
join: function(self: Path, ...: string | Path): Path
normalize: function(self: Path): Path
absolute: function(self: Path): (Path?, string?)
resolve: function(self: Path, ...: string | Path): (Path?, string?)
canonicalize: function(self: Path): (Path?, string?)
relativeTo: function(self: Path, base: string | Path): (Path?, string?)
parent: function(self: Path): Path?
fileName: function(self: Path): string?
stem: function(self: Path): string?
extension: function(self: Path): string?
withFileName: function(self: Path, name: string): Path
withExtension: function(self: Path, extension: string): Path
isAbsolute: function(self: Path): boolean
isRelative: function(self: Path): boolean
end
```
#### Methods
##### `toString`
Returns the native UTF-8 path text.
```nupp
toString: function(self: Path): string
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `Path` | this path |
###### Returns
| Type | Description |
| --- | --- |
| `string` | the path text |
##### `join`
Appends path components using the current platform's rules.
```nupp
join: function(self: Path, ...: string | Path): Path
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `Path` | this path |
| `...` | `string | Path` | the components to append |
###### Returns
| Type | Description |
| --- | --- |
| `Path` | the joined path |
##### `normalize`
Removes lexical `.` and `..` components without accessing the filesystem.
```nupp
normalize: function(self: Path): Path
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `Path` | this path |
###### Returns
| Type | Description |
| --- | --- |
| `Path` | the normalized path |
##### `absolute`
Resolves a relative path against the current working directory.
```nupp
absolute: function(self: Path): (Path?, string?)
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `Path` | this path |
###### Returns
| Type | Description |
| --- | --- |
| `Path?` | the absolute path, or nil on failure |
| `string?` | a failure reason, when unsuccessful |
##### `resolve`
Makes this path absolute, appends components, and normalizes the result.
```nupp
resolve: function(self: Path, ...: string | Path): (Path?, string?)
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `Path` | this path |
| `...` | `string | Path` | the components to append |
###### Returns
| Type | Description |
| --- | --- |
| `Path?` | the resolved path, or nil on failure |
| `string?` | a failure reason, when unsuccessful |
##### `canonicalize`
Resolves filesystem links and returns the real path.
```nupp
canonicalize: function(self: Path): (Path?, string?)
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `Path` | this path |
###### Returns
| Type | Description |
| --- | --- |
| `Path?` | the canonical path, or nil on failure |
| `string?` | a failure reason, when unsuccessful |
##### `relativeTo`
Computes this path relative to a compatible base path.
```nupp
relativeTo: function(self: Path, base: string | Path): (Path?, string?)
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `Path` | this path |
| `base` | `string | Path` | the path to make this value relative to |
###### Returns
| Type | Description |
| --- | --- |
| `Path?` | the relative path, or nil on failure |
| `string?` | a failure reason, when unsuccessful |
##### `parent`
Returns the parent path, when one exists.
```nupp
parent: function(self: Path): Path?
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `Path` | this path |
###### Returns
| Type | Description |
| --- | --- |
| `Path?` | the parent path |
##### `fileName`
Returns the final path component, when one exists.
```nupp
fileName: function(self: Path): string?
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `Path` | this path |
###### Returns
| Type | Description |
| --- | --- |
| `string?` | the file name |
##### `stem`
Returns the file name without its final extension, when one exists.
```nupp
stem: function(self: Path): string?
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `Path` | this path |
###### Returns
| Type | Description |
| --- | --- |
| `string?` | the file stem |
##### `extension`
Returns the final file-name extension without its separator.
```nupp
extension: function(self: Path): string?
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `Path` | this path |
###### Returns
| Type | Description |
| --- | --- |
| `string?` | the extension |
##### `withFileName`
Replaces the final path component.
```nupp
withFileName: function(self: Path, name: string): Path
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `Path` | this path |
| `name` | `string` | the replacement file name, without path separators |
###### Returns
| Type | Description |
| --- | --- |
| `Path` | the modified path |
##### `withExtension`
Replaces the final file-name extension.
```nupp
withExtension: function(self: Path, extension: string): Path
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `Path` | this path |
| `extension` | `string` | the replacement extension, without path separators |
###### Returns
| Type | Description |
| --- | --- |
| `Path` | the modified path |
##### `isAbsolute`
Reports whether this path is absolute.
```nupp
isAbsolute: function(self: Path): boolean
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `Path` | this path |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | whether the path is absolute |
##### `isRelative`
Reports whether this path is relative.
```nupp
isRelative: function(self: Path): boolean
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `Path` | this path |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | whether the path is relative |
### `Reader` _interface_
A forward-only byte source. An empty read or zero-byte readInto is EOF.
```nupp
interface Reader
read: function(self: Reader, count: integer): (string?, string?)
readInto: function(self: Reader, destination: Buffer, offset: integer?, count: integer?): (integer?, string?)
transferTo: function(self: Reader, destination: Writer): (integer?, string?)
close: function(self: Reader): (boolean, string?)
end
```
#### Methods
##### `read`
Reads the next bytes, returning an empty string at EOF.
```nupp
read: function(self: Reader, count: integer): (string?, string?)
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `Reader` | this reader |
| `count` | `integer` | the maximum number of bytes; non-positive values still read one |
###### Returns
| Type | Description |
| --- | --- |
| `string?` | the bytes, or nil on failure |
| `string?` | a failure reason, when unsuccessful |
##### `readInto`
Reads bytes into a buffer, returning zero at EOF.
```nupp
readInto: function(
self: Reader,
destination: Buffer,
offset: integer?,
count: integer?
): (integer?, string?)
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `Reader` | this reader |
| `destination` | `Buffer` | the buffer to append to or overwrite |
| `offset` | `integer?` | the zero-based destination offset, or zero when omitted |
| `count` | `integer?` | the maximum bytes to read, or 64 KiB when omitted |
###### Returns
| Type | Description |
| --- | --- |
| `integer?` | the number of bytes read, or nil on failure |
| `string?` | a failure reason, when unsuccessful |
##### `transferTo`
Copies all remaining bytes into a writer.
```nupp
transferTo: function(self: Reader, destination: Writer): (integer?, string?)
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `Reader` | this reader |
| `destination` | `Writer` | the writer to receive the bytes |
###### Returns
| Type | Description |
| --- | --- |
| `integer?` | the number of bytes copied, or nil on failure |
| `string?` | a failure reason, when unsuccessful |
##### `close`
Closes this reader. Repeated calls are safe.
```nupp
close: function(self: Reader): (boolean, string?)
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `Reader` | this reader |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | whether the close succeeded |
| `string?` | a failure reason, when unsuccessful |
### `URI` _record_
An immutable normalized absolute URI.
```nupp
record URI
record Components
scheme: string
userInfo: string?
host: string?
port: integer?
path: string?
query: string?
fragment: string?
end
toString: function(self: URI): string
scheme: function(self: URI): string
authority: function(self: URI): string?
username: function(self: URI): string
password: function(self: URI): string?
host: function(self: URI): string?
port: function(self: URI): integer?
path: function(self: URI): string
query: function(self: URI): string?
fragment: function(self: URI): string?
userInfo: function(self: URI): string?
withScheme: function(self: URI, scheme: string): URI
withUserInfo: function(self: URI, userInfo: string?): URI
withHost: function(self: URI, host: string?): URI
withPort: function(self: URI, port: integer?): URI
withPath: function(self: URI, path: string): URI
withQuery: function(self: URI, query: string?): URI
withFragment: function(self: URI, fragment: string?): URI
concatPath: function(self: URI, path: string): URI
withEndpoint: function(self: URI, endpoint: URI): URI
resolve: function(self: URI, reference: string): (URI?, string?)
end
```
#### Methods
##### `toString`
Returns the complete normalized URI text.
```nupp
toString: function(self: URI): string
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `URI` | this URI |
###### Returns
| Type | Description |
| --- | --- |
| `string` | the URI text |
##### `scheme`
Returns the normalized URI scheme.
```nupp
scheme: function(self: URI): string
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `URI` | this URI |
###### Returns
| Type | Description |
| --- | --- |
| `string` | the scheme |
##### `authority`
Returns the complete authority component, when present.
```nupp
authority: function(self: URI): string?
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `URI` | this URI |
###### Returns
| Type | Description |
| --- | --- |
| `string?` | the authority |
##### `username`
Returns the username component, or an empty string when absent.
```nupp
username: function(self: URI): string
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `URI` | this URI |
###### Returns
| Type | Description |
| --- | --- |
| `string` | the username |
##### `password`
Returns the password component, when present.
```nupp
password: function(self: URI): string?
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `URI` | this URI |
###### Returns
| Type | Description |
| --- | --- |
| `string?` | the password |
##### `host`
Returns the normalized host, when present.
```nupp
host: function(self: URI): string?
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `URI` | this URI |
###### Returns
| Type | Description |
| --- | --- |
| `string?` | the host |
##### `port`
Returns the port, when explicitly present.
```nupp
port: function(self: URI): integer?
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `URI` | this URI |
###### Returns
| Type | Description |
| --- | --- |
| `integer?` | the port |
##### `path`
Returns the URI path component.
```nupp
path: function(self: URI): string
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `URI` | this URI |
###### Returns
| Type | Description |
| --- | --- |
| `string` | the path |
##### `query`
Returns the query without its leading `?`, when present.
```nupp
query: function(self: URI): string?
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `URI` | this URI |
###### Returns
| Type | Description |
| --- | --- |
| `string?` | the query |
##### `fragment`
Returns the fragment without its leading `#`, when present.
```nupp
fragment: function(self: URI): string?
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `URI` | this URI |
###### Returns
| Type | Description |
| --- | --- |
| `string?` | the fragment |
##### `userInfo`
Returns the complete user-information component, when present.
```nupp
userInfo: function(self: URI): string?
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `URI` | this URI |
###### Returns
| Type | Description |
| --- | --- |
| `string?` | the user information |
##### `withScheme`
Returns a copy with a replacement scheme.
```nupp
withScheme: function(self: URI, scheme: string): URI
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `URI` | this URI |
| `scheme` | `string` | the replacement scheme |
###### Returns
| Type | Description |
| --- | --- |
| `URI` | the modified URI |
##### `withUserInfo`
Returns a copy with replacement or removed user information.
```nupp
withUserInfo: function(self: URI, userInfo: string?): URI
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `URI` | this URI |
| `userInfo` | `string?` | the replacement, or nil to remove it |
###### Returns
| Type | Description |
| --- | --- |
| `URI` | the modified URI |
##### `withHost`
Returns a copy with a replacement or removed host.
```nupp
withHost: function(self: URI, host: string?): URI
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `URI` | this URI |
| `host` | `string?` | the replacement, or nil to remove it |
###### Returns
| Type | Description |
| --- | --- |
| `URI` | the modified URI |
##### `withPort`
Returns a copy with a replacement or removed port.
```nupp
withPort: function(self: URI, port: integer?): URI
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `URI` | this URI |
| `port` | `integer?` | the replacement from 0 through 65535, or nil to remove it |
###### Returns
| Type | Description |
| --- | --- |
| `URI` | the modified URI |
##### `withPath`
Returns a copy with a replacement path.
```nupp
withPath: function(self: URI, path: string): URI
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `URI` | this URI |
| `path` | `string` | the replacement path |
###### Returns
| Type | Description |
| --- | --- |
| `URI` | the modified URI |
##### `withQuery`
Returns a copy with a replacement or removed query.
```nupp
withQuery: function(self: URI, query: string?): URI
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `URI` | this URI |
| `query` | `string?` | the replacement without `?`, or nil to remove it |
###### Returns
| Type | Description |
| --- | --- |
| `URI` | the modified URI |
##### `withFragment`
Returns a copy with a replacement or removed fragment.
```nupp
withFragment: function(self: URI, fragment: string?): URI
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `URI` | this URI |
| `fragment` | `string?` | the replacement without `#`, or nil to remove it |
###### Returns
| Type | Description |
| --- | --- |
| `URI` | the modified URI |
##### `concatPath`
Appends path text without interpreting it as a URI reference.
```nupp
concatPath: function(self: URI, path: string): URI
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `URI` | this URI |
| `path` | `string` | the path text to append |
###### Returns
| Type | Description |
| --- | --- |
| `URI` | the modified URI |
##### `withEndpoint`
Replaces the scheme and authority while retaining resource components.
```nupp
withEndpoint: function(self: URI, endpoint: URI): URI
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `URI` | this URI |
| `endpoint` | `URI` | the URI supplying the scheme and authority |
###### Returns
| Type | Description |
| --- | --- |
| `URI` | the endpoint-rerouted URI |
##### `resolve`
Resolves a URI reference according to RFC reference-resolution rules.
```nupp
resolve: function(self: URI, reference: string): (URI?, string?)
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `URI` | this URI |
| `reference` | `string` | the URI reference to resolve |
###### Returns
| Type | Description |
| --- | --- |
| `URI?` | the resolved URI, or nil on failure |
| `string?` | a failure reason, when unsuccessful |
#### Types
##### `Components` _record_
Components accepted when constructing an absolute URI.
###### Fields
| Name | Type | Description |
| --- | --- | --- |
| `scheme` | `string` | The required URI scheme. |
| `userInfo` | `string?` | Optional user information, including a password when needed. |
| `host` | `string?` | The optional host name or address. |
| `port` | `integer?` | The optional port from 0 through 65535. |
| `path` | `string?` | The path, or an empty path when omitted. |
| `query` | `string?` | The optional query without its leading `?`. |
| `fragment` | `string?` | The optional fragment without its leading `#`. |
### `Writer` _interface_
A forward-only byte destination.
```nupp
interface Writer
write: function(self: Writer, bytes: string): (boolean, string?)
writeFrom: function(self: Writer, source: Buffer, offset: integer?, count: integer?): (integer?, string?)
writeView: function(self: Writer, source: ByteView, offset: integer?, count: integer?): (integer?, string?)
flush: function(self: Writer): (boolean, string?)
close: function(self: Writer): (boolean, string?)
end
```
#### Methods
##### `write`
Writes a string of bytes.
```nupp
write: function(self: Writer, bytes: string): (boolean, string?)
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `Writer` | this writer |
| `bytes` | `string` | the bytes to write |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | whether the write succeeded |
| `string?` | a failure reason, when unsuccessful |
##### `writeFrom`
Copies bytes from a mutable buffer.
```nupp
writeFrom: function(self: Writer, source: Buffer, offset: integer?, count: integer?): (integer?, string?)
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `Writer` | this writer |
| `source` | `Buffer` | the source buffer |
| `offset` | `integer?` | the zero-based start, or zero when omitted |
| `count` | `integer?` | the number of bytes, or the rest of the buffer when omitted |
###### Returns
| Type | Description |
| --- | --- |
| `integer?` | the number of bytes written, or nil on failure |
| `string?` | a failure reason, when unsuccessful |
##### `writeView`
Copies bytes from an immutable view.
```nupp
writeView: function(
self: Writer,
source: ByteView,
offset: integer?,
count: integer?
): (integer?, string?)
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `Writer` | this writer |
| `source` | `ByteView` | the source byte view |
| `offset` | `integer?` | the zero-based start, or zero when omitted |
| `count` | `integer?` | the number of bytes, or the rest of the view when omitted |
###### Returns
| Type | Description |
| --- | --- |
| `integer?` | the number of bytes written, or nil on failure |
| `string?` | a failure reason, when unsuccessful |
##### `flush`
Flushes any buffered output.
```nupp
flush: function(self: Writer): (boolean, string?)
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `Writer` | this writer |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | whether the flush succeeded |
| `string?` | a failure reason, when unsuccessful |
##### `close`
Closes this writer. Repeated calls are safe.
```nupp
close: function(self: Writer): (boolean, string?)
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `Writer` | this writer |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | whether the close succeeded |
| `string?` | a failure reason, when unsuccessful |
## Functions
### `currentDirectory` _function_
Reads the process's current working directory.
```nupp
local currentDirectory: function(): (Path?, string?)
```
#### Returns
| Type | Description |
| --- | --- |
| `Path?` | the current directory, or nil on failure |
| `string?` | a failure reason, when unsuccessful |
### `isURI` _function_
Reports whether a value is a URI object.
```nupp
local isURI: function(value: any): boolean
```
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `value` | `any` | the value to inspect |
#### Returns
| Type | Description |
| --- | --- |
| `boolean` | whether the value is a URI |
### `separator` _function_
Returns the current platform's primary path separator.
```nupp
local separator: function(): string
```
#### Returns
| Type | Description |
| --- | --- |
| `string` | the path separator |
### `validate` _function_
Checks whether text is a valid absolute URI without retaining an object.
```nupp
local validate: function(text: string): (boolean, string?)
```
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `text` | `string` | the URI text to validate |
#### Returns
| Type | Description |
| --- | --- |
| `boolean` | whether the text is valid |
| `string?` | a failure reason, when invalid |