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    /// Writes the ChiselSession to a file by serializing it to a JSON string
74    ///
75    /// ### Returns
76    ///
77    /// Returns the path of the new cache file
78    pub fn write(&mut self) -> Result<String> {
79        // Try to create the cache directory
80        let cache_dir = Self::cache_dir()?;
81        std::fs::create_dir_all(&cache_dir)?;
82
83        let cache_file_name = match self.id.as_ref() {
84            Some(id) => {
85                // ID is already set- use the existing cache file.
86                format!("{cache_dir}chisel-{id}.json")
87            }
88            None => {
89                // Get the next session cache ID / file
90                let (id, file_name) = Self::next_cached_session()?;
91                // Set the session's ID
92                self.id = Some(id);
93                // Return the new session's cache file name
94                file_name
95            }
96        };
97
98        // Write the current ChiselSession to that file
99        let serialized_contents = serde_json::to_string_pretty(self)?;
100        std::fs::write(&cache_file_name, serialized_contents)?;
101
102        // Return the full cache file path
103        // Ex: /home/user/.foundry/cache/chisel/chisel-0.json
104        Ok(cache_file_name)
105    }
106
107    /// Get the next default session cache file name
108    ///
109    /// ### Returns
110    ///
111    /// Optionally, returns a tuple containing the next cached session's id and file name.
112    pub fn next_cached_session() -> Result<(String, String)> {
113        let cache_dir = Self::cache_dir()?;
114        let mut entries = std::fs::read_dir(&cache_dir)?;
115
116        // If there are no existing cached sessions, just create the first one: "chisel-0.json"
117        let mut latest = if let Some(e) = entries.next() {
118            e?
119        } else {
120            return Ok((String::from("0"), format!("{cache_dir}chisel-0.json")));
121        };
122
123        let mut session_num = 1;
124        // Get the latest cached session
125        for entry in entries {
126            let entry = entry?;
127            if entry.metadata()?.modified()? >= latest.metadata()?.modified()? {
128                latest = entry;
129            }
130
131            // Increase session_num counter rather than cloning the iterator and using `.count`
132            session_num += 1;
133        }
134
135        Ok((format!("{session_num}"), format!("{cache_dir}chisel-{session_num}.json")))
136    }
137
138    /// The Chisel Cache Directory
139    ///
140    /// ### Returns
141    ///
142    /// Optionally, the directory of the chisel cache.
143    pub fn cache_dir() -> Result<String> {
144        let home_dir =
145            dirs::home_dir().ok_or_else(|| eyre::eyre!("Failed to grab home directory"))?;
146        let home_dir_str = home_dir
147            .to_str()
148            .ok_or_else(|| eyre::eyre!("Failed to convert home directory to string"))?;
149        Ok(format!("{home_dir_str}/.foundry/cache/chisel/"))
150    }
151
152    /// Create the cache directory if it does not exist
153    ///
154    /// ### Returns
155    ///
156    /// The unit type if the operation was successful.
157    pub fn create_cache_dir() -> Result<()> {
158        let cache_dir = Self::cache_dir()?;
159        if !Path::new(&cache_dir).exists() {
160            std::fs::create_dir_all(&cache_dir)?;
161        }
162        Ok(())
163    }
164
165    /// Returns a list of all available cached sessions.
166    pub fn get_sessions() -> Result<Vec<(String, String)>> {
167        // Read the cache directory entries
168        let cache_dir = Self::cache_dir()?;
169        let entries = std::fs::read_dir(cache_dir)?;
170
171        // For each entry, get the file name and modified time
172        let mut sessions = Vec::new();
173        for entry in entries {
174            let entry = entry?;
175            let modified_time = entry.metadata()?.modified()?;
176            let file_name = entry.file_name();
177            let file_name = file_name
178                .into_string()
179                .map_err(|e| eyre::eyre!(format!("{}", e.to_string_lossy())))?;
180            sessions.push((
181                systemtime_strftime(modified_time, "[year]-[month]-[day] [hour]:[minute]:[second]")
182                    .unwrap(),
183                file_name,
184            ));
185        }
186        Ok(sessions)
187    }
188
189    /// Loads a specific ChiselSession from the specified cache file
190    ///
191    /// ### Takes
192    ///
193    /// The ID of the chisel session that you wish to load.
194    ///
195    /// ### Returns
196    ///
197    /// Optionally, an owned instance of the loaded chisel session.
198    pub fn load(id: &str) -> Result<Self> {
199        let cache_dir = Self::cache_dir()?;
200        let contents = std::fs::read_to_string(Path::new(&format!("{cache_dir}chisel-{id}.json")))?;
201        let chisel_env: Self = serde_json::from_str(&contents)?;
202        Ok(chisel_env)
203    }
204
205    /// Gets the most recent chisel session from the cache dir
206    ///
207    /// ### Returns
208    ///
209    /// Optionally, the file name of the most recently modified cached session.
210    pub fn latest_cached_session() -> Result<String> {
211        let cache_dir = Self::cache_dir()?;
212        let mut entries = std::fs::read_dir(cache_dir)?;
213        let mut latest = entries.next().ok_or_else(|| eyre::eyre!("No entries found!"))??;
214        for entry in entries {
215            let entry = entry?;
216            if entry.metadata()?.modified()? > latest.metadata()?.modified()? {
217                latest = entry;
218            }
219        }
220        Ok(latest
221            .path()
222            .to_str()
223            .ok_or_else(|| eyre::eyre!("Failed to get session path!"))?
224            .to_string())
225    }
226
227    /// Loads the latest ChiselSession from the cache file
228    ///
229    /// ### Returns
230    ///
231    /// Optionally, an owned instance of the most recently modified cached session.
232    pub fn latest() -> Result<Self> {
233        let last_session = Self::latest_cached_session()?;
234        let last_session_contents = std::fs::read_to_string(Path::new(&last_session))?;
235        let chisel_env: Self = serde_json::from_str(&last_session_contents)?;
236        Ok(chisel_env)
237    }
238}
239
240/// Generic helper function that attempts to convert a type that has
241/// an [`Into<OffsetDateTime>`] implementation into a formatted date string.
242fn systemtime_strftime<T>(dt: T, format: &str) -> Result<String>
243where
244    T: Into<OffsetDateTime>,
245{
246    Ok(dt.into().format(&format_description::parse(format)?)?)
247}