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).unwrap_or_else(|_| p.clone()).hash(&mut h);
52        }
53        Ok(FileStore {
54            path: dir.join(format!("{:016x}.session", h.finish())),
55        })
56    }
57}
58
59/// The history directory: `$QUARB_CACHE_DIR/quai`, else
60/// `~/.quarb/quai`, else a temp fallback.
61#[cfg(feature = "native")]
62fn base_dir() -> anyhow::Result<std::path::PathBuf> {
63    let root = if let Some(d) = std::env::var_os("QUARB_CACHE_DIR") {
64        std::path::PathBuf::from(d)
65    } else if let Some(home) = std::env::var_os("HOME") {
66        std::path::PathBuf::from(home).join(".quarb")
67    } else {
68        std::env::temp_dir().join("quarb")
69    };
70    Ok(root.join("quai"))
71}
72
73#[cfg(feature = "native")]
74impl Store for FileStore {
75    fn load(&self) -> Option<SessionState> {
76        // Line 1 is the counter; the rest is the macro table verbatim.
77        let text = std::fs::read_to_string(&self.path).ok()?;
78        let (first, rest) = text.split_once('\n')?;
79        Some(SessionState {
80            line_no: first.trim().parse().ok()?,
81            defs_text: rest.to_string(),
82        })
83    }
84    fn save(&self, state: &SessionState) -> anyhow::Result<()> {
85        std::fs::write(&self.path, format!("{}\n{}", state.line_no, state.defs_text))?;
86        Ok(())
87    }
88}