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