std/fs/lib
std/fs/src/lib.trb
The file system: File, which is both a stream's reading end and its writing end, and IoError, what every
operation here fails with. The whole-file helpers (readText, writeText, readBytes, writeBytes) are for what
fits in memory; chunks(), lines() and the Sink side are for everything else (docs/design/STREAMS.md).
Around the contents: remove, rename, copy, createDirectory, removeDirectory, list and walk for the tree,
metadata for what a path is, symbolic links, temporary files and directories, and writeBytesAtomically for a
file that is replaced whole or not at all. Every one of them takes the same path text on every system - / between
the names, a drive letter on Windows - and the runtime converts it where it calls the system (docs/design/PATH.md
section 6), so nothing here branches on the operating system.
type IoError
type IoError with Show, Error
What went wrong with which path.
extend IoError with From<Utf8Error>
extend IoError with From<Utf8Error>
Bytes off a disk are not always UTF-8, and a String always is - so decoding is one of the ways reading fails.
fn from
static fn from(value: Utf8Error): IoError
type File
native shared type File with Close, Sink<Bytes, IoError>
A file that is open. It has an identity (the handle of the operating system), so it is a shared type:
fn firstText(path: String): Result<String, IoError> {
using file = File.open(path)?
file.readAll()
}
The file closes when the last reference to it goes away - here at the end of firstText, because using keeps
the name inside its block. Nothing calls close() by hand.
An open file is both ends of a stream: chunks()/lines() read it, and the file is a Sink<Bytes, IoError> for
writing, so everything that writes into a sink writes into a file. Most code needs neither: the functions without
self read and write whole files, and move, copy, remove and describe what a path names.
Related
fn open
native static fn open(path: String): Result<File, IoError>
Opens an existing file for reading.
fn create
native static fn create(path: String): Result<File, IoError>
Creates the file, or empties it if it is there. The counterpart of open, for the writing side.
fn readAll
native var fn readAll(): Result<String, IoError>
The rest of the file as text, from wherever the cursor is. Fails if the bytes are not UTF-8.
fn close
native var fn close()
Releases the operating system handle. Synchronous and cannot fail, as every Close promises.
fn chunks
var fn chunks(size: Int = 65536): Source<Bytes, IoError>
The file as a stream of byte chunks of at most size, each read on a thread of the blocking pool so the worker goes
on meanwhile. Everything above it (lines, framing, decoding) is a Stage and therefore ordinary TorbScript.
fn lines
var fn lines(): Source<String, IoError>
The lines of the file, as a stream. A read failure ends the stream and the reader sees it, because it is in the type - it can never be swallowed halfway through.
fn add
var fn add(item: Bytes): Task<Result<Void, IoError>>
The writing end. A File is a Sink<Bytes, IoError>, so addText, addLine and fill are all there.
fn end
var fn end(): Task<Result<Void, IoError>>
Flushes to the operating system and reports what a buffered write could only report now.
fn readText
native static fn readText(path: String): Result<String, IoError>
A String is always valid UTF-8, so bytes that are not are an IoError and never a replacement character.
fn writeText
native static fn writeText(path: String, text: String): Result<Void, IoError>
Creates or empties path and writes text into it in one call - the writing counterpart of readText.
fn readBytes
static fn readBytes(path: String): Result<Bytes, IoError>
The whole file as bytes, synchronously: for what fits in memory, and for code that cannot wait for a task - a tool,
a test, the top level of a program. read is the same for a task, and chunks() reads a file of any size.
Examples
fn sizeOf(path: String): Result<Int, IoError> {
const bytes = File.readBytes(path)?
Ok bytes.length()
}
fn writeBytes
static fn writeBytes(path: String, bytes: Bytes): Result<Void, IoError>
Creates or empties path and writes bytes into it in one call, synchronously - the counterpart of readBytes.
fn read
static fn read(path: String): Task<Result<Bytes, IoError>>
The whole file as bytes, read chunk by chunk on a thread of the blocking pool so the worker goes on meanwhile: what
readBytes is for a task. The counterpart of write, which takes a source: Source.from([bytes]) is one.
fn write
static fn write(path: String, var source: Source<Bytes, IoError>): Task<Result<Void, IoError>>
Creates path and writes everything source delivers into it, of any size and without holding it in memory:
File.write(path, response.body.mapFailure(IoError.from)).await()?
fn writeBytesAtomically
static fn writeBytesAtomically(path: String, bytes: Bytes): Result<Void, IoError>
Replaces path whole or not at all: the bytes go into a new file beside it, which is flushed to the disk and then
moved over path in one step. A reader - or the program after a crash - finds the old content or the new one and
never a part of either, which writeBytes cannot promise. A file that was there keeps its permissions, and where
this fails nothing is left behind.
Examples
fn saveIndex(path: String, entries: List<String>): Result<Void, IoError> {
File.writeTextAtomically path, entries.joined(separator: "\n")
}
Pitfalls
- On Windows a file that another program holds open without allowing its deletion cannot be replaced, and the call fails; the old content stays.
fn writeTextAtomically
static fn writeTextAtomically(path: String, text: String): Result<Void, IoError>
File.writeBytesAtomically for text, which is always UTF-8.
fn absolutePath
native static fn absolutePath(path: String): Result<String, IoError>
The path as an absolute path, with . and .. resolved against the working directory. The file does not have
to exist: this is text arithmetic, not a lookup, so it does not follow links either.
fn exists
native static fn exists(path: String): Bool
Whether something exists at path, file or directory. A symbolic link counts as what it points at.
fn isDirectory
native static fn isDirectory(path: String): Bool
Whether path is a directory rather than a file.
fn createDirectory
native static fn createDirectory(path: String): Result<Void, IoError>
Creates the directory and every directory above it that is missing, and does nothing where it is already there - so a caller that only wants a place to write does not have to ask first.
fn list
native static fn list(path: String): Result<List<String>, IoError>
The names of the entries of a directory, sorted.
Errors
- The directory cannot be read, or the name of one of its entries is not valid UTF-8. A
Stringis always UTF-8, so such an entry has no name to answer: the message shows its bytes, and no replacement character is invented.
fn walk
static fn walk(path: String): Result<List<String>, IoError>
Every entry below the directory, as a path relative to it with / between the names: a directory comes before what
is in it, and the entries of one directory come in the order list answers them. A symbolic link is an entry and
is never followed, so a link that points back up cannot make the walk endless.
Examples
fn sources(root: String): Result<List<String>, IoError> {
const entries = File.walk(root)?
Ok entries.filter({ _.endsWith ".trb" }).toList()
}
fn remove
native static fn remove(path: String): Result<Void, IoError>
Removes one file, one symbolic link - the link, never what it points at - or one empty directory. A read-only file is removed as well, on Windows too. Fails where nothing is there.
fn removeDirectory
static fn removeDirectory(path: String): Result<Void, IoError>
Removes the directory and everything in it, and does nothing where nothing is there - the counterpart of
createDirectory, which does nothing where the directory is already there. A symbolic link inside is removed and
never followed, so nothing outside the directory is touched; path itself being a link removes the link.
Errors
pathis a file, or something inside cannot be removed. What was removed before that stays removed.
fn rename
native static fn rename(path: String, to: String): Result<Void, IoError>
Moves path to to in one step, replacing a file that is at to - which is what makes it the last step of every
replacement that must not be seen half done. Both have to be on one volume: across volumes a move is a copy and a
remove, and no system does that in one step.
fn copy
native static fn copy(path: String, to: String): Result<Void, IoError>
Copies the content and the permissions of the file path to to, which is created or replaced. A symbolic link is
followed; a directory is refused.
fn metadata
static fn metadata(path: String): Result<Metadata, IoError>
What path is, how large, when it was last written and who may do what with it - of what a symbolic link points
at, the way every other function here follows one. linkMetadata describes the link itself.
Examples
fn isNewer(first: String, second: String): Result<Bool, IoError> {
const one = File.metadata(first)?
const other = File.metadata(second)?
Ok(one.modified > other.modified)
}
fn linkMetadata
static fn linkMetadata(path: String): Result<Metadata, IoError>
File.metadata of a symbolic link itself rather than of what it points at: the one way to see that a path is a link.
fn setPermissions
static fn setPermissions(path: String, permissions: Permissions): Result<Void, IoError>
Sets the permission bits of path. On Windows only the owner's write bit counts: without it the file is read-only,
and with it it is not.
fn createSymbolicLink
native static fn createSymbolicLink(path: String, target: String): Result<Void, IoError>
Creates a symbolic link at path whose target is target, stored exactly as written: a relative target is read
from the directory of the link, not from the working directory.
Pitfalls
- Windows lets a program create one only in developer mode or with the privilege to; without either this fails with
Operation not permitted, and a program that must run everywhere copies instead.
fn symbolicLinkTarget
native static fn symbolicLinkTarget(path: String): Result<String, IoError>
The target of the symbolic link at path as it was stored, with / between its names on every system.
fn createTemporaryFile
static fn createTemporaryFile(prefix: String = "", inside: String? = None): Result<String, IoError>
Creates a new, empty file with a name nobody else has - prefix and twelve letters and digits - and answers its
path. It is created where nothing was, so no other program can have it too, and on POSIX only its owner may read
it. It lives in inside, or in the system's directory for temporary files; removing it is the caller's.
Examples
fn scratch(content: String): Result<String, IoError> {
const path = File.createTemporaryFile(prefix: "report-")?
File.writeText(path, content)?
Ok path
}
fn createTemporaryDirectory
static fn createTemporaryDirectory(prefix: String = "", inside: String? = None): Result<String, IoError>
Creates a new, empty directory with a name nobody else has, the way File.createTemporaryFile creates a file, and
answers its path. removeDirectory removes it with everything that was put into it.