venus_core/
paths.rs

1//! Notebook directory management.
2//!
3//! Provides consistent directory structure for Venus notebooks,
4//! ensuring the same paths are used across CLI and server components.
5
6use std::fs;
7use std::path::{Path, PathBuf};
8
9use crate::error::Result;
10
11/// Directory structure for a Venus notebook.
12///
13/// All Venus-related files are stored under a `.venus` directory
14/// next to the notebook file:
15///
16/// ```text
17/// notebook.rs
18/// .venus/
19/// ├── build/      # Compiled cell dylibs
20/// │   ├── cells/  # Individual cell builds
21/// │   └── universe/ # Universe library build
22/// ├── cache/      # Compilation cache metadata
23/// └── state/      # Persistent cell outputs
24/// ```
25#[derive(Debug, Clone)]
26pub struct NotebookDirs {
27    /// The `.venus` directory itself.
28    pub venus_dir: PathBuf,
29
30    /// Build directory for compiled artifacts.
31    pub build_dir: PathBuf,
32
33    /// Cache directory for compilation metadata.
34    pub cache_dir: PathBuf,
35
36    /// State directory for persistent outputs.
37    pub state_dir: PathBuf,
38}
39
40impl NotebookDirs {
41    /// Create directory structure from a notebook path.
42    ///
43    /// Creates all necessary directories if they don't exist.
44    ///
45    /// # Arguments
46    /// * `notebook_path` - Path to the notebook file (e.g., `examples/hello.rs`)
47    ///
48    /// # Errors
49    /// Returns an error if directory creation fails.
50    pub fn from_notebook_path(notebook_path: &Path) -> Result<Self> {
51        let notebook_dir = notebook_path.parent().unwrap_or(Path::new("."));
52        Self::from_notebook_dir(notebook_dir)
53    }
54
55    /// Create directory structure from the notebook's parent directory.
56    ///
57    /// Creates all necessary directories if they don't exist.
58    ///
59    /// # Arguments
60    /// * `notebook_dir` - Directory containing the notebook file
61    ///
62    /// # Errors
63    /// Returns an error if directory creation fails.
64    pub fn from_notebook_dir(notebook_dir: &Path) -> Result<Self> {
65        let venus_dir = notebook_dir.join(".venus");
66        let build_dir = venus_dir.join("build");
67        let cache_dir = venus_dir.join("cache");
68        let state_dir = venus_dir.join("state");
69
70        // Create all directories (Error::Io auto-converts via #[from])
71        fs::create_dir_all(&build_dir)?;
72        fs::create_dir_all(&cache_dir)?;
73        fs::create_dir_all(&state_dir)?;
74
75        Ok(Self {
76            venus_dir,
77            build_dir,
78            cache_dir,
79            state_dir,
80        })
81    }
82
83    /// Clean all build artifacts.
84    ///
85    /// Removes the entire `.venus` directory and recreates it.
86    pub fn clean(&self) -> Result<()> {
87        if self.venus_dir.exists() {
88            fs::remove_dir_all(&self.venus_dir)?;
89        }
90
91        // Recreate the structure
92        fs::create_dir_all(&self.build_dir)?;
93        fs::create_dir_all(&self.cache_dir)?;
94        fs::create_dir_all(&self.state_dir)?;
95
96        Ok(())
97    }
98}
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103    use tempfile::TempDir;
104
105    #[test]
106    fn test_from_notebook_path() {
107        let temp = TempDir::new().expect("Failed to create temp dir");
108        let notebook_path = temp.path().join("test.rs");
109
110        let dirs = NotebookDirs::from_notebook_path(&notebook_path)
111            .expect("Failed to create dirs");
112
113        assert!(dirs.venus_dir.ends_with(".venus"));
114        assert!(dirs.build_dir.exists());
115        assert!(dirs.cache_dir.exists());
116        assert!(dirs.state_dir.exists());
117    }
118
119    #[test]
120    fn test_clean() {
121        let temp = TempDir::new().expect("Failed to create temp dir");
122        let notebook_path = temp.path().join("test.rs");
123
124        let dirs = NotebookDirs::from_notebook_path(&notebook_path)
125            .expect("Failed to create dirs");
126
127        // Create a test file
128        let test_file = dirs.build_dir.join("test.txt");
129        fs::write(&test_file, "test").expect("Failed to write test file");
130        assert!(test_file.exists());
131
132        // Clean should remove everything
133        dirs.clean().expect("Failed to clean");
134        assert!(!test_file.exists());
135
136        // But directories should be recreated
137        assert!(dirs.build_dir.exists());
138    }
139}