pub(crate) mod error;
mod parse;
#[cfg(test)]
mod tests;
use alloc::borrow::Cow;
use alloc::borrow::ToOwned;
use core::fmt;
use crate::Patch;
use crate::binary::BinaryPatch;
use crate::utils::Text;
pub use error::PatchSetParseError;
use error::PatchSetParseErrorKind;
pub use parse::PatchSet;
#[derive(Debug, Clone)]
pub struct ParseOptions {
pub(crate) format: Format,
}
#[derive(Debug, Clone, Copy)]
pub(crate) enum Format {
UniDiff,
GitDiff,
}
impl ParseOptions {
pub fn unidiff() -> Self {
Self {
format: Format::UniDiff,
}
}
pub fn gitdiff() -> Self {
Self {
format: Format::GitDiff,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FileMode {
Regular,
Executable,
Symlink,
Gitlink,
}
impl core::str::FromStr for FileMode {
type Err = PatchSetParseError;
fn from_str(mode: &str) -> Result<Self, Self::Err> {
match mode {
"100644" => Ok(Self::Regular),
"100755" => Ok(Self::Executable),
"120000" => Ok(Self::Symlink),
"160000" => Ok(Self::Gitlink),
_ => Err(PatchSetParseErrorKind::InvalidFileMode(mode.to_owned()).into()),
}
}
}
#[derive(Clone, PartialEq, Eq)]
pub enum PatchKind<'a, T: ToOwned + ?Sized> {
Text(Patch<'a, T>),
Binary(BinaryPatch<'a>),
}
impl<T: ?Sized, O> fmt::Debug for PatchKind<'_, T>
where
T: ToOwned<Owned = O> + fmt::Debug,
O: core::borrow::Borrow<T> + fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PatchKind::Text(patch) => f.debug_tuple("Text").field(patch).finish(),
PatchKind::Binary(patch) => f.debug_tuple("Binary").field(patch).finish(),
}
}
}
impl<'a, T: ToOwned + ?Sized> PatchKind<'a, T> {
pub fn as_text(&self) -> Option<&Patch<'a, T>> {
match self {
PatchKind::Text(patch) => Some(patch),
PatchKind::Binary(_) => None,
}
}
pub fn as_binary(&self) -> Option<&BinaryPatch<'a>> {
match self {
PatchKind::Binary(patch) => Some(patch),
PatchKind::Text(_) => None,
}
}
pub fn is_binary(&self) -> bool {
matches!(self, PatchKind::Binary(_))
}
}
#[derive(Clone, PartialEq, Eq)]
pub struct FilePatch<'a, T: ToOwned + ?Sized> {
operation: FileOperation<'a, T>,
kind: PatchKind<'a, T>,
old_mode: Option<FileMode>,
new_mode: Option<FileMode>,
}
impl<T: ?Sized, O> fmt::Debug for FilePatch<'_, T>
where
T: ToOwned<Owned = O> + fmt::Debug,
O: core::borrow::Borrow<T> + fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("FilePatch")
.field("operation", &self.operation)
.field("kind", &self.kind)
.field("old_mode", &self.old_mode)
.field("new_mode", &self.new_mode)
.finish()
}
}
impl<'a, T: ToOwned + ?Sized> FilePatch<'a, T> {
fn new(
operation: FileOperation<'a, T>,
patch: Patch<'a, T>,
old_mode: Option<FileMode>,
new_mode: Option<FileMode>,
) -> Self {
Self {
operation,
kind: PatchKind::Text(patch),
old_mode,
new_mode,
}
}
fn new_binary(
operation: FileOperation<'a, T>,
patch: BinaryPatch<'a>,
old_mode: Option<FileMode>,
new_mode: Option<FileMode>,
) -> Self {
Self {
operation,
kind: PatchKind::Binary(patch),
old_mode,
new_mode,
}
}
pub fn operation(&self) -> &FileOperation<'a, T> {
&self.operation
}
pub fn patch(&self) -> &PatchKind<'a, T> {
&self.kind
}
pub fn into_patch(self) -> PatchKind<'a, T> {
self.kind
}
pub fn old_mode(&self) -> Option<&FileMode> {
self.old_mode.as_ref()
}
pub fn new_mode(&self) -> Option<&FileMode> {
self.new_mode.as_ref()
}
}
#[derive(PartialEq, Eq)]
pub enum FileOperation<'a, T: ToOwned + ?Sized> {
Delete(Cow<'a, T>),
Create(Cow<'a, T>),
Modify {
original: Cow<'a, T>,
modified: Cow<'a, T>,
},
Rename {
from: Cow<'a, T>,
to: Cow<'a, T>,
},
Copy {
from: Cow<'a, T>,
to: Cow<'a, T>,
},
}
impl<T: ToOwned + ?Sized> Clone for FileOperation<'_, T> {
fn clone(&self) -> Self {
match self {
Self::Delete(p) => Self::Delete(p.clone()),
Self::Create(p) => Self::Create(p.clone()),
Self::Modify { original, modified } => Self::Modify {
original: original.clone(),
modified: modified.clone(),
},
Self::Rename { from, to } => Self::Rename {
from: from.clone(),
to: to.clone(),
},
Self::Copy { from, to } => Self::Copy {
from: from.clone(),
to: to.clone(),
},
}
}
}
impl<T: ?Sized, O> fmt::Debug for FileOperation<'_, T>
where
T: ToOwned<Owned = O> + fmt::Debug,
O: core::borrow::Borrow<T> + fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Delete(p) => f.debug_tuple("Delete").field(p).finish(),
Self::Create(p) => f.debug_tuple("Create").field(p).finish(),
Self::Modify { original, modified } => f
.debug_struct("Modify")
.field("original", original)
.field("modified", modified)
.finish(),
Self::Rename { from, to } => f
.debug_struct("Rename")
.field("from", from)
.field("to", to)
.finish(),
Self::Copy { from, to } => f
.debug_struct("Copy")
.field("from", from)
.field("to", to)
.finish(),
}
}
}
impl<T: Text + ?Sized> FileOperation<'_, T> {
pub fn strip_prefix(&self, n: usize) -> FileOperation<'_, T> {
fn strip<T: Text + ?Sized>(path: &T, n: usize) -> &T {
let mut remaining = path;
for _ in 0..n {
match remaining.split_at_exclusive("/") {
Some((_first, rest)) => remaining = rest,
None => return remaining,
}
}
remaining
}
match self {
FileOperation::Delete(path) => FileOperation::Delete(Cow::Borrowed(strip(path, n))),
FileOperation::Create(path) => FileOperation::Create(Cow::Borrowed(strip(path, n))),
FileOperation::Modify { original, modified } => FileOperation::Modify {
original: Cow::Borrowed(strip(original, n)),
modified: Cow::Borrowed(strip(modified, n)),
},
FileOperation::Rename { from, to } => FileOperation::Rename {
from: Cow::Borrowed(strip(from, n)),
to: Cow::Borrowed(strip(to, n)),
},
FileOperation::Copy { from, to } => FileOperation::Copy {
from: Cow::Borrowed(strip(from, n)),
to: Cow::Borrowed(strip(to, n)),
},
}
}
pub fn is_create(&self) -> bool {
matches!(self, FileOperation::Create(_))
}
pub fn is_delete(&self) -> bool {
matches!(self, FileOperation::Delete(_))
}
pub fn is_modify(&self) -> bool {
matches!(self, FileOperation::Modify { .. })
}
pub fn is_rename(&self) -> bool {
matches!(self, FileOperation::Rename { .. })
}
pub fn is_copy(&self) -> bool {
matches!(self, FileOperation::Copy { .. })
}
}