Skip to main content

chisel/
session.rs

1//! ChiselSession
2//!
3//! This module contains the `ChiselSession` struct, which is the top-level
4//! wrapper for a serializable REPL session.
5
6use crate::prelude::{SessionSource, SessionSourceConfig};
7use eyre::Result;
8use foundry_evm::core::evm::FoundryEvmNetwork;
9use serde::{Deserialize, Serialize};
10use std::path::Path;
11use time::{OffsetDateTime, format_description};
12
13/// A Chisel REPL Session
14#[derive(Debug, Serialize, Deserialize)]
15#[serde(bound = "")]
16pub struct ChiselSession<FEN: FoundryEvmNetwork> {
17    /// The `SessionSource` object that houses the REPL session.
18    pub source: SessionSource<FEN>,
19    /// The current session's identifier
20    pub id: Option<String>,
21}
22
23// ChiselSession Common Associated Functions
24impl<FEN: FoundryEvmNetwork> ChiselSession<FEN> {
25    /// Create a new `ChiselSession` with a specified `solc` version and configuration.
26    ///
27    /// ### Takes
28    ///
29    /// An instance of [SessionSourceConfig]
30    ///
31    /// ### Returns
32    ///
33    /// A new instance of [ChiselSession]
34    pub fn new(config: SessionSourceConfig<FEN>) -> Result<Self> {
35        // Return initialized ChiselSession with set solc version
36        Ok(Self { source: SessionSource::new(config)?, id: None })
37    }
38
39    /// Render the full source code for the current session.
40    ///
41    /// ### Returns
42    ///
43    /// Returns the full, flattened source code for the current session.
44    ///
45    /// ### Notes
46    ///
47    /// This function will not panic, but will return a blank string if the
48    /// session's [SessionSource] is None.
49    pub fn contract_source(&self) -> String {
50        self.source.to_repl_source()
51    }
52
53    /// Clears the cache directory
54    ///
55    /// ### WARNING
56    ///
57    /// This will delete all sessions from the cache.
58    /// There is no method of recovering these deleted sessions.
59    pub fn clear_cache() -> Result<()> {
60        let cache_dir = Self::cache_dir()?;
61        for entry in std::fs::read_dir(cache_dir)? {
62            let entry = entry?;
63            let path = entry.path();
64            if path.is_dir() {
65                std::fs::remove_dir_all(path)?;
66            } else {
67                std::fs::remove_file(path)?;
68            }
69        }
70        Ok(())
71    }
72
73    /// Removes a cached session if it exists.
74    pub fn remove_cached_session(id: &str) -> Result<()> {
75        let cache_file = format!("{}chisel-{id}.json", Self::cache_dir()?);
76        match std::fs::remove_file(cache_file) {
77            Ok(()) => Ok(()),
78            Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(()),
79            Err(error) => Err(error.into()),
80        }
81    }
82
83    /// Writes the ChiselSession to a file by serializing it to a JSON string
84    ///
85    /// ### Returns
86    ///
87    /// Returns the path of the new cache file
88    pub fn write(&mut self) -> Result<String> {
89        // Try to create the cache directory
90        let cache_dir = Self::cache_dir()?;
91        std::fs::create_dir_all(&cache_dir)?;
92
93        let cache_file_name = match self.id.as_ref() {
94            Some(id) => {
95                // ID is already set- use the existing cache file.
96                format!("{cache_dir}chisel-{id}.json")
97            }
98            None => {
99                // Get the next session cache ID / file
100                let (id, file_name) = Self::next_cached_session()?;
101                // Set the session's ID
102                self.id = Some(id);
103                // Return the new session's cache file name
104                file_name
105            }
106        };
107
108        // Write the current ChiselSession to that file
109        let serialized_contents = serde_json::to_string_pretty(self)?;
110        std::fs::write(&cache_file_name, serialized_contents)?;
111
112        // Return the full cache file path
113        // Ex: /home/user/.foundry/cache/chisel/chisel-0.json
114        Ok(cache_file_name)
115    }
116
117    /// Get the next default session cache file name
118    ///
119    /// ### Returns
120    ///
121    /// Optionally, returns a tuple containing the next cached session's id and file name.
122    pub fn next_cached_session() -> Result<(String, String)> {
123        let cache_dir = Self::cache_dir()?;
124        let entries = std::fs::read_dir(&cache_dir)?;
125        let session_num = entries.filter(Result::is_ok).count();
126
127        Ok((format!("{session_num}"), format!("{cache_dir}chisel-{session_num}.json")))
128    }
129
130    /// The Chisel Cache Directory
131    ///
132    /// ### Returns
133    ///
134    /// Optionally, the directory of the chisel cache.
135    pub fn cache_dir() -> Result<String> {
136        let home_dir =
137            dirs::home_dir().ok_or_else(|| eyre::eyre!("Failed to grab home directory"))?;
138        let home_dir_str = home_dir
139            .to_str()
140            .ok_or_else(|| eyre::eyre!("Failed to convert home directory to string"))?;
141        Ok(format!("{home_dir_str}/.foundry/cache/chisel/"))
142    }
143
144    /// Create the cache directory if it does not exist
145    ///
146    /// ### Returns
147    ///
148    /// The unit type if the operation was successful.
149    pub fn create_cache_dir() -> Result<()> {
150        let cache_dir = Self::cache_dir()?;
151        if !Path::new(&cache_dir).exists() {
152            std::fs::create_dir_all(&cache_dir)?;
153        }
154        Ok(())
155    }
156
157    /// Returns a list of all available cached sessions.
158    pub fn get_sessions() -> Result<Vec<(String, String)>> {
159        // Read the cache directory entries
160        let cache_dir = Self::cache_dir()?;
161        let entries = std::fs::read_dir(cache_dir)?;
162
163        // For each entry, get the file name and modified time
164        let mut sessions = Vec::new();
165        for entry in entries {
166            let entry = entry?;
167            let modified_time = entry.metadata()?.modified()?;
168            let file_name = entry.file_name();
169            let file_name = file_name
170                .into_string()
171                .map_err(|e| eyre::eyre!(format!("{}", e.to_string_lossy())))?;
172            sessions.push((
173                systemtime_strftime(modified_time, "[year]-[month]-[day] [hour]:[minute]:[second]")
174                    .unwrap(),
175                file_name,
176            ));
177        }
178        Ok(sessions)
179    }
180
181    /// Loads a specific ChiselSession from the specified cache file
182    ///
183    /// ### Takes
184    ///
185    /// The ID of the chisel session that you wish to load.
186    ///
187    /// ### Returns
188    ///
189    /// Optionally, an owned instance of the loaded chisel session.
190    pub fn load(id: &str) -> Result<Self> {
191        let cache_dir = Self::cache_dir()?;
192        let contents = std::fs::read_to_string(Path::new(&format!("{cache_dir}chisel-{id}.json")))?;
193        let chisel_env: Self = serde_json::from_str(&contents)?;
194        Ok(chisel_env)
195    }
196
197    /// Gets the most recent chisel session from the cache dir
198    ///
199    /// ### Returns
200    ///
201    /// Optionally, the file name of the most recently modified cached session.
202    pub fn latest_cached_session() -> Result<String> {
203        let cache_dir = Self::cache_dir()?;
204        let mut entries = std::fs::read_dir(cache_dir)?;
205        let mut latest = entries.next().ok_or_else(|| eyre::eyre!("No entries found!"))??;
206        for entry in entries {
207            let entry = entry?;
208            if entry.metadata()?.modified()? > latest.metadata()?.modified()? {
209                latest = entry;
210            }
211        }
212        Ok(latest
213            .path()
214            .to_str()
215            .ok_or_else(|| eyre::eyre!("Failed to get session path!"))?
216            .to_string())
217    }
218
219    /// Loads the latest ChiselSession from the cache file
220    ///
221    /// ### Returns
222    ///
223    /// Optionally, an owned instance of the most recently modified cached session.
224    pub fn latest() -> Result<Self> {
225        let last_session = Self::latest_cached_session()?;
226        let last_session_contents = std::fs::read_to_string(Path::new(&last_session))?;
227        let chisel_env: Self = serde_json::from_str(&last_session_contents)?;
228        Ok(chisel_env)
229    }
230}
231
232/// Generic helper function that attempts to convert a type that has
233/// an [`Into<OffsetDateTime>`] implementation into a formatted date string.
234fn systemtime_strftime<T>(dt: T, format: &str) -> Result<String>
235where
236    T: Into<OffsetDateTime>,
237{
238    Ok(dt.into().format(&format_description::parse(format)?)?)
239}