Home Documentation Templates Examples Showcase GitHub
Theme

Scripting · Managed files

Safe, transactional file editing.

file(path) creates a managed file value for seekable reads/writes and surgical edits. Content operations require an explicit open(); changes are not committed until save().

Lifecycle

f := file("src/main.cpp")
f.open("rw")

f.replace_once(
    "constexpr int version = 3;",
    "constexpr int version = 4;"
)
f.insert_after("#include <string>", "\n#include <vector>")

if(f.modified()) {
    f.save()
}
f.close()

open() defaults to read-only "r". Modes are r, w, a and rw. A dirty file cannot be closed: call save() to commit it or revert() to abandon pending edits first. Exiting nift run or nift sh with a managed file still open is an error; unsaved state is discarded during cleanup.

MethodPurpose
path() / exists()Inspect the path-backed value without opening it.
open() / open(mode)Begin a managed session. Default mode is r.
modified()Report whether working contents differ from the last saved state.
save()Atomically commit writable working contents where the platform supports replacement; remain open and clean.
revert()Discard pending edits and restore the last saved contents.
close()Close a clean session. Closing a dirty session is an error.

Seekable reading and writing

f := file("values.txt")
f.open("rw")
line := f.read_line()
f.seek(0)
head := f.read(8)
position := f.tell()
rest := f.read_all()
f.seek(0)
f.write("updated")
f.write_line(" value")
f.flush()
f.save()
f.close()

Managed files provide read() (all remaining bytes), read(n), read_line(), read_all(), read_val(), eof(), tell(), seek(pos), write(value), write_line(value) and flush(). Positions are byte offsets. Reads observe the current working contents, including unsaved edits.

flush() is retained for output-vocabulary parity with ofs and is a no-op on a managed working copy: a managed file's edits live in memory and nothing reaches the filesystem until save(), so flush() never commits and has no observable effect. Only save() is the commit boundary.

Surgical edits

f := file("README.md")
f.open("rw")
f.replace("old-name", "new-name")
f.replace_once("Version 4.2", "Version 4.3")
f.insert(0, "<!-- generated -->\n")
f.insert_before("## Install", "Intro text\n\n")
f.insert_after("## Install", "\nRun `nift build`.")
f.prepend("Title\n")
f.append("\nDone.\n")
f.save()
f.close()

replace() replaces all non-overlapping matches and returns the replacement count. replace_once(), insert_before() and insert_after() require exactly one match/anchor. Zero or multiple matches are errors and leave the working contents unchanged.

read(), read(n), write(), write_line() and seek() move the cursor; structural edits (replace(), replace_once(), insert(), insert_before(), insert_after(), prepend(), append()) leave the cursor where it was, clamped to the new working length, and revert() resets it to the start.

Explicit rollback

f := file("config.json")
f.open("rw")
f.replace_once(old, replacement)
if(valid) {
    f.save()
}
else {
    f.revert()
}
f.close()

Closed-state path operations

A managed file must be closed for copy(destination), move(destination) and remove(). Copy and move return a new file(destination) value and reuse Nift's existing filesystem/path-security rules.

Which file API?

APIUse it for
open(path)A one-shot whole-file read into a string.
file(path)Explicitly managed, seekable and transactional file editing.
ifs(path) / ofs(path)Incremental streaming I/O without a managed working-copy transaction.
NextFile streams →