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.

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:

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:

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:

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:

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.

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:

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:

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

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:

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.

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:

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:

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:

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.

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

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

Module contents

Types

TypeKindDescription
EntryrecordOne directory child, as the directory itself describes it.
FilerecordAn open file, and the obligation to close it.
FileReaderrecordA forward-only reader over an open file.
FileWriterrecordAn appending writer over an open file.
InforecordOne resolved path's attributes.
KindtypeWhat a resolved path refers to.
LineIteratortypeAnswers each line in turn, and nil at the end of the file.
ModetypeHow an open file may be used.
OrigintypeWhat a seek offset is measured from.
TemporaryOptionsrecordWhere a temporary is created, and what surrounds its generated name.
TemporaryPathrecordA created temporary path, and the obligation to settle it.
UserFoldertypeA well-known user folder.

Functions

FunctionKindDescription
appendfunctionAdds to the end of a file, creating it when it does not exist.
copyfunctionCopies a file's contents over a destination.
createDirectoryfunctionCreates a directory and every missing parent.
createSymlinkfunctionCreates a symbolic link.
createTemporaryDirectoryfunctionCreates a uniquely named empty directory and hands over the obligation to remove or persist it.
createTemporaryFilefunctionCreates a uniquely named empty file and hands over the obligation to remove or persist it.
currentDirectoryfunctionReads the process's current working directory.
existsfunctionWhether a path resolves to anything at all.
globfunctionExpands a filesystem pattern into matching paths.
infofunctionDescribes one path, following symbolic links.
isDirectoryfunctionWhether a path resolves to a directory.
isFilefunctionWhether a path resolves to a regular file.
isSymlinkfunctionWhether a path is itself a symbolic link, without following it.
linesfunctionIterates a file's lines, closing it at the end.
listfunctionLists a directory's immediate children.
openfunctionOpens a file and hands over the obligation to close it.
pendingTransfersfunctionHow many whole-file transfers this program is still holding.
readfunctionReads a whole file.
readLinkfunctionReads a symbolic link's target without resolving it.
removefunctionRemoves a file, a symbolic link, or a directory.
renamefunctionRenames a path, replacing an existing destination.
setReadOnlyfunctionSets or clears a path's read-only attribute.
userFolderfunctionAnswers a well-known user folder.
writefunctionWrites a whole file, replacing its contents.
writeAtomicfunctionWrites a whole file through a temporary beside it, so an interrupted write leaves the destination as it was rather...

Types#

Entryrecord#

record Entry
    name: string
    kind: Kind
end

One directory child, as the directory itself describes it.

Fields

name#
name: string

The child's name, without any directory part.

kind#
kind: Kind

What the entry itself is, without following a symbolic link.

Filerecord#

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#
newReader: function newReader(self): FileReader

Opens a forward-only reader at the file's current position.

Arguments
NameTypeDescription
selfany

this file

Returns
TypeDescription
FileReader

the new reader

Raises
  • when the file is closed

newWriter#
newWriter: function newWriter(self): FileWriter

Opens a forward-only writer at the file's current position.

Arguments
NameTypeDescription
selfany

this file

Returns
TypeDescription
FileWriter

the new writer

Raises
  • when the file is closed

size#
size: function size(self): integer?, string?

Answers the file's byte length without moving the cursor.

Arguments
NameTypeDescription
selfany

this file

Returns
TypeDescription
integer?

the byte length, or nil on failure

string?

a failure reason, when unsuccessful

Raises
  • when the file is closed

seek#
seek: function seek(self, offset: integer?, origin: Origin?): integer?, string?

Moves the cursor and answers where it landed.

Arguments
NameTypeDescription
selfany

this file

offsetinteger?

the distance to move

originOrigin?

what the offset is measured from, defaulting to the start

Returns
TypeDescription
integer?

the new position, or nil on failure

string?

a failure reason, when unsuccessful

Raises
  • when origin names no seek origin

position#
position: function position(self): integer?, string?

Answers the cursor's current position.

Arguments
NameTypeDescription
selfany

this file

Returns
TypeDescription
integer?

the position, or nil on failure

string?

a failure reason, when unsuccessful

flush#
flush: function flush(self): boolean, string?

Pushes buffered writes at the operating system.

Arguments
NameTypeDescription
selfany

this file

Returns
TypeDescription
boolean

whether the flush succeeded

string?

a failure reason, when unsuccessful

Raises
  • when the file is closed

isReleased#
isReleased: function isReleased(self): boolean

Whether this file has been closed.

Arguments
NameTypeDescription
selfany

this file

Returns
TypeDescription
boolean

whether it is closed

drop#
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
NameTypeDescription
takes selfFile

this file, spent by the call

Returns
TypeDescription
nil
close#
close: function close(takes self): boolean

Closes the file and reports whether it succeeded.

Repeated calls are safe.

Arguments
NameTypeDescription
takes selfany

this file, spent by the call

Returns
TypeDescription
boolean

whether the close succeeded

FileReaderrecord#

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<uint8>): (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#
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
NameTypeDescription
takes selfFileReader

this owner, spent by the call

Returns
TypeDescription
nil
read#
read: function read(self, count: integer): string?, string?

Reads up to count bytes.

An empty answer is the end of the file.

Arguments
NameTypeDescription
selfany

this reader

countinteger

the most bytes to read

Returns
TypeDescription
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#
readSpan: function readSpan(self, exclusive destination: span.Writable<uint8>): integer?, string?
Arguments
NameTypeDescription
selfany
exclusive destinationspan.Writable<uint8>
Returns
TypeDescription
integer?
string?
readInto#
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
NameTypeDescription
selfany

this reader

exclusive destinationio.Buffer

the buffer to write into

offsetinteger?

where in the destination to start, or the beginning

countinteger?

the most bytes to read

Returns
TypeDescription
integer?

how many bytes were read, or nil on failure

string?

why it could not read, when unsuccessful

transferTo#
transferTo: function transferTo(self, exclusive destination: io.Writer): integer?, string?

Reads the rest of the file into a writer.

Arguments
NameTypeDescription
selfany

this reader

exclusive destinationio.Writer

the writer to fill

Returns
TypeDescription
integer?

how many bytes moved, or nil on failure

string?

why it could not, when unsuccessful

close#
close: function close(takes self): nil

Closes the reader without closing the file behind it.

Arguments
NameTypeDescription
takes selfany

this reader, spent by the call

Returns
TypeDescription
nil

FileWriterrecord#

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#
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
NameTypeDescription
takes selfFileWriter

this owner, spent by the call

Returns
TypeDescription
nil
write#
write: function write(exclusive self, bytes: string): boolean, string?

Writes bytes at the file's current position.

Arguments
NameTypeDescription
exclusive selfany

this writer

bytesstring

the bytes to write

Returns
TypeDescription
boolean

whether they were written

string?

why they were not, when unsuccessful

Raises
  • when bytes is not a string

writeSpan#
writeSpan: function writeSpan(exclusive self, borrows source: span.ByteSpan): integer?, string?
Arguments
NameTypeDescription
exclusive selfany
borrows sourcespan.ByteSpan
Returns
TypeDescription
integer?
string?
flush#
flush: function flush(self): boolean, string?

Pushes the file's buffered writes at the operating system.

Arguments
NameTypeDescription
selfany

this writer

Returns
TypeDescription
boolean

whether the flush succeeded

string?

why it did not, when it did not

close#
close: function close(takes self): nil

Closes the writer without closing the file behind it.

Arguments
NameTypeDescription
takes selfany

this writer, spent by the call

Returns
TypeDescription
nil

Inforecord#

record Info
    kind: Kind
    size: integer
    modified: number
    readOnly: boolean
end

One resolved path's attributes.

Fields

kind#
kind: Kind

What the path refers to, after following symbolic links.

size#
size: integer

The byte length of a file's contents.

modified#
modified: number

Seconds since the Unix epoch, with a fractional part where the platform records one.

readOnly#
readOnly: boolean

Whether the platform refuses writes to this path.

Kindtype#

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.

LineIteratortype#

type LineIterator = function(): string?

Answers each line in turn, and nil at the end of the file.

Modetype#

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.

Origintype#

type Origin = "set" | "current" | "end"

What a seek offset is measured from.

TemporaryOptionsrecord#

record TemporaryOptions
    directory: (string | paths.Path)?
    prefix: string?
    suffix: string?
end

Where a temporary is created, and what surrounds its generated name.

Fields

directory#
directory: (string | paths.Path)?

Where to create it, or the platform's temporary directory when omitted.

prefix#
prefix: string?

Text before the generated part.

suffix#
suffix: string?

Text after the generated part, such as an extension.

TemporaryPathrecord#

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#
toString: function toString(self): string

Returns the created path.

Arguments
NameTypeDescription
selfany

this temporary path

Returns
TypeDescription
string

the path text

persist#
persist: function persist(self, destination: string | paths.Path): boolean, string?

Moves this path to a permanent destination, replacing what is there.

Arguments
NameTypeDescription
selfany

this temporary path

destinationstring | paths.Path

where to move it

Returns
TypeDescription
boolean

whether the move happened

string?

a failure reason, when unsuccessful

isReleased#
isReleased: function isReleased(self): boolean

Whether this path has been removed or persisted.

Arguments
NameTypeDescription
selfany

this temporary path

Returns
TypeDescription
boolean

whether it is settled

drop#
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
NameTypeDescription
takes selfTemporaryPath

this temporary path, spent by the call

Returns
TypeDescription
nil
close#
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
NameTypeDescription
takes selfany

this temporary path, spent by the call

Returns
TypeDescription
boolean

whether the removal succeeded

__tostring#
__tostring: function(self): string
Arguments
NameTypeDescription
?self
Returns
TypeDescription
string

UserFoldertype#

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#

appendfunction#

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

NameTypeDescription
pathstring | paths.Path

the file to extend

bytesstring | io.ByteView

what to add

Returns

TypeDescription
boolean

whether the write succeeded

string?

a failure reason, when unsuccessful

copyfunction#

function copy(from: string | paths.Path, to: string | paths.Path): boolean, string?

Copies a file's contents over a destination.

Arguments

NameTypeDescription
fromstring | paths.Path

the file to copy

tostring | paths.Path

where to copy it

Returns

TypeDescription
boolean

whether the copy succeeded

string?

a failure reason, when unsuccessful

createDirectoryfunction#

function createDirectory(path: string | paths.Path): boolean, string?

Creates a directory and every missing parent.

An existing directory succeeds.

Arguments

NameTypeDescription
pathstring | paths.Path

the directory to create

Returns

TypeDescription
boolean

whether the directory exists afterwards

string?

a failure reason, when unsuccessful

createTemporaryDirectoryfunction#

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

NameTypeDescription
optionsTemporaryOptions?

where to create it and how to name it

Returns

TypeDescription
affine(TemporaryPath?, destroyOwner)

the created path, or nil on failure

string?

a failure reason, when unsuccessful

createTemporaryFilefunction#

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:

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

NameTypeDescription
optionsTemporaryOptions?

where to create it and how to name it

Returns

TypeDescription
affine(TemporaryPath?, destroyOwner)

the created path, or nil on failure

string?

a failure reason, when unsuccessful

currentDirectoryfunction#

function currentDirectory(): string?, string?

Reads the process's current working directory.

Returns

TypeDescription
string?

the current directory, or nil on failure

string?

a failure reason, when unsuccessful

existsfunction#

function exists(path: string | paths.Path): boolean

Whether a path resolves to anything at all.

Arguments

NameTypeDescription
pathstring | paths.Path

the path to test

Returns

TypeDescription
boolean

whether it resolves

globfunction#

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:

for _, match in ipairs(assert(nupp.io.files.glob("src/**/*.nupp"))) do
    print(match)
end

Arguments

NameTypeDescription
patternstring | paths.Path

the filesystem pattern to expand

Returns

TypeDescription
{string}?

the matching paths, or nil when the pattern or walk fails

string?

a failure reason, when unsuccessful

infofunction#

function info(path: string | paths.Path): Info?, string?

Describes one path, following symbolic links.

Examples#

Read a file's size:

local info = assert(nupp.io.files.info("nupp.lua"))
assert(info.kind == "file" and info.size > 0)

Arguments

NameTypeDescription
pathstring | paths.Path

the path to describe

Returns

TypeDescription
Info?

the attributes, or nil when the path cannot be read

string?

a failure reason, when unsuccessful

isDirectoryfunction#

function isDirectory(path: string | paths.Path): boolean

Whether a path resolves to a directory.

Arguments

NameTypeDescription
pathstring | paths.Path

the path to test

Returns

TypeDescription
boolean

whether it is a directory

isFilefunction#

function isFile(path: string | paths.Path): boolean

Whether a path resolves to a regular file.

Arguments

NameTypeDescription
pathstring | paths.Path

the path to test

Returns

TypeDescription
boolean

whether it is a file

linesfunction#

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

NameTypeDescription
pathstring | paths.Path

the file to read

Returns

TypeDescription
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

listfunction#

function list(path: string | paths.Path): {Entry}?, string?

Lists a directory's immediate children.

Examples#

Count the modules beside a file:

local entries = assert(nupp.io.files.list("src"))
for _, entry in ipairs(entries) do
    print(entry.kind, entry.name)
end

Arguments

NameTypeDescription
pathstring | paths.Path

the directory to list

Returns

TypeDescription
{Entry}?

the children in the platform's order, or nil on failure

string?

a failure reason, when unsuccessful

openfunction#

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:

do
    local file = assert(nupp.io.files.open("image.png"))
    print(file:newReader():read(8))
end

Arguments

NameTypeDescription
pathstring | paths.Path

the file to open

modeMode?

how it may be used, defaulting to reading

Returns

TypeDescription
affine(File?, destroyOwner)

the open file, or nil on failure

string?

a failure reason, when unsuccessful

Raises

  • when mode names no open mode

pendingTransfersfunction#

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

TypeDescription
integer

the number of live transfers

readfunction#

function read(path: string | paths.Path): string?, string?

Reads a whole file.

Examples#

Read a file's bytes, or report why not:

local text, reason = nupp.io.files.read("nupp.lua")
assert(text, reason)

Arguments

NameTypeDescription
pathstring | paths.Path

the file to read

Returns

TypeDescription
string?

the contents, or nil on failure

string?

a failure reason, when unsuccessful

removefunction#

function remove(path: string | paths.Path, recursive: boolean?): boolean, string?

Removes a file, a symbolic link, or a directory.

Arguments

NameTypeDescription
pathstring | paths.Path

the path to remove

recursiveboolean?

whether to remove a directory's contents with it

Returns

TypeDescription
boolean

whether the path was removed

string?

a failure reason, when unsuccessful

renamefunction#

function rename(from: string | paths.Path, to: string | paths.Path): boolean, string?

Renames a path, replacing an existing destination.

Arguments

NameTypeDescription
fromstring | paths.Path

the path to rename

tostring | paths.Path

the new path

Returns

TypeDescription
boolean

whether the rename happened

string?

a failure reason, when unsuccessful

setReadOnlyfunction#

function setReadOnly(path: string | paths.Path, readOnly: boolean): boolean, string?

Sets or clears a path's read-only attribute.

Arguments

NameTypeDescription
pathstring | paths.Path

the path to change

readOnlyboolean

whether to refuse writes

Returns

TypeDescription
boolean

whether the attribute was set

string?

a failure reason, when unsuccessful

userFolderfunction#

function userFolder(which: UserFolder): string?, string?

Answers a well-known user folder.

Arguments

NameTypeDescription
whichUserFolder

the folder to locate

Returns

TypeDescription
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

writefunction#

function write(path: string | paths.Path, bytes: string | io.ByteView): boolean, string?

Writes a whole file, replacing its contents.

Arguments

NameTypeDescription
pathstring | paths.Path

the file to write

bytesstring | io.ByteView

the contents

Returns

TypeDescription
boolean

whether the write succeeded

string?

a failure reason, when unsuccessful

writeAtomicfunction#

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

NameTypeDescription
pathstring | paths.Path

the file to write

bytesstring | io.ByteView

the contents

Returns

TypeDescription
boolean

whether the write succeeded

string?

a failure reason, when unsuccessful