Skip to main content

quarb_session/
store.rs

1//! Where a session's durable state persists across runs.
2
3/// The persistable part of a session: the macro history and the line
4/// counter. Frozen snapshots stay in memory for now — they regenerate
5/// on re-run, so persisting them is a later refinement.
6#[derive(Clone, Default)]
7pub struct SessionState {
8    pub defs_text: String,
9    pub line_no: usize,
10}
11
12/// A place to load and save [`SessionState`]. [`MemStore`] keeps
13/// nothing (a fresh session each run); a file store (native) or a
14/// browser store (wasm, localStorage) persists. The caller keys the
15/// store by the session's source identity.
16pub trait Store {
17    fn load(&self) -> Option<SessionState>;
18    fn save(&self, state: &SessionState) -> anyhow::Result<()>;
19}
20
21/// The ephemeral store: no persistence, a fresh session every time.
22pub struct MemStore;
23
24impl Store for MemStore {
25    fn load(&self) -> Option<SessionState> {
26        None
27    }
28    fn save(&self, _state: &SessionState) -> anyhow::Result<()> {
29        Ok(())
30    }
31}
32
33/// A store persisting the macro history to a file under `~/.quarb`,
34/// keyed by the source set — so restarting `quai` over the same
35/// sources restores its `&N` history. (Native only; the wasm build
36/// persists to localStorage instead.)
37#[cfg(feature = "native")]
38pub struct FileStore {
39    path: std::path::PathBuf,
40}
41
42#[cfg(feature = "native")]
43impl FileStore {
44    /// A store keyed by the canonical form of `sources`.
45    pub fn new(sources: &[std::path::PathBuf]) -> anyhow::Result<Self> {
46        use std::hash::{Hash, Hasher};
47        let dir = base_dir()?;
48        std::fs::create_dir_all(&dir)?;
49        let mut h = std::collections::hash_map::DefaultHasher::new();
50        for p in sources {
51            std::fs::canonicalize(p)
52                .unwrap_or_else(|_| p.clone())
53                .hash(&mut h);
54        }
55        Ok(FileStore {
56            path: dir.join(format!("{:016x}.session", h.finish())),
57        })
58    }
59}
60
61/// The history directory: `$QUARB_CACHE_DIR/quai`, else
62/// `~/.quarb/quai`, else a temp fallback.
63#[cfg(feature = "native")]
64fn base_dir() -> anyhow::Result<std::path::PathBuf> {
65    let root = if let Some(d) = std::env::var_os("QUARB_CACHE_DIR") {
66        std::path::PathBuf::from(d)
67    } else if let Some(home) = std::env::var_os("HOME") {
68        std::path::PathBuf::from(home).join(".quarb")
69    } else {
70        std::env::temp_dir().join("quarb")
71    };
72    Ok(root.join("quai"))
73}
74
75#[cfg(feature = "native")]
76impl Store for FileStore {
77    fn load(&self) -> Option<SessionState> {
78        // Line 1 is the counter; the rest is the macro table verbatim.
79        let text = std::fs::read_to_string(&self.path).ok()?;
80        let (first, rest) = text.split_once('\n')?;
81        Some(SessionState {
82            line_no: first.trim().parse().ok()?,
83            defs_text: rest.to_string(),
84        })
85    }
86    fn save(&self, state: &SessionState) -> anyhow::Result<()> {
87        std::fs::write(
88            &self.path,
89            format!("{}\n{}", state.line_no, state.defs_text),
90        )?;
91        Ok(())
92    }
93}