use crate::Rope;
#[cfg(feature = "facet")]
use facet::Facet;
pub mod error;
pub use error::PatchError;
use crate::snip::snippet::{Boundary, BoundaryMode, Snippet};
use crate::snip::target::Target;
#[derive(Debug, Clone)]
#[cfg_attr(feature = "facet", derive(Facet))]
pub struct Patch {
pub file: Option<String>,
pub snippet: Snippet,
pub replacement: String,
#[cfg_attr(feature = "facet", facet(default))]
#[cfg(feature = "symbol_path")]
pub symbol_path: Option<Vec<String>>,
}
impl Patch {
pub fn apply(&self, rope: &mut Rope) -> Result<(), PatchError> {
let resolution = self.snippet.resolve(rope)?;
if resolution.end > rope.len_chars() {
return Err(PatchError::RangeOutOfBounds);
}
if resolution.start < resolution.end {
rope.remove(resolution.start..resolution.end);
}
rope.insert(resolution.start, &self.replacement);
Ok(())
}
pub fn apply_to_file(&self) -> Result<String, PatchError> {
let file_path = self.file.as_ref().ok_or(PatchError::MissingFilePath)?;
let content = std::fs::read_to_string(file_path).map_err(PatchError::IoError)?;
let mut rope = Rope::from_str(&content);
self.apply(&mut rope)?;
Ok(rope.to_string())
}
pub fn write_to_file(&self) -> Result<(), PatchError> {
let file_path = self.file.as_ref().ok_or(PatchError::MissingFilePath)?;
let content = self.apply_to_file()?;
std::fs::write(file_path, content).map_err(PatchError::IoError)?;
Ok(())
}
#[must_use]
pub fn in_memory(snippet: Snippet, replacement: impl Into<String>) -> Self {
Self {
file: None,
snippet,
replacement: replacement.into(),
#[cfg(feature = "symbol_path")]
symbol_path: None,
}
}
pub fn apply_to_string(&self, content: &str) -> Result<String, PatchError> {
let mut rope = Rope::from_str(content);
self.apply(&mut rope)?;
Ok(rope.to_string())
}
#[must_use]
pub fn from_literal_target(
file: String,
needle: &str,
mode: BoundaryMode,
replacement: impl Into<String>,
) -> Self {
let target = Target::Literal(needle.to_string());
let boundary = Boundary::new(target, mode);
let snippet = Snippet::At(boundary);
Self {
file: Some(file),
snippet,
replacement: replacement.into(),
#[cfg(feature = "symbol_path")]
symbol_path: None,
}
}
#[must_use]
pub fn from_line_range(
file: String,
start_line: usize,
end_line: usize,
replacement: impl Into<String>,
) -> Self {
let start = Boundary::new(Target::Line(start_line), BoundaryMode::Include);
let end = Boundary::new(Target::Line(end_line), BoundaryMode::Exclude);
let snippet = Snippet::Between { start, end };
Self {
file: Some(file),
snippet,
replacement: replacement.into(),
#[cfg(feature = "symbol_path")]
symbol_path: None,
}
}
#[must_use]
pub fn from_line_positions(
file: String,
line_start: usize,
col_start: usize,
line_end: usize,
col_end: usize,
_rope: &Rope,
replacement: impl Into<String>,
) -> Self {
let start_target = Target::Position {
line: line_start + 1, col: col_start + 1,
};
let end_target = Target::Position {
line: line_end + 1,
col: col_end + 1,
};
let start = Boundary::new(start_target, BoundaryMode::Include);
let end = Boundary::new(end_target, BoundaryMode::Exclude);
let snippet = Snippet::Between { start, end };
Self {
file: Some(file),
snippet,
replacement: replacement.into(),
#[cfg(feature = "symbol_path")]
symbol_path: None,
}
}
}