1use crate::{
5 profile::LayeredProfile,
6 profile::{source::PunktfSource, transform::Transform},
7 visit::*,
8};
9use std::path::Path;
10
11fn transform_content(
14 profile: &LayeredProfile,
15 file: &File<'_>,
16 content: String,
17) -> color_eyre::Result<String> {
18 let mut content = content;
19
20 let exec_transformers: Vec<_> = file.dotfile().transformers.to_vec();
22
23 for transformer in profile.transformers().chain(exec_transformers.iter()) {
28 content = transformer.transform(content)?;
29 }
30
31 Ok(content)
32}
33
34#[derive(Debug)]
36pub enum Event<'a> {
37 NewFile {
39 relative_source_path: &'a Path,
41
42 target_path: &'a Path,
44 },
45
46 NewDirectory {
48 relative_source_path: &'a Path,
50
51 target_path: &'a Path,
53 },
54
55 Diff {
57 relative_source_path: &'a Path,
59
60 target_path: &'a Path,
62
63 old_content: String,
65
66 new_content: String,
72 },
73}
74
75impl Event<'_> {
76 pub const fn target_path(&self) -> &Path {
78 match self {
79 Self::NewFile { target_path, .. } => target_path,
80 Self::NewDirectory { target_path, .. } => target_path,
81 Self::Diff { target_path, .. } => target_path,
82 }
83 }
84}
85
86#[derive(Debug, Clone, Copy)]
90pub struct Diff<F>(F);
91
92impl<F> Diff<F>
93where
94 F: Fn(Event<'_>),
95{
96 pub const fn new(f: F) -> Self {
98 Self(f)
99 }
100
101 pub fn diff(self, source: &PunktfSource, profile: &mut LayeredProfile) {
103 let mut resolver = ResolvingVisitor(self);
104 let walker = Walker::new(profile);
105
106 if let Err(err) = walker.walk(source, &mut resolver) {
107 log::error!("Failed to execute diff: {err}");
108 }
109 }
110
111 fn dispatch(&self, event: Event<'_>) {
113 (self.0)(event)
114 }
115}
116
117macro_rules! safe_read_file_content {
124 ($path:expr, $display_path:expr) => {{
125 match std::fs::read_to_string($path) {
126 Ok(old) => old,
127 Err(err) if err.kind() == std::io::ErrorKind::InvalidData => {
128 log::info!("[{}] Ignored - Binary data", $display_path);
129 return Ok(());
130 }
131 Err(err) => {
132 log::error!("[{}] Error - Failed to read file: {err}", $display_path);
133 return Ok(());
134 }
135 }
136 }};
137}
138
139impl<F> Visitor for Diff<F>
140where
141 F: Fn(Event<'_>),
142{
143 fn accept_file<'a>(
148 &mut self,
149 _: &PunktfSource,
150 profile: &LayeredProfile,
151 file: &File<'a>,
152 ) -> Result {
153 if file.target_path.exists() {
154 let old =
155 safe_read_file_content!(&file.target_path, file.relative_source_path.display());
156
157 let new =
158 safe_read_file_content!(&file.source_path, file.relative_source_path.display());
159
160 let new = match transform_content(profile, file, new) {
161 Ok(new) => new,
162 Err(err) => {
163 log::error!(
164 "[{}] Error - Failed to apply transformer: {err}",
165 file.relative_source_path.display(),
166 );
167 return Ok(());
168 }
169 };
170
171 if new != old {
172 self.dispatch(Event::Diff {
173 relative_source_path: &file.relative_source_path,
174 target_path: &file.target_path,
175 old_content: old,
176 new_content: new,
177 });
178 }
179 } else {
180 self.dispatch(Event::NewFile {
181 relative_source_path: &file.relative_source_path,
182 target_path: &file.target_path,
183 })
184 }
185
186 Ok(())
187 }
188
189 fn accept_directory<'a>(
193 &mut self,
194 _: &PunktfSource,
195 _: &LayeredProfile,
196 directory: &Directory<'a>,
197 ) -> Result {
198 if !directory.target_path.exists() {
199 self.dispatch(Event::NewDirectory {
200 relative_source_path: &directory.relative_source_path,
201 target_path: &directory.target_path,
202 })
203 }
204
205 Ok(())
206 }
207
208 fn accept_link(&mut self, _: &PunktfSource, _: &LayeredProfile, link: &Symlink) -> Result {
213 log::info!(
214 "[{}] Ignoring - Symlinks are not supported for diffs",
215 link.source_path.display()
216 );
217
218 Ok(())
219 }
220
221 fn accept_rejected<'a>(
223 &mut self,
224 _: &PunktfSource,
225 _: &LayeredProfile,
226 rejected: &Rejected<'a>,
227 ) -> Result {
228 log::info!(
229 "[{}] Rejected - {}",
230 rejected.relative_source_path.display(),
231 rejected.reason,
232 );
233
234 Ok(())
235 }
236
237 fn accept_errored<'a>(
239 &mut self,
240 _: &PunktfSource,
241 _: &LayeredProfile,
242 errored: &Errored<'a>,
243 ) -> Result {
244 log::error!(
245 "[{}] Error - {}",
246 errored.relative_source_path.display(),
247 errored
248 );
249
250 Ok(())
251 }
252}
253
254impl<F> TemplateVisitor for Diff<F>
255where
256 F: Fn(Event<'_>),
257{
258 fn accept_template<'a>(
263 &mut self,
264 _: &PunktfSource,
265 profile: &LayeredProfile,
266 file: &File<'a>,
267 resolve_content: impl FnOnce(&str) -> color_eyre::Result<String>,
270 ) -> Result {
271 if file.target_path.exists() {
272 let old =
273 safe_read_file_content!(&file.target_path, file.relative_source_path.display());
274
275 let new =
276 safe_read_file_content!(&file.source_path, file.relative_source_path.display());
277
278 let new = match resolve_content(&new) {
279 Ok(content) => content,
280 Err(err) => {
281 log::error!(
282 "[{}] Error - Failed to resolve template: {err}",
283 file.source_path.display()
284 );
285
286 return Ok(());
287 }
288 };
289
290 let new = match transform_content(profile, file, new) {
291 Ok(new) => new,
292 Err(err) => {
293 log::error!(
294 "[{}] Error - Failed to apply transformer: {err}",
295 file.relative_source_path.display(),
296 );
297 return Ok(());
298 }
299 };
300
301 if new != old {
302 self.dispatch(Event::Diff {
303 relative_source_path: &file.relative_source_path,
304 target_path: &file.target_path,
305 old_content: old,
306 new_content: new,
307 });
308 }
309 } else {
310 self.dispatch(Event::NewFile {
311 relative_source_path: &file.relative_source_path,
312 target_path: &file.target_path,
313 })
314 }
315
316 Ok(())
317 }
318}