use std::{borrow::Cow, convert::TryFrom, fmt, ops::Deref};
use thiserror::Error;
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum Error {
#[error("the trailers paragraph is missing in the given message")]
MissingParagraph,
#[error("trailing data after trailers section: '{0}")]
Trailing(String),
#[error(transparent)]
Parse(#[from] nom::Err<nom::error::Error<String>>),
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Trailer<'a> {
pub token: Token<'a>,
pub values: Vec<Cow<'a, str>>,
}
impl<'a> Trailer<'a> {
pub fn display(&'a self, separator: &'a str) -> Display<'a> {
Display {
trailer: self,
separator,
}
}
pub fn to_owned(&self) -> OwnedTrailer {
OwnedTrailer::from(self)
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Token<'a>(&'a str);
#[derive(Debug)]
pub struct OwnedTrailer {
pub token: OwnedToken,
pub values: Vec<String>,
}
#[derive(Debug)]
pub struct OwnedToken(String);
impl<'a> From<&Trailer<'a>> for OwnedTrailer {
fn from(t: &Trailer<'a>) -> Self {
OwnedTrailer {
token: OwnedToken(t.token.0.to_string()),
values: t.values.iter().map(|v| v.to_string()).collect(),
}
}
}
impl<'a> From<Trailer<'a>> for OwnedTrailer {
fn from(t: Trailer<'a>) -> Self {
(&t).into()
}
}
impl<'a> From<&'a OwnedTrailer> for Trailer<'a> {
fn from(t: &'a OwnedTrailer) -> Self {
Trailer {
token: Token(t.token.0.as_str()),
values: t.values.iter().map(Cow::from).collect(),
}
}
}
impl Deref for OwnedToken {
type Target = str;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum InvalidToken {
#[error("trailing characters: '{0}'")]
Trailing(String),
#[error(transparent)]
Parse(#[from] nom::Err<nom::error::Error<String>>),
}
impl<'a> TryFrom<&'a str> for Token<'a> {
type Error = InvalidToken;
fn try_from(s: &'a str) -> Result<Self, Self::Error> {
match parser::token(s) {
Ok((rest, token)) if rest.is_empty() => Ok(token),
Ok((trailing, _)) => Err(InvalidToken::Trailing(trailing.to_owned())),
Err(e) => Err(e.to_owned().into()),
}
}
}
impl Deref for Token<'_> {
type Target = str;
fn deref(&self) -> &Self::Target {
self.0
}
}
pub struct Display<'a> {
trailer: &'a Trailer<'a>,
separator: &'a str,
}
impl<'a> fmt::Display for Display<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{}{}{}",
self.trailer.token.deref(),
self.separator,
self.trailer.values.join("\n ")
)
}
}
pub trait Separator<'a> {
fn sep_for(&self, token: &Token) -> &'a str;
}
impl<'a> Separator<'a> for &'a str {
fn sep_for(&self, _: &Token) -> &'a str {
self
}
}
impl<'a, F> Separator<'a> for F
where
F: Fn(&Token) -> &'a str,
{
fn sep_for(&self, token: &Token) -> &'a str {
self(token)
}
}
pub struct DisplayMany<'a, S> {
separator: S,
trailers: &'a [Trailer<'a>],
}
impl<'a, S> fmt::Display for DisplayMany<'a, S>
where
S: Separator<'a>,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for (i, trailer) in self.trailers.iter().enumerate() {
if i > 0 {
writeln!(f)?
}
write!(
f,
"{}",
trailer.display(self.separator.sep_for(&trailer.token))
)?
}
Ok(())
}
}
pub fn parse<'a>(message: &'a str, separators: &'a str) -> Result<Vec<Trailer<'a>>, Error> {
let trailers_paragraph =
match parser::paragraphs(message.trim_end()).map(|(_, ps)| ps.last().cloned()) {
Ok(None) | Err(_) => return Err(Error::MissingParagraph),
Ok(Some(p)) => {
if p.is_empty() {
return Err(Error::MissingParagraph);
}
p
},
};
match parser::trailers(trailers_paragraph, separators) {
Ok((rest, trailers)) if rest.is_empty() => Ok(trailers),
Ok((unparseable, _)) => Err(Error::Trailing(unparseable.to_owned())),
Err(e) => Err(e.to_owned().into()),
}
}
pub fn display<'a, S>(separator: S, trailers: &'a [Trailer<'a>]) -> DisplayMany<'a, S>
where
S: Separator<'a>,
{
DisplayMany {
separator,
trailers,
}
}
pub mod parser {
use std::borrow::Cow;
use super::{Token, Trailer};
use nom::{
branch::alt,
bytes::complete::{tag, take_until, take_while1},
character::complete::{line_ending, not_line_ending, one_of, space0, space1},
combinator::{map, rest},
multi::{many0, separated_list1},
sequence::{delimited, preceded, separated_pair, terminated},
IResult,
};
const EMPTY_LINE: &str = "\n\n";
pub fn paragraphs(s: &str) -> IResult<&str, Vec<&str>> {
separated_list1(tag(EMPTY_LINE), paragraph)(s)
}
pub fn paragraph(s: &str) -> IResult<&str, &str> {
alt((take_until(EMPTY_LINE), rest))(s)
}
pub fn trailers<'a>(s: &'a str, separators: &'a str) -> IResult<&'a str, Vec<Trailer<'a>>> {
many0(|s| trailer(s, separators))(s)
}
pub fn trailer<'a>(s: &'a str, separators: &'a str) -> IResult<&'a str, Trailer<'a>> {
let mut parser = separated_pair(token, |s| separator(separators, s), values);
let (rest, (token, values)) = parser(s)?;
Ok((rest, Trailer { token, values }))
}
pub(super) fn token(s: &str) -> IResult<&str, Token> {
take_while1(|c: char| c.is_alphanumeric() || c == '-')(s)
.map(|(i, token_str)| (i, Token(token_str)))
}
fn separator<'a>(separators: &'a str, s: &'a str) -> IResult<&'a str, char> {
delimited(space0, one_of(separators), space0)(s)
}
fn values(s: &str) -> IResult<&str, Vec<Cow<'_, str>>> {
let (r, opt_inline_value) = until_eol_or_eof(s)?;
let (r, mut values) = multiline_values(r)?;
if !opt_inline_value.is_empty() {
values.insert(0, opt_inline_value.into())
}
Ok((r, values))
}
fn multiline_values(s: &str) -> IResult<&str, Vec<Cow<'_, str>>> {
many0(map(indented_line_contents, Cow::from))(s)
}
fn until_eol_or_eof(s: &str) -> IResult<&str, &str> {
alt((until_eol, rest))(s)
}
fn indented_line_contents(s: &str) -> IResult<&str, &str> {
preceded(space1, until_eol_or_eof)(s)
}
fn until_eol(s: &str) -> IResult<&str, &str> {
terminated(not_line_ending, line_ending)(s)
}
}