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