Skip to main content

opys_engine/
retired.rs

1//! The retired-id ledger: `_retired.md`, a frontmatter map of reserved
2//! `id -> title`. Closing, retiring, renumbering, or deleting a document
3//! records its id here so the global id sequence never hands the number out
4//! again; the title is kept for human reference (git records the when and why).
5//!
6//! This supersedes the pre-0.12 plaintext `_retired.txt` (`ID  # retired …`).
7//! A legacy ledger is read on load and migrated to `_retired.md` on the next
8//! write. Reusing frontmatter + the `refs` id->title map machinery means one
9//! less bespoke format to parse.
10
11use std::path::{Path, PathBuf};
12
13use crate::frontmatter::{self, Frontmatter};
14use crate::refs;
15
16/// The ledger filename.
17pub const FILE: &str = "_retired.md";
18/// The pre-0.12 plaintext ledger, migrated to [`FILE`] on the next write.
19pub const LEGACY_FILE: &str = "_retired.txt";
20/// The frontmatter key holding the reserved `id -> title` map.
21pub const FIELD: &str = "retired";
22
23const BODY: &str = "# Retired ids\n\nReserved ids that must never be reused. \
24Managed by opys — the value is the document's last title; git records when and \
25why each id was retired.\n";
26
27/// Path to the markdown ledger under `base`.
28pub fn path(base: &Path) -> PathBuf {
29    base.join(FILE)
30}
31
32/// Path to the legacy plaintext ledger under `base`.
33pub fn legacy_path(base: &Path) -> PathBuf {
34    base.join(LEGACY_FILE)
35}
36
37/// The ledger as `(id, title)` pairs sorted by number. Prefers `_retired.md`;
38/// falls back to the legacy plaintext ledger (ids only, empty titles). A missing
39/// ledger reads as empty.
40pub fn read(base: &Path) -> Vec<(String, String)> {
41    if let Ok(text) = std::fs::read_to_string(path(base)) {
42        if let Ok((fm, _)) = frontmatter::parse(&text, FILE) {
43            return refs::parse_in(&fm, FIELD);
44        }
45    }
46    read_legacy(base)
47}
48
49/// Parse the legacy plaintext ledger (`ID  # retired DATE: reason`): the id is
50/// the first token of each non-comment line; titles are unknown (empty).
51fn read_legacy(base: &Path) -> Vec<(String, String)> {
52    let Ok(text) = std::fs::read_to_string(legacy_path(base)) else {
53        return Vec::new();
54    };
55    let mut out = Vec::new();
56    for line in text.lines() {
57        let head = line.split('#').next().unwrap_or("");
58        if let Some(id) = head.split_whitespace().next() {
59            if !id.is_empty() {
60                out.push((id.to_string(), String::new()));
61            }
62        }
63    }
64    out
65}
66
67/// Render `(id, title)` entries (sorted by number) to the ledger file's text.
68pub fn serialize(entries: &[(String, String)]) -> String {
69    let mut fm = Frontmatter::new();
70    refs::set_in(&mut fm, FIELD, entries);
71    frontmatter::serialize(&fm, BODY)
72}