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}