calternal_notes_core::tasks::frontmatter
Byte-stable, line-scoped YAML frontmatter field reader + writer.
Why hand-rolled (and NOT a YAML library)
Section titled “Why hand-rolled (and NOT a YAML library)”Task frontmatter is stored opaquely as frontmatter_raw (model.rs) and
field-level YAML editing was deliberately deferred for round-trip safety
(see dayfile.rs). The hard requirement (spec §13) is byte stability:
editing one field must preserve EVERY other byte — key order, indentation,
comments, blank lines, the \r\n vs \n line-ending style, and the trailing
newline. A real YAML parser/serializer cannot meet this: round-tripping
through serde_yaml/yaml-rust reorders keys, drops comments, re-quotes
scalars, normalizes indentation and folds line endings. So we DON’T parse the
document — we treat it as bytes and surgically rewrite exactly one line,
concatenating the untouched byte slices on either side of the single edit.
Scope (documented limits)
Section titled “Scope (documented limits)”Scalar single-line fields only — the fields the app actually sets:
status, completed, title, due, scheduled, start, repeat,
area, priority, created. recurrence remains a legacy spelling.
It does not edit the tags: YAML list
(a multi-line block sequence); that is out of scope for v1 (contract §5).
A NEW field is appended as the last line before the closing ---
(deterministic position so the result is reproducible).
Values are emitted through needs_quote so a scalar that would otherwise be
mis-parsed by YAML (e.g. one containing ": ") is double-quoted and thus
round-trips instead of silently becoming a mapping.
Source: crates/calternal-notes-core/src/tasks/frontmatter.rs
Functions
Section titled “Functions”set_task_frontmatter_field
Section titled “set_task_frontmatter_field”pub fn set_task_frontmatter_field(text: &str, field: &str, value: Option<&str>) -> StringByte-stable single-field frontmatter editor.
value = Some(v) sets/replaces the field; value = None removes it.
Every other byte — key order, spacing, comments, blank lines, line-ending
style and the trailing newline — is preserved. See the module docs for the
rationale and scope.
Behavior matrix:
- field present +
Some(v): rebuild that one line in place (preserving its indent and EOL). If the rebuilt line is byte-identical to the original, the originaltextis returned unchanged (idempotence lock, §13). - field present +
None: drop exactly that line (including its EOL). - field absent +
Some(v): appendfield: vas the last line before the closing---. - field absent +
None: no-op (returnstextunchanged). - no frontmatter block +
Some(v): synthesize a minimal---…---block and prepend it (so a frontmatterless file is not corrupted). - no frontmatter block +
None: no-op.
Source: crates/calternal-notes-core/src/tasks/frontmatter.rs:382
set_task_tags
Section titled “set_task_tags”pub fn set_task_tags(text: &str, tags: &[String]) -> StringReplace only the tags: frontmatter segment. Legacy block lists and new
flow lists are both read; the writer emits a YAML-compatible JSON flow list
and preserves every byte outside the old tag segment.
Source: crates/calternal-notes-core/src/tasks/frontmatter.rs:408