use crate::{
profile::LayeredProfile,
profile::{source::PunktfSource, transform::Transform},
visit::*,
};
use std::path::Path;
fn transform_content(
profile: &LayeredProfile,
file: &File<'_>,
content: String,
) -> color_eyre::Result<String> {
let mut content = content;
let exec_transformers: Vec<_> = file.dotfile().transformers.to_vec();
for transformer in profile.transformers().chain(exec_transformers.iter()) {
content = transformer.transform(content)?;
}
Ok(content)
}
#[derive(Debug)]
pub enum Event<'a> {
NewFile {
relative_source_path: &'a Path,
target_path: &'a Path,
},
NewDirectory {
relative_source_path: &'a Path,
target_path: &'a Path,
},
Diff {
relative_source_path: &'a Path,
target_path: &'a Path,
old_content: String,
new_content: String,
},
}
impl Event<'_> {
pub const fn target_path(&self) -> &Path {
match self {
Self::NewFile { target_path, .. } => target_path,
Self::NewDirectory { target_path, .. } => target_path,
Self::Diff { target_path, .. } => target_path,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct Diff<F>(F);
impl<F> Diff<F>
where
F: Fn(Event<'_>),
{
pub const fn new(f: F) -> Self {
Self(f)
}
pub fn diff(self, source: &PunktfSource, profile: &mut LayeredProfile) {
let mut resolver = ResolvingVisitor(self);
let walker = Walker::new(profile);
if let Err(err) = walker.walk(source, &mut resolver) {
log::error!("Failed to execute diff: {err}");
}
}
fn dispatch(&self, event: Event<'_>) {
(self.0)(event)
}
}
macro_rules! safe_read_file_content {
($path:expr, $display_path:expr) => {{
match std::fs::read_to_string($path) {
Ok(old) => old,
Err(err) if err.kind() == std::io::ErrorKind::InvalidData => {
log::info!("[{}] Ignored - Binary data", $display_path);
return Ok(());
}
Err(err) => {
log::error!("[{}] Error - Failed to read file: {err}", $display_path);
return Ok(());
}
}
}};
}
impl<F> Visitor for Diff<F>
where
F: Fn(Event<'_>),
{
fn accept_file<'a>(
&mut self,
_: &PunktfSource,
profile: &LayeredProfile,
file: &File<'a>,
) -> Result {
if file.target_path.exists() {
let old =
safe_read_file_content!(&file.target_path, file.relative_source_path.display());
let new =
safe_read_file_content!(&file.source_path, file.relative_source_path.display());
let new = match transform_content(profile, file, new) {
Ok(new) => new,
Err(err) => {
log::error!(
"[{}] Error - Failed to apply transformer: {err}",
file.relative_source_path.display(),
);
return Ok(());
}
};
if new != old {
self.dispatch(Event::Diff {
relative_source_path: &file.relative_source_path,
target_path: &file.target_path,
old_content: old,
new_content: new,
});
}
} else {
self.dispatch(Event::NewFile {
relative_source_path: &file.relative_source_path,
target_path: &file.target_path,
})
}
Ok(())
}
fn accept_directory<'a>(
&mut self,
_: &PunktfSource,
_: &LayeredProfile,
directory: &Directory<'a>,
) -> Result {
if !directory.target_path.exists() {
self.dispatch(Event::NewDirectory {
relative_source_path: &directory.relative_source_path,
target_path: &directory.target_path,
})
}
Ok(())
}
fn accept_link(&mut self, _: &PunktfSource, _: &LayeredProfile, link: &Symlink) -> Result {
log::info!(
"[{}] Ignoring - Symlinks are not supported for diffs",
link.source_path.display()
);
Ok(())
}
fn accept_rejected<'a>(
&mut self,
_: &PunktfSource,
_: &LayeredProfile,
rejected: &Rejected<'a>,
) -> Result {
log::info!(
"[{}] Rejected - {}",
rejected.relative_source_path.display(),
rejected.reason,
);
Ok(())
}
fn accept_errored<'a>(
&mut self,
_: &PunktfSource,
_: &LayeredProfile,
errored: &Errored<'a>,
) -> Result {
log::error!(
"[{}] Error - {}",
errored.relative_source_path.display(),
errored
);
Ok(())
}
}
impl<F> TemplateVisitor for Diff<F>
where
F: Fn(Event<'_>),
{
fn accept_template<'a>(
&mut self,
_: &PunktfSource,
profile: &LayeredProfile,
file: &File<'a>,
resolve_content: impl FnOnce(&str) -> color_eyre::Result<String>,
) -> Result {
if file.target_path.exists() {
let old =
safe_read_file_content!(&file.target_path, file.relative_source_path.display());
let new =
safe_read_file_content!(&file.source_path, file.relative_source_path.display());
let new = match resolve_content(&new) {
Ok(content) => content,
Err(err) => {
log::error!(
"[{}] Error - Failed to resolve template: {err}",
file.source_path.display()
);
return Ok(());
}
};
let new = match transform_content(profile, file, new) {
Ok(new) => new,
Err(err) => {
log::error!(
"[{}] Error - Failed to apply transformer: {err}",
file.relative_source_path.display(),
);
return Ok(());
}
};
if new != old {
self.dispatch(Event::Diff {
relative_source_path: &file.relative_source_path,
target_path: &file.target_path,
old_content: old,
new_content: new,
});
}
} else {
self.dispatch(Event::NewFile {
relative_source_path: &file.relative_source_path,
target_path: &file.target_path,
})
}
Ok(())
}
}