mod replace;
mod smart;
mod udiff;
mod write;
pub use replace::*;
pub use smart::*;
pub use udiff::*;
pub use write::*;
use std::collections::HashMap;
use std::path::PathBuf;
use fs_err;
use serde::{Deserialize, Serialize};
use crate::{config::Config, error::Result};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum Change {
Write(write::WriteFile),
Replace(replace::Replace),
Smart(smart::Smart),
UDiff(udiff::UDiff),
}
#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Patch {
pub changes: Vec<Change>,
}
impl Patch {
pub fn changed_files(&self) -> Vec<PathBuf> {
let mut paths = vec![];
for change in &self.changes {
match change {
Change::Write(write_file) => paths.push(write_file.path.clone()),
Change::Replace(replace) => paths.push(replace.path.clone()),
Change::Smart(block) => paths.push(block.path.clone()),
Change::UDiff(udiff) => paths.extend(udiff.modified_files.iter().map(|f| f.into())),
}
}
paths
}
pub fn snapshot(&self, config: &Config) -> Result<HashMap<PathBuf, String>> {
let mut snapshot = HashMap::new();
for path in self.changed_files() {
let abs_path = config.abspath(&path)?;
let content = fs_err::read_to_string(&abs_path)?;
snapshot.insert(path, content);
}
Ok(snapshot)
}
pub(crate) fn apply(&self, config: &Config) -> Result<()> {
let mut modified_cache = self.snapshot(config)?;
for change in &self.changes {
match change {
Change::Replace(replace) => replace.apply_to_cache(&mut modified_cache)?,
Change::Write(write_file) => write_file.apply_to_cache(&mut modified_cache)?,
Change::Smart(smart) => smart.apply_to_cache(&mut modified_cache)?,
Change::UDiff(udiff) => udiff.apply_to_cache(&mut modified_cache)?,
}
}
for (path, content) in modified_cache {
let abs_path = config.abspath(&path)?;
fs_err::write(&abs_path, content)?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn test_changed_files() {
let mut patch = Patch::default();
patch.changes.push(Change::Write(write::WriteFile {
path: PathBuf::from("file1.txt"),
content: "content".to_string(),
}));
patch.changes.push(Change::Replace(replace::Replace {
path: PathBuf::from("file2.txt"),
old: "old".to_string(),
new: "new".to_string(),
}));
let changed_files = patch.changed_files();
assert_eq!(changed_files.len(), 2);
assert!(changed_files.contains(&PathBuf::from("file1.txt")));
assert!(changed_files.contains(&PathBuf::from("file2.txt")));
}
#[test]
fn test_apply() {
use crate::testutils::test_project;
let test_project = test_project();
test_project.create_file_tree(&["file1.txt", "file2.txt"]);
test_project.write("file1.txt", "initial content");
test_project.write("file2.txt", "content with old text");
let mut patch = Patch::default();
patch.changes.push(Change::Write(write::WriteFile {
path: PathBuf::from("file1.txt"),
content: "new content".to_string(),
}));
patch.changes.push(Change::Replace(replace::Replace {
path: PathBuf::from("file2.txt"),
old: "content with old text".to_string(),
new: "content with new text".to_string(),
}));
patch.apply(&test_project.config).unwrap();
assert_eq!(test_project.read("file1.txt"), "new content");
assert_eq!(test_project.read("file2.txt"), "content with new text");
}
}