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,
};
#[derive(Clone, Default, Debug, PartialEq, Eq)]
pub struct Segment {
pub this: u8,
pub max: u8,
}
#[derive(Clone, Default, Debug, PartialEq, Eq)]
pub struct Subject {
pub version: u8,
pub segment: Option<Segment>,
pub prefix: String,
pub message: String,
}
impl Subject {
pub fn from(s: String) -> Result<Self> {
Ok(Self::parse(s.as_str()).unwrap().1)
}
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,
},
))
}
fn parens(input: &str) -> IResult<&str, &str> {
delimited(char('['), is_not("]"), char(']'))(input)
}
}
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
}
);
}