Home About 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 script.f or nift 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), 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?

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