safe_writer.rs provides atomic, modification-aware file output. Its central
concern is protecting manually edited generated files: if a file in gen/ was
changed by hand since weaveback last wrote it, the next run refuses to
overwrite it and returns ModifiedExternally instead.
SafeFileWriter is used by `ChunkWriter` in noweb.rs.
Baseline and source-map data is persisted to `WeavebackDb`.
See weaveback_tangle.adoc for the module map.
Write flow
For each output file:
-
before_write(name)— validate the path, create missing directories, and stage aNamedTempFilewith the correct extension so formatters can identify the file type. Returns the temp-file path; the caller writes content there. -
after_write(name)— runs the optional formatter on the temp copy, checks whether the existinggen/file has been modified externally, copies the temp file togen/if the content changed, stores the new content as the baseline in the in-memory database, and returns the final bytes so the caller can use them (e.g. for source-map remapping) without re-reading the file from disk. -
finish(target)— merges the in-memory database intotarget(weaveback.db), making baselines and source maps persistent.
Modification detection
After the first successful write the content is stored as a baseline in the
gen_baselines database table. On the next run, after_write reads the
existing gen/ file and compares it to the baseline. If they differ, someone
has edited the file externally and the run aborts rather than clobber the edit.
Security
validate_filename rejects:
-
absolute paths (Unix and Windows style)
-
Windows-style drive paths (
C:) -
..path-traversal components
Error type
// <[safe-writer-errors]>=
use Error;
// @@
Configuration
SafeWriterConfig carries the per-run settings. It implements Default so
callers can use struct-update syntax and only specify the fields they care about.
// <[safe-writer-config]>=
// @@
Struct and constructors
SafeFileWriter owns the canonical gen/ base path (canonicalized at
construction time), the in-memory database, the active configuration, and the
staging map that keeps each NamedTempFile alive between before_write and
after_write.
// <[safe-writer-struct]>=
// @@
Internal write helpers
These three methods are private implementation details of after_write.
atomic_copy copies a source file to a destination via a .tmp side-car,
syncing to disk before renaming, to guard against partial writes on crash.
copy_if_different skips the copy entirely when source and destination are
byte-for-byte identical, keeping build-system timestamps stable and avoiding
unnecessary recompilation. It compares files incrementally in fixed-size
chunks rather than loading them entirely into memory.
run_formatter shells out to the configured formatter command, using
shlex::split to tokenise the command string so that quoted arguments and
paths with spaces work correctly. The temp-file path is appended as the
final argument.
// <[safe-writer-helpers]>=
// @@
before_write and after_write
before_write validates the path, ensures the output directory exists, and
creates a NamedTempFile with the correct extension (so formatters like
rustfmt can identify the file type by name). The temp file is stored in
staging; after_write consumes it.
after_write implements the four-step write pipeline described in the Write
flow section above.
// <[safe-writer-rw]>=
// @@
Accessors and finish
// <[safe-writer-accessors]>=
// @@
Path validation
validate_filename is called by both before_write and after_write.
It enforces the same three rules described in the Security section above.
// <[validate-filename]>=
// @@
Assembly
// <[@file weaveback-tangle/src/safe_writer.rs]>=
use crate;
use shlex;
use HashMap;
use ;
use Read;
use ;
use ;
use NamedTempFile;
// <[safe-writer-errors]>
// <[safe-writer-config]>
// <[safe-writer-struct]>
// <[safe-writer-helpers]>
// <[safe-writer-rw]>
// <[safe-writer-accessors]>
// <[validate-filename]>
// @@