Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor #26

Merged
merged 3 commits into from
Nov 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
210 changes: 209 additions & 1 deletion src/quilt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ mod find_common_patch_suffix_tests {
}

/// A entry in a series file
#[derive(Debug)]
#[derive(Debug, PartialEq, Eq)]
pub enum SeriesEntry {
/// A patch entry
Patch {
Expand Down Expand Up @@ -214,6 +214,19 @@ impl Series {

Ok(())
}

/// Get an iterator over the entries in the series file
pub fn iter(&self) -> std::slice::Iter<SeriesEntry> {
self.entries.iter()
}
}

impl std::ops::Index<usize> for Series {
type Output = SeriesEntry;

fn index(&self, index: usize) -> &Self::Output {
&self.entries[index]
}
}

impl Default for Series {
Expand All @@ -235,3 +248,198 @@ pub fn read_quilt_series<R: std::io::Read>(mut reader: R) -> std::path::PathBuf
reader.read_to_string(&mut s).unwrap();
s.into()
}

/// A quilt patch
pub struct QuiltPatch {
/// The name of the patch
pub name: String,

/// The options for the patch
pub options: Vec<String>,

/// The patch contents
pub patch: Vec<u8>,
}

impl QuiltPatch {
/// Get the patch contents as a byte slice
pub fn as_bytes(&self) -> &[u8] {
&self.patch
}

/// Get the name of the patch
pub fn name(&self) -> &str {
&self.name
}

/// Get the patch options
pub fn options(&self) -> &[String] {
&self.options
}

/// Get the patch contents
pub fn parse(&self) -> Result<Vec<crate::unified::UnifiedPatch>, crate::unified::Error> {
let lines = self.patch.split(|&b| b == b'\n');
crate::unified::parse_patches(lines.map(|x| x.to_vec()))
.filter_map(|patch| match patch {
Ok(crate::unified::PlainOrBinaryPatch::Plain(patch)) => Some(Ok(patch)),
Ok(crate::unified::PlainOrBinaryPatch::Binary(_)) => None,
Err(err) => Some(Err(err)),
})
.collect()
}
}

/// Read quilt patches from a directory.
pub fn iter_quilt_patches(directory: &std::path::Path) -> impl Iterator<Item = QuiltPatch> + '_ {
let series_path = directory.join("series");

let series = if series_path.exists() {
Series::read(std::fs::File::open(series_path).unwrap()).unwrap()
} else {
Series::new()
};

series
.iter()
.filter_map(move |entry| {
let (patch, options) = match entry {
SeriesEntry::Patch { name, options } => (name, options),
SeriesEntry::Comment(_) => return None,
};
let p = directory.join(patch);
let lines = std::fs::read_to_string(p).unwrap();
Some(QuiltPatch {
name: patch.to_string(),
patch: lines.into_bytes(),
options: options.clone(),
})
})
.collect::<Vec<_>>()
.into_iter()
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_series_read() {
let series = Series::read(
r#"0001-foo.patch
# This is a comment
0002-bar.patch --reverse
0003-baz.patch --reverse --fuzz=3
"#
.as_bytes(),
)
.unwrap();
assert_eq!(series.len(), 3);
assert_eq!(
series[0],
SeriesEntry::Patch {
name: "0001-foo.patch".to_string(),
options: vec![]
}
);
assert_eq!(
series[1],
SeriesEntry::Comment("# This is a comment".to_string())
);
assert_eq!(
series[2],
SeriesEntry::Patch {
name: "0002-bar.patch".to_string(),
options: vec!["--reverse".to_string()]
}
);
assert_eq!(
series[3],
SeriesEntry::Patch {
name: "0003-baz.patch".to_string(),
options: vec!["--reverse".to_string(), "--fuzz=3".to_string()]
}
);
}

#[test]
fn test_series_write() {
let mut series = Series::new();
series.append("0001-foo.patch", None);
series.append("0002-bar.patch", Some(&["--reverse".to_string()]));
series.append(
"0003-baz.patch",
Some(&["--reverse".to_string(), "--fuzz=3".to_string()]),
);

let mut writer = vec![];
series.write(&mut writer).unwrap();
let series = String::from_utf8(writer).unwrap();
assert_eq!(
series,
"0001-foo.patch\n0002-bar.patch --reverse\n0003-baz.patch --reverse --fuzz=3\n"
);
}

#[test]
fn test_series_remove() {
let mut series = Series::new();
series.append("0001-foo.patch", None);
series.append("0002-bar.patch", Some(&["--reverse".to_string()]));
series.append(
"0003-baz.patch",
Some(&["--reverse".to_string(), "--fuzz=3".to_string()]),
);

series.remove("0002-bar.patch");

let mut writer = vec![];
series.write(&mut writer).unwrap();
let series = String::from_utf8(writer).unwrap();
assert_eq!(
series,
"0001-foo.patch\n0003-baz.patch --reverse --fuzz=3\n"
);
}

#[test]
fn test_series_contains() {
let mut series = Series::new();
series.append("0001-foo.patch", None);
series.append("0002-bar.patch", Some(&["--reverse".to_string()]));
series.append(
"0003-baz.patch",
Some(&["--reverse".to_string(), "--fuzz=3".to_string()]),
);

assert!(series.contains("0002-bar.patch"));
assert!(!series.contains("0004-qux.patch"));
}

#[test]
fn test_series_patches() {
let mut series = Series::new();
series.append("0001-foo.patch", None);
series.append("0002-bar.patch", Some(&["--reverse".to_string()]));
series.append(
"0003-baz.patch",
Some(&["--reverse".to_string(), "--fuzz=3".to_string()]),
);

let patches: Vec<_> = series.patches().collect();
assert_eq!(
patches,
&["0001-foo.patch", "0002-bar.patch", "0003-baz.patch"]
);
}

#[test]
fn test_series_is_empty() {
let series = Series::new();
assert!(series.is_empty());

let mut series = Series::new();
series.append("0001-foo.patch", None);
assert!(!series.is_empty());
}
}
78 changes: 58 additions & 20 deletions src/unified.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
//! Parsing of unified patches
use crate::{ContentPatch, SingleFilePatch};
use regex::bytes::Regex;
use std::num::ParseIntError;

Expand Down Expand Up @@ -330,7 +331,7 @@ mod iter_hunks_tests {
///
/// # Arguments
/// * `iter_lines`: Iterator over lines
pub fn parse_patch<'a, I>(iter_lines: I) -> Result<Box<dyn crate::SingleFilePatch>, Error>
pub fn parse_patch<'a, I>(iter_lines: I) -> Result<PlainOrBinaryPatch, Error>
where
I: Iterator<Item = &'a [u8]> + 'a,
{
Expand All @@ -339,7 +340,7 @@ where
let ((orig_name, orig_ts), (mod_name, mod_ts)) = match get_patch_names(&mut iter_lines) {
Ok(names) => names,
Err(Error::BinaryFiles(orig_name, mod_name)) => {
return Ok(Box::new(BinaryPatch(orig_name, mod_name)));
return Ok(PlainOrBinaryPatch::Binary(BinaryPatch(orig_name, mod_name)));
}
Err(e) => return Err(e),
};
Expand All @@ -348,11 +349,12 @@ where
for hunk in iter_hunks(&mut iter_lines) {
patch.hunks.push(hunk?);
}
Ok(Box::new(patch))
Ok(PlainOrBinaryPatch::Plain(patch))
}

#[cfg(test)]
mod patches_tests {
use super::*;
macro_rules! test_patch {
($name:ident, $orig:expr, $mod:expr, $patch:expr) => {
#[test]
Expand Down Expand Up @@ -826,25 +828,57 @@ mod iter_file_patch_tests {
}
}

/// A patch that can be applied to a single file
pub enum PlainOrBinaryPatch {
/// A unified patch
Plain(UnifiedPatch),

/// An indication that two binary files differ
Binary(BinaryPatch),
}

impl SingleFilePatch for PlainOrBinaryPatch {
fn oldname(&self) -> &[u8] {
match self {
Self::Plain(patch) => patch.orig_name.as_slice(),
Self::Binary(patch) => patch.0.as_slice(),
}
}

fn newname(&self) -> &[u8] {
match self {
Self::Plain(patch) => patch.mod_name.as_slice(),
Self::Binary(patch) => patch.1.as_slice(),
}
}
}

impl crate::ContentPatch for PlainOrBinaryPatch {
fn apply_exact(&self, orig: &[u8]) -> Result<Vec<u8>, crate::ApplyError> {
match self {
Self::Plain(patch) => patch.apply_exact(orig),
Self::Binary(_) => Err(crate::ApplyError::Unapplyable),
}
}
}

/// Parse a patch file
///
/// # Arguments
/// * `iter`: Iterator over lines
pub fn parse_patches<'a, I>(iter: I) -> Result<Vec<Box<dyn crate::SingleFilePatch>>, Error>
pub fn parse_patches<I>(iter: I) -> impl Iterator<Item = Result<PlainOrBinaryPatch, Error>>
where
I: Iterator<Item = Vec<u8>>,
{
iter_file_patch(iter)
.filter_map(|entry| match entry {
Ok(FileEntry::Patch(lines)) => match parse_patch(lines.iter().map(|l| l.as_slice())) {
Ok(patch) => Some(Ok(patch)),
Err(e) => Some(Err(e)),
},
Ok(FileEntry::Junk(_)) => None,
Ok(FileEntry::Meta(_)) => None,
iter_file_patch(iter).filter_map(|entry| match entry {
Ok(FileEntry::Patch(lines)) => match parse_patch(lines.iter().map(|l| l.as_slice())) {
Ok(patch) => Some(Ok(patch)),
Err(e) => Some(Err(e)),
})
.collect()
},
Ok(FileEntry::Junk(_)) => None,
Ok(FileEntry::Meta(_)) => None,
Err(e) => Some(Err(e)),
})
}

#[cfg(test)]
Expand All @@ -860,7 +894,8 @@ mod parse_patches_tests {
" # <[email protected]>\n",
" #\n",
];
let patches = super::parse_patches(lines.iter().map(|l| l.as_bytes().to_vec())).unwrap();
let patches =
super::parse_patches(lines.iter().map(|l| l.as_bytes().to_vec())).collect::<Vec<_>>();
assert_eq!(patches.len(), 1);
}
}
Expand All @@ -869,7 +904,7 @@ mod parse_patches_tests {
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BinaryPatch(pub Vec<u8>, pub Vec<u8>);

impl crate::SingleFilePatch for BinaryPatch {
impl SingleFilePatch for BinaryPatch {
fn oldname(&self) -> &[u8] {
&self.0
}
Expand Down Expand Up @@ -984,27 +1019,30 @@ impl UnifiedPatch {
///
/// # Arguments
/// * `iter`: Iterator over lines
pub fn parse_patches<'a, I>(iter: I) -> Result<Vec<Box<dyn crate::SingleFilePatch>>, Error>
pub fn parse_patches<I>(iter: I) -> Result<Vec<PlainOrBinaryPatch>, Error>
where
I: Iterator<Item = Vec<u8>>,
{
iter_file_patch(iter)
.filter_map(|entry| match entry {
Ok(FileEntry::Patch(lines)) => {
match Self::parse_patch(lines.iter().map(|l| l.as_slice())) {
Ok(patch) => Some(Ok(Box::new(patch) as Box<dyn crate::SingleFilePatch>)),
Ok(patch) => Some(Ok(PlainOrBinaryPatch::Plain(patch))),
Err(e) => Some(Err(e)),
}
}
Ok(FileEntry::Junk(_)) => None,
Ok(FileEntry::Meta(_)) => None,
Err(Error::BinaryFiles(orig_name, mod_name)) => Some(Ok(
PlainOrBinaryPatch::Binary(BinaryPatch(orig_name, mod_name)),
)),
Err(e) => Some(Err(e)),
})
.collect()
}
}

impl crate::SingleFilePatch for UnifiedPatch {
impl SingleFilePatch for UnifiedPatch {
/// Old file name
fn oldname(&self) -> &[u8] {
&self.orig_name
Expand All @@ -1016,7 +1054,7 @@ impl crate::SingleFilePatch for UnifiedPatch {
}
}

impl crate::ContentPatch for UnifiedPatch {
impl ContentPatch for UnifiedPatch {
/// Apply this patch to a file
fn apply_exact(&self, orig: &[u8]) -> Result<Vec<u8>, crate::ApplyError> {
let orig_lines = splitlines(orig).map(|l| l.to_vec());
Expand Down