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 script.f or nift with a managed file still open is an error; unsaved state is discarded during cleanup.
| Method | Purpose |
|---|---|
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), write_val(value) and flush(). Positions are byte offsets. Reads observe the current working contents, including unsaved edits.
read_val() parses one serialized Nift data value (scalar, array or object) at the cursor, skipping leading whitespace, and advances the cursor past it; at end it returns null. write_val(value) writes the value's compact JSON serialization followed by a newline at the cursor, so sequential write_val()/read_val() pairs round-trip structured values. Malformed input to read_val() is a hard error.
flush() is retained for output-vocabulary parity with ofstream 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?
| API | Use it for |
|---|---|
open(path) | A one-shot whole-file read into a string. |
file(path) | Explicitly managed, seekable and transactional file editing. |
ifstream(path) / ofstream(path) | Incremental streaming I/O without a managed working-copy transaction. |
A backend save() failure is recoverable (io.write_failed for a temporary-file write, io.atomic_replace_failed for the final atomic replacement) and can be caught. After a caught save failure the file stays open and dirty so save() can be retried or revert() can discard the edits; the on-disk content is unchanged. Lifecycle misuse (closing a dirty file, operating without opening, wrong mode) stays fatal.