patchkit/
lib.rs

1#![deny(missing_docs)]
2//! A crate for parsing and manipulating patches.
3//!
4//! # Examples
5//!
6//! ```
7//! use patchkit::ContentPatch;
8//! use patchkit::unified::parse_patch;
9//! use patchkit::unified::{UnifiedPatch, Hunk, HunkLine};
10//!
11//! let patch = UnifiedPatch::parse_patch(vec![
12//!     "--- a/file1\n",
13//!     "+++ b/file1\n",
14//!     "@@ -1,1 +1,1 @@\n",
15//!     "-a\n",
16//!     "+b\n",
17//! ].into_iter().map(|s| s.as_bytes())).unwrap();
18//!
19//! assert_eq!(patch, UnifiedPatch {
20//!     orig_name: b"a/file1".to_vec(),
21//!     mod_name: b"b/file1".to_vec(),
22//!     orig_ts: None,
23//!     mod_ts: None,
24//!     hunks: vec![
25//!         Hunk {
26//!             mod_pos: 1,
27//!             mod_range: 1,
28//!             orig_pos: 1,
29//!             orig_range: 1,
30//!             lines: vec![
31//!                 HunkLine::RemoveLine(b"a\n".to_vec()),
32//!                 HunkLine::InsertLine(b"b\n".to_vec()),
33//!             ],
34//!             tail: None
35//!         },
36//!     ],
37//! });
38//!
39//! let applied = patch.apply_exact(&b"a\n"[..]).unwrap();
40//! assert_eq!(applied, b"b\n");
41//! ```
42
43pub mod ed;
44pub mod quilt;
45pub mod timestamp;
46pub mod unified;
47
48/// Strip the specified number of path components from the beginning of the path.
49pub fn strip_prefix(path: &std::path::Path, prefix: usize) -> &std::path::Path {
50    let mut components = path.components();
51    for _ in 0..prefix {
52        components.next();
53    }
54    std::path::Path::new(components.as_path())
55}
56
57/// Error that occurs when applying a patch
58#[derive(Debug)]
59pub enum ApplyError {
60    /// A conflict occurred
61    Conflict(String),
62
63    /// The patch is unapplyable
64    Unapplyable,
65}
66
67impl std::fmt::Display for ApplyError {
68    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
69        match self {
70            Self::Conflict(reason) => write!(f, "Conflict: {}", reason),
71            Self::Unapplyable => write!(f, "Patch unapplyable"),
72        }
73    }
74}
75
76impl std::error::Error for ApplyError {}
77
78/// A patch to a single file
79pub trait SingleFilePatch: ContentPatch {
80    /// Old file name
81    fn oldname(&self) -> &[u8];
82
83    /// New file name
84    fn newname(&self) -> &[u8];
85}
86
87/// A patch that can be applied to file content
88pub trait ContentPatch {
89    /// Apply this patch to a file
90    fn apply_exact(&self, orig: &[u8]) -> Result<Vec<u8>, ApplyError>;
91}
92
93#[test]
94fn test_strip_prefix() {
95    assert_eq!(
96        std::path::PathBuf::from("b"),
97        strip_prefix(std::path::Path::new("a/b"), 1)
98    );
99    assert_eq!(
100        std::path::PathBuf::from("a/b"),
101        strip_prefix(std::path::Path::new("a/b"), 0)
102    );
103    assert_eq!(
104        std::path::PathBuf::from(""),
105        strip_prefix(std::path::Path::new("a/b"), 2)
106    );
107}