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.
| 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) 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?
| API | Use 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. |