aboutsummaryrefslogtreecommitdiff
path: root/libgitmail/src/patch/parsers.rs
diff options
context:
space:
mode:
Diffstat (limited to 'libgitmail/src/patch/parsers.rs')
-rw-r--r--libgitmail/src/patch/parsers.rs163
1 files changed, 163 insertions, 0 deletions
diff --git a/libgitmail/src/patch/parsers.rs b/libgitmail/src/patch/parsers.rs
new file mode 100644
index 0000000..9779e2b
--- /dev/null
+++ b/libgitmail/src/patch/parsers.rs
@@ -0,0 +1,163 @@
+//! Contains additional parsers that are required for git patch emails
+//!
+//! The parsers are written in nom, and generate
+//! [Patch](../struct.Patch.html) objects, or an Error. A parser
+//! should neven panic.
+//!
+//! There's also some unit and quick check tests that make sure that
+//! valid git patch emails can be parsed. If you have emails you
+//! don't mind becoming part of the test set, feel free to email in a
+//! patch!
+
+use crate::{
+ patch::{Header, HeaderMap},
+ Error, Result,
+};
+use nom::{
+ bytes::complete::{is_not, tag, take_till},
+ character::complete::char,
+ combinator::opt,
+ sequence::{delimited, preceded},
+ IResult,
+};
+
+/// The segment data for a patch mail
+#[derive(Clone, Default, Debug, PartialEq, Eq)]
+pub struct Segment {
+ /// The number of a patch in it's series
+ pub this: u8,
+ /// The total number of patches in a seriesThe first
+ pub max: u8,
+}
+
+/// The typed subject line of a patch mail
+///
+/// A message might be: "[PATCH v2 1/5], which is parsed into this
+/// structure to be easier to work with
+#[derive(Clone, Default, Debug, PartialEq, Eq)]
+pub struct Subject {
+ /// The patch revision (e.g. `v2`)
+ pub version: u8,
+ /// Patch segment information (e.g. `1/5`)
+ pub segment: Option<Segment>,
+ /// Subject prefix (e.g. `PATCH`)
+ pub prefix: String,
+ /// The actual patch commit message
+ pub message: String,
+}
+
+impl Subject {
+ /// Create a typed subject from a subject string
+ pub fn from(s: String) -> Result<Self> {
+ Ok(Self::parse(s.as_str()).unwrap().1)
+ }
+
+ /// This is a terrible function that does terrible things
+ fn parse(input: &str) -> IResult<&str, Self> {
+ let input = opt(tag("Re:"))(input).map(|(i, _)| i.trim())?;
+ let (msg, input) = Self::parens(input).map(|(a, b)| (a.trim(), b))?;
+ let (input, prefix) =
+ take_till(|c| c == ' ')(input).map(|(a, b)| (a.trim(), b))?;
+
+ let (input, version) =
+ opt(preceded(tag("v"), take_till(|c| c == ' ')))(input)
+ .map(|(i, v)| (i.trim(), v.map_or(1, |v| v.parse::<u8>().unwrap())))?;
+
+ let (max_str, this_opt) = opt(take_till(|c| c == '/'))(input)?;
+
+ let segment = if max_str.len() > 1 {
+ let max = max_str[1..].parse::<u8>().unwrap_or(1);
+ let this = this_opt.unwrap().parse::<u8>().unwrap();
+ Some(Segment { max, this })
+ } else {
+ None
+ };
+
+ Ok((
+ input,
+ Self {
+ message: msg.into(),
+ prefix: prefix.into(),
+ version,
+ segment,
+ },
+ ))
+ }
+
+ /// Grab text between the [ ... ] section of a subject
+ fn parens(input: &str) -> IResult<&str, &str> {
+ delimited(char('['), is_not("]"), char(']'))(input)
+ }
+}
+
+/// A utility function that can get a header into a semi-typed field
+pub(crate) fn get_header(headers: &HeaderMap, key: &str) -> Result<Header> {
+ headers.get(key).map_or(Err(Error::FailedParsing), |h| {
+ match h.split(",").map(|s| s.trim()).collect::<Vec<_>>() {
+ vec if vec.len() == 0 => Err(Error::FailedParsing),
+ vec if vec.len() == 1 => {
+ Ok(Header::Single(String::from(*vec.get(0).unwrap())))
+ }
+ vec => Ok(Header::Multi(
+ vec.into_iter().map(|s| String::from(s)).collect(),
+ )),
+ }
+ })
+}
+
+#[test]
+fn full_test() {
+ let subject =
+ "Re: [PATCH v2 2/3] docs: fix spelling of \"contributors\"".into();
+ assert_eq!(
+ Subject::from(subject).unwrap(),
+ Subject {
+ message: "docs: fix spelling of \"contributors\"".into(),
+ prefix: "PATCH".into(),
+ version: 2,
+ segment: Some(Segment { max: 3, this: 2 })
+ }
+ );
+}
+
+#[test]
+fn minimal_test() {
+ let subject = "Re: [PATCH] docs: fix spelling of \"contributors\"".into();
+ assert_eq!(
+ Subject::from(subject).unwrap(),
+ Subject {
+ message: "docs: fix spelling of \"contributors\"".into(),
+ prefix: "PATCH".into(),
+ version: 1,
+ segment: None
+ }
+ );
+}
+
+#[test]
+fn version_test() {
+ let subject = "Re: [PATCH v4] docs: fix spelling of \"contributors\"".into();
+ assert_eq!(
+ Subject::from(subject).unwrap(),
+ Subject {
+ message: "docs: fix spelling of \"contributors\"".into(),
+ prefix: "PATCH".into(),
+ version: 4,
+ segment: None
+ }
+ );
+}
+
+#[test]
+fn double_digit_version_test() {
+ let subject = "Re: [PATCH v11] docs: fix spelling of \"contributors\"".into();
+ assert_eq!(
+ Subject::from(subject).unwrap(),
+ Subject {
+ message: "docs: fix spelling of \"contributors\"".into(),
+ prefix: "PATCH".into(),
+ version: 11,
+ segment: None
+ }
+ );
+}