# Transforms
The write path covers attribute-level edits: a link's href, an
image title, a heading level, anything stored in `Node::attrs`.
`walk` plus `|=` handles the bulk of real rewrites.
## Operators
```
walk(f) post-order. recurse into children, then apply f
```
`|=` shapes:
```sh
(codeblocks | select(.lang == "rust")).lang |= "rs"
```
`del` looks like a builtin call but carries mutation semantics,
so the write path intercepts it before the evaluator sees it.
`.attr` on the left of `|=` is handled the same way.
`walk(f)` runs `f` at every node, children first. Inside `f` you
can use the same mutation operators:
```sh
```
Conditions in `walk(f)` run through the read-only evaluator.
Mutation arms go through the walk-aware mini-interpreter in
`mutate::apply_walk_f`.
## Output modes
```
--dry-run print a unified diff, exit 0 either way
-U atomic in-place write (temp file + rename(2))
(default) write transformed markdown to stdout
```
`--dry-run` and `-U` combine. `--dry-run` prints what `-U` would
do without touching the file.
```sh
```
## How source bytes survive
Every `Node` parsed from the buffer carries a byte `Span`. The
serializer in `emit::md` walks the mutated tree. For any clean
subtree with a span, it calls
`out.extend_from_slice(&source[span])`, returning the original
bytes untouched. Mutations set `dirty = true` on the affected
node and every ancestor up to the root. Dirty subtrees get
re-emitted as pulldown events and fed to `pulldown-cmark-to-cmark`.
Consequences:
- A link href rewrite touches only the link span in the output.
Surrounding paragraphs, fences, list markers, blank-line padding
all stay verbatim.
- Regenerated spans go through `pulldown-cmark-to-cmark`, which
normalises some style choices (emphasis marker, fence width).
That normalisation is localised to dirty subtrees.
- Synthetic nodes (from `node(...)` or `section(...)`) have no
span, so they always regenerate.
## Atomic writes
`-U` writes through `tempfile::NamedTempFile::persist`:
1. Create a temp file in the same directory as the target (same
filesystem, so `rename(2)` is atomic and `EXDEV` can't bite).
2. Write the transformed bytes.
3. `fsync` the temp file.
4. Rename over the target.
If anything fails before the rename, the original is untouched.
## What's not supported
- `=`, set-style assignment. Use `|=`.
- Structural mutation. `.children |= [...]`, inserting siblings,
replacing a whole subtree. `walk(f)` covers the needed shapes.
- Mutation targets that don't resolve to `Node` values. `|=` and
`del` expect node-valued selectors on the left.