ralph_workflow/workspace/
workspace_fs.rs1#[derive(Debug, Clone)]
10pub struct WorkspaceFs {
11 root: PathBuf,
12}
13
14impl WorkspaceFs {
15 #[must_use]
21 pub const fn new(repo_root: PathBuf) -> Self {
22 Self { root: repo_root }
23 }
24}
25
26impl Workspace for WorkspaceFs {
27 fn root(&self) -> &Path {
28 &self.root
29 }
30
31 fn read(&self, relative: &Path) -> io::Result<String> {
32 fs::read_to_string(self.root.join(relative))
33 }
34
35 fn read_bytes(&self, relative: &Path) -> io::Result<Vec<u8>> {
36 fs::read(self.root.join(relative))
37 }
38
39 fn write(&self, relative: &Path, content: &str) -> io::Result<()> {
40 let path = self.root.join(relative);
41 if let Some(parent) = path.parent() {
42 fs::create_dir_all(parent)?;
43 }
44 fs::write(path, content)
45 }
46
47 fn write_bytes(&self, relative: &Path, content: &[u8]) -> io::Result<()> {
48 let path = self.root.join(relative);
49 if let Some(parent) = path.parent() {
50 fs::create_dir_all(parent)?;
51 }
52 fs::write(path, content)
53 }
54
55 fn append_bytes(&self, relative: &Path, content: &[u8]) -> io::Result<()> {
56 use std::io::Write;
57 let path = self.root.join(relative);
58 if let Some(parent) = path.parent() {
59 fs::create_dir_all(parent)?;
60 }
61 let mut file = fs::OpenOptions::new()
62 .create(true)
63 .append(true)
64 .open(path)?;
65 file.write_all(content)?;
66 file.flush()
67 }
68
69 fn exists(&self, relative: &Path) -> bool {
70 self.root.join(relative).exists()
71 }
72
73 fn is_file(&self, relative: &Path) -> bool {
74 self.root.join(relative).is_file()
75 }
76
77 fn is_dir(&self, relative: &Path) -> bool {
78 self.root.join(relative).is_dir()
79 }
80
81 fn remove(&self, relative: &Path) -> io::Result<()> {
82 fs::remove_file(self.root.join(relative))
83 }
84
85 fn remove_if_exists(&self, relative: &Path) -> io::Result<()> {
86 let path = self.root.join(relative);
87 if path.exists() {
88 fs::remove_file(path)?;
89 }
90 Ok(())
91 }
92
93 fn remove_dir_all(&self, relative: &Path) -> io::Result<()> {
94 fs::remove_dir_all(self.root.join(relative))
95 }
96
97 fn remove_dir_all_if_exists(&self, relative: &Path) -> io::Result<()> {
98 let path = self.root.join(relative);
99 if path.exists() {
100 fs::remove_dir_all(path)?;
101 }
102 Ok(())
103 }
104
105 fn create_dir_all(&self, relative: &Path) -> io::Result<()> {
106 fs::create_dir_all(self.root.join(relative))
107 }
108
109 fn read_dir(&self, relative: &Path) -> io::Result<Vec<DirEntry>> {
110 let abs_path = self.root.join(relative);
111 let mut entries = Vec::new();
112 for entry in fs::read_dir(abs_path)? {
113 let entry = entry?;
114 let metadata = entry.metadata()?;
115 let rel_path = relative.join(entry.file_name());
117 let modified = metadata.modified().ok();
118 if let Some(mod_time) = modified {
119 entries.push(DirEntry::with_modified(
120 rel_path,
121 metadata.is_file(),
122 metadata.is_dir(),
123 mod_time,
124 ));
125 } else {
126 entries.push(DirEntry::new(
127 rel_path,
128 metadata.is_file(),
129 metadata.is_dir(),
130 ));
131 }
132 }
133 Ok(entries)
134 }
135
136 fn rename(&self, from: &Path, to: &Path) -> io::Result<()> {
137 fs::rename(self.root.join(from), self.root.join(to))
138 }
139
140 fn write_atomic(&self, relative: &Path, content: &str) -> io::Result<()> {
141 use std::io::Write;
142 use tempfile::NamedTempFile;
143
144 let path = self.root.join(relative);
145
146 if let Some(parent) = path.parent() {
148 fs::create_dir_all(parent)?;
149 }
150
151 let parent_dir = path.parent().unwrap_or_else(|| Path::new("."));
154 let mut temp_file = NamedTempFile::new_in(parent_dir)?;
155
156 #[cfg(unix)]
159 {
160 use std::os::unix::fs::PermissionsExt;
161 let mut perms = fs::metadata(temp_file.path())?.permissions();
162 perms.set_mode(0o600);
163 fs::set_permissions(temp_file.path(), perms)?;
164 }
165
166 temp_file.write_all(content.as_bytes())?;
168 temp_file.flush()?;
169
170 if !crate::interrupt::user_interrupted_occurred() {
184 temp_file.as_file().sync_all()?;
185 }
186
187 temp_file.persist(&path).map_err(|e| e.error)?;
189
190 Ok(())
191 }
192
193 fn set_readonly(&self, relative: &Path) -> io::Result<()> {
194 let path = self.root.join(relative);
195 if !path.exists() {
196 return Ok(());
197 }
198
199 let metadata = fs::metadata(&path)?;
200 let mut perms = metadata.permissions();
201
202 #[cfg(unix)]
203 {
204 use std::os::unix::fs::PermissionsExt;
205 perms.set_mode(0o444);
206 }
207
208 #[cfg(windows)]
209 {
210 perms.set_readonly(true);
211 }
212
213 fs::set_permissions(path, perms)
214 }
215
216 fn set_writable(&self, relative: &Path) -> io::Result<()> {
217 let path = self.root.join(relative);
218 if !path.exists() {
219 return Ok(());
220 }
221
222 let metadata = fs::metadata(&path)?;
223 let mut perms = metadata.permissions();
224
225 #[cfg(unix)]
226 {
227 use std::os::unix::fs::PermissionsExt;
228 perms.set_mode(0o644);
229 }
230
231 #[cfg(windows)]
232 {
233 perms.set_readonly(false);
234 }
235
236 fs::set_permissions(path, perms)
237 }
238}