use crate::patch::{Patch, PatchError};
use crate::Rope;
use std::collections::HashMap;
pub struct PatchSet {
pub patches: Vec<Patch>,
}
impl PatchSet {
#[must_use]
pub fn new() -> Self {
Self {
patches: Vec::new(),
}
}
#[must_use]
pub fn len(&self) -> usize {
self.patches.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.patches.is_empty()
}
pub fn add(&mut self, patch: Patch) {
self.patches.push(patch);
}
pub fn apply_to_files(&self) -> Result<HashMap<String, String>, PatchError> {
let mut results = HashMap::new();
let mut by_file: HashMap<String, Vec<&Patch>> = HashMap::new();
for patch in &self.patches {
let file = patch
.file
.as_ref()
.ok_or(PatchError::MissingFilePath)?
.clone();
by_file.entry(file).or_default().push(patch);
}
for (file, patches) in by_file {
let content = std::fs::read_to_string(&file).map_err(PatchError::IoError)?;
let rope = Rope::from_str(&content);
let mut resolved: Vec<(&Patch, (usize, usize))> = Vec::new();
for patch in &patches {
let resolution = patch.snippet.resolve(&rope)?;
let range = (resolution.start, resolution.end);
resolved.push((patch, range));
}
for i in 0..resolved.len() {
for j in (i + 1)..resolved.len() {
let (patch1, range1) = resolved[i];
let (patch2, range2) = resolved[j];
let overlaps = range1.0 < range2.1 && range2.0 < range1.1;
if overlaps && !patch1.replacement.is_empty() && !patch2.replacement.is_empty()
{
return Err(PatchError::OverlappingRanges { range1, range2 });
}
}
}
resolved.sort_by_key(|(_, range)| std::cmp::Reverse(range.0));
let mut rope = rope;
for (patch, _) in resolved {
patch.apply(&mut rope)?;
}
results.insert(file, rope.to_string());
}
Ok(results)
}
pub fn write_to_files(&self) -> Result<(), PatchError> {
let results = self.apply_to_files()?;
for (file, content) in results {
std::fs::write(&file, content).map_err(PatchError::IoError)?;
}
Ok(())
}
}
impl Default for PatchSet {
fn default() -> Self {
Self::new()
}
}