Skip to main content

vs_cli/
active_session.rs

1//! Read/write the `active-session` pointer file.
2//!
3//! `~/.vibesurfer/active-session` holds one line: the id of the most
4//! recently-opened session. The CLI reads it on startup; `vs
5//! session-open` writes it; `vs session-close` clears it. `--session`
6//! on any command overrides.
7
8use std::fs;
9use std::io::ErrorKind;
10use std::path::Path;
11
12use anyhow::{Context as _, Result};
13
14/// Read the active session id, or `None` if the file is missing.
15pub fn read(path: impl AsRef<Path>) -> Result<Option<String>> {
16    let path = path.as_ref();
17    match fs::read_to_string(path) {
18        Ok(s) => {
19            let trimmed = s.trim();
20            if trimmed.is_empty() {
21                Ok(None)
22            } else {
23                Ok(Some(trimmed.to_string()))
24            }
25        }
26        Err(e) if e.kind() == ErrorKind::NotFound => Ok(None),
27        Err(e) => Err(e).with_context(|| format!("read {}", path.display())),
28    }
29}
30
31/// Write `id` as the active session.
32pub fn write(path: impl AsRef<Path>, id: &str) -> Result<()> {
33    let path = path.as_ref();
34    if let Some(parent) = path.parent() {
35        fs::create_dir_all(parent).with_context(|| format!("create {}", parent.display()))?;
36    }
37    fs::write(path, format!("{id}\n")).with_context(|| format!("write {}", path.display()))?;
38    Ok(())
39}
40
41/// Clear the active session (delete the file). Idempotent.
42pub fn clear(path: impl AsRef<Path>) -> Result<()> {
43    match fs::remove_file(path.as_ref()) {
44        Ok(()) => Ok(()),
45        Err(e) if e.kind() == ErrorKind::NotFound => Ok(()),
46        Err(e) => Err(e).context("remove active-session"),
47    }
48}
49
50#[cfg(test)]
51mod tests {
52    use super::*;
53
54    #[test]
55    fn round_trip() {
56        let dir = tempfile::tempdir().unwrap();
57        let path = dir.path().join("active-session");
58        assert!(read(&path).unwrap().is_none());
59        write(&path, "s_abc").unwrap();
60        assert_eq!(read(&path).unwrap().as_deref(), Some("s_abc"));
61        clear(&path).unwrap();
62        assert!(read(&path).unwrap().is_none());
63        // Idempotent.
64        clear(&path).unwrap();
65    }
66
67    #[test]
68    fn empty_file_returns_none() {
69        let dir = tempfile::tempdir().unwrap();
70        let path = dir.path().join("active-session");
71        std::fs::write(&path, "\n  \n").unwrap();
72        assert!(read(&path).unwrap().is_none());
73    }
74}