mod format;
mod parse;
pub use format::PatchFormatter;
pub use parse::ParsePatchError;
use std::{borrow::Cow, fmt, ops};
const NO_NEWLINE_AT_EOF: &str = "\\ No newline at end of file";
#[derive(PartialEq, Eq)]
pub struct Patch<'a, T: ToOwned + ?Sized> {
original: Option<Filename<'a, T>>,
modified: Option<Filename<'a, T>>,
hunks: Vec<Hunk<'a, T>>,
}
impl<'a, T: ToOwned + ?Sized> Patch<'a, T> {
pub(crate) fn new<O, M>(
original: Option<O>,
modified: Option<M>,
hunks: Vec<Hunk<'a, T>>,
) -> Self
where
O: Into<Cow<'a, T>>,
M: Into<Cow<'a, T>>,
{
let original = original.map(|o| Filename(o.into()));
let modified = modified.map(|m| Filename(m.into()));
Self {
original,
modified,
hunks,
}
}
pub fn original(&self) -> Option<&T> {
self.original.as_ref().map(AsRef::as_ref)
}
pub fn modified(&self) -> Option<&T> {
self.modified.as_ref().map(AsRef::as_ref)
}
pub fn hunks(&self) -> &[Hunk<'_, T>] {
&self.hunks
}
pub fn reverse(&self) -> Patch<'_, T> {
let hunks = self.hunks.iter().map(Hunk::reverse).collect();
Patch {
original: self.modified.clone(),
modified: self.original.clone(),
hunks,
}
}
}
impl<T: AsRef<[u8]> + ToOwned + ?Sized> Patch<'_, T> {
pub fn to_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::new();
PatchFormatter::new()
.write_patch_into(self, &mut bytes)
.unwrap();
bytes
}
}
impl<'a> Patch<'a, str> {
#[allow(clippy::should_implement_trait)]
pub fn from_str(s: &'a str) -> Result<Patch<'a, str>, ParsePatchError> {
parse::parse(s)
}
}
impl<'a> Patch<'a, [u8]> {
pub fn from_bytes(s: &'a [u8]) -> Result<Patch<'a, [u8]>, ParsePatchError> {
parse::parse_bytes(s)
}
}
impl<T: ToOwned + ?Sized> Clone for Patch<'_, T> {
fn clone(&self) -> Self {
Self {
original: self.original.clone(),
modified: self.modified.clone(),
hunks: self.hunks.clone(),
}
}
}
impl fmt::Display for Patch<'_, str> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", PatchFormatter::new().fmt_patch(self))
}
}
impl<T: ?Sized, O> fmt::Debug for Patch<'_, T>
where
T: ToOwned<Owned = O> + fmt::Debug,
O: std::borrow::Borrow<T> + fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Patch")
.field("original", &self.original)
.field("modified", &self.modified)
.field("hunks", &self.hunks)
.finish()
}
}
#[derive(PartialEq, Eq)]
struct Filename<'a, T: ToOwned + ?Sized>(Cow<'a, T>);
const ESCAPED_CHARS: &[char] = &['\n', '\t', '\0', '\r', '\"', '\\'];
const ESCAPED_CHARS_BYTES: &[u8] = &[b'\n', b'\t', b'\0', b'\r', b'\"', b'\\'];
impl Filename<'_, str> {
fn needs_to_be_escaped(&self) -> bool {
self.0.contains(ESCAPED_CHARS)
}
}
impl<T: ToOwned + AsRef<[u8]> + ?Sized> Filename<'_, T> {
fn needs_to_be_escaped_bytes(&self) -> bool {
self.0
.as_ref()
.as_ref()
.iter()
.any(|b| ESCAPED_CHARS_BYTES.contains(b))
}
fn write_into<W: std::io::Write>(&self, mut w: W) -> std::io::Result<()> {
if self.needs_to_be_escaped_bytes() {
w.write_all(b"\"")?;
for b in self.0.as_ref().as_ref() {
if ESCAPED_CHARS_BYTES.contains(b) {
w.write_all(b"\\")?;
}
w.write_all(&[*b])?;
}
w.write_all(b"\"")?;
} else {
w.write_all(self.0.as_ref().as_ref())?;
}
Ok(())
}
}
impl<T: ToOwned + ?Sized> AsRef<T> for Filename<'_, T> {
fn as_ref(&self) -> &T {
&self.0
}
}
impl<T: ToOwned + ?Sized> ops::Deref for Filename<'_, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T: ToOwned + ?Sized> Clone for Filename<'_, T> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl fmt::Display for Filename<'_, str> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use std::fmt::Write;
if self.needs_to_be_escaped() {
f.write_char('\"')?;
for c in self.0.chars() {
if ESCAPED_CHARS.contains(&c) {
f.write_char('\\')?;
}
f.write_char(c)?;
}
f.write_char('\"')?;
} else {
f.write_str(&self.0)?;
}
Ok(())
}
}
impl<T: ?Sized, O> fmt::Debug for Filename<'_, T>
where
T: ToOwned<Owned = O> + fmt::Debug,
O: std::borrow::Borrow<T> + fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("Filename").field(&self.0).finish()
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct Hunk<'a, T: ?Sized> {
old_range: HunkRange,
new_range: HunkRange,
function_context: Option<&'a T>,
lines: Vec<Line<'a, T>>,
}
fn hunk_lines_count<T: ?Sized>(lines: &[Line<'_, T>]) -> (usize, usize) {
lines.iter().fold((0, 0), |count, line| match line {
Line::Context(_) => (count.0 + 1, count.1 + 1),
Line::Delete(_) => (count.0 + 1, count.1),
Line::Insert(_) => (count.0, count.1 + 1),
})
}
impl<'a, T: ?Sized> Hunk<'a, T> {
pub(crate) fn new(
old_range: HunkRange,
new_range: HunkRange,
function_context: Option<&'a T>,
lines: Vec<Line<'a, T>>,
) -> Self {
let (old_count, new_count) = hunk_lines_count(&lines);
assert_eq!(old_range.len, old_count);
assert_eq!(new_range.len, new_count);
Self {
old_range,
new_range,
function_context,
lines,
}
}
pub fn old_range(&self) -> HunkRange {
self.old_range
}
pub fn new_range(&self) -> HunkRange {
self.new_range
}
pub fn function_context(&self) -> Option<&T> {
self.function_context
}
pub fn lines(&self) -> &[Line<'a, T>] {
&self.lines
}
pub fn reverse(&self) -> Self {
let lines = self.lines.iter().map(Line::reverse).collect();
Self {
old_range: self.new_range,
new_range: self.old_range,
function_context: self.function_context,
lines,
}
}
}
impl<T: ?Sized> Clone for Hunk<'_, T> {
fn clone(&self) -> Self {
Self {
old_range: self.old_range,
new_range: self.new_range,
function_context: self.function_context,
lines: self.lines.clone(),
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct HunkRange {
start: usize,
len: usize,
}
impl HunkRange {
pub(crate) fn new(start: usize, len: usize) -> Self {
Self { start, len }
}
pub fn range(&self) -> ops::Range<usize> {
self.start..self.end()
}
pub fn start(&self) -> usize {
self.start
}
pub fn end(&self) -> usize {
self.start + self.len
}
pub fn len(&self) -> usize {
self.len
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
}
impl fmt::Display for HunkRange {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.start)?;
if self.len != 1 {
write!(f, ",{}", self.len)?;
}
Ok(())
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum Line<'a, T: ?Sized> {
Context(&'a T),
Delete(&'a T),
Insert(&'a T),
}
impl<T: ?Sized> Copy for Line<'_, T> {}
impl<T: ?Sized> Clone for Line<'_, T> {
fn clone(&self) -> Self {
*self
}
}
impl<T: ?Sized> Line<'_, T> {
pub fn reverse(&self) -> Self {
match self {
Line::Context(s) => Line::Context(s),
Line::Delete(s) => Line::Insert(s),
Line::Insert(s) => Line::Delete(s),
}
}
}