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