From 5c4b3dfa8e6e1cc6004ec6753da61aa85ddf5ab9 Mon Sep 17 00:00:00 2001 From: Eric Scouten Date: Fri, 26 Jul 2024 23:20:08 -0700 Subject: [PATCH] Retrofit `Header::parse` to use new API style (#102) --- src/document/document.rs | 4 +- src/document/header.rs | 57 ++++++++++++------------- src/tests/document/header.rs | 82 ++++++++++++++++++------------------ 3 files changed, 70 insertions(+), 73 deletions(-) diff --git a/src/document/document.rs b/src/document/document.rs index 1df943a3..c709ebc5 100644 --- a/src/document/document.rs +++ b/src/document/document.rs @@ -44,8 +44,8 @@ impl<'a> Document<'a> { let i = source.discard_empty_lines(); let (i, header) = if i.starts_with("= ") { - let (i, header) = Header::parse(i).ok()?; - (i, Some(header)) + let pr = Header::parse(i)?; + (pr.rem, Some(pr.t)) } else { (i, None) }; diff --git a/src/document/header.rs b/src/document/header.rs index 3ea9fc08..6273e635 100644 --- a/src/document/header.rs +++ b/src/document/header.rs @@ -1,15 +1,9 @@ use std::slice::Iter; -use nom::{ - bytes::complete::tag, - character::complete::space1, - error::{Error, ErrorKind}, - multi::many0, - Err, IResult, +use crate::{ + document::Attribute, primitives::trim_input_for_rem, span::ParseResult, HasSpan, Span, }; -use crate::{document::Attribute, primitives::trim_input_for_rem, HasSpan, Span}; - /// An AsciiDoc document may begin with a document header. The document header /// encapsulates the document title, author and revision information, /// document-wide attributes, and other document metadata. @@ -21,27 +15,33 @@ pub struct Header<'a> { } impl<'a> Header<'a> { - pub(crate) fn parse(i: Span<'a>) -> IResult { + pub(crate) fn parse(i: Span<'a>) -> Option> { let source = i.discard_empty_lines(); // TEMPORARY: Titles are optional, but we're not prepared for that yet. - let (rem, title) = parse_title(source)?; - let (rem, attributes) = many0(Attribute::parse)(rem)?; + let title = parse_title(source)?; + + let mut attributes: Vec = vec![]; + let mut rem = title.rem; - // Header must be followed by an empty line. - if rem.take_empty_line().is_none() { - return Err(Err::Error(Error::new(rem, ErrorKind::NonEmpty))); + while let Ok(attr) = Attribute::parse(rem) { + attributes.push(attr.1); + rem = attr.0; } let source = trim_input_for_rem(source, rem); - Ok(( - rem.discard_empty_lines(), - Self { - title: Some(title), + + // Header must be followed by an empty line or EOF. + let pr = rem.take_empty_line()?; + + Some(ParseResult { + t: Self { + title: Some(title.t), attributes, source, }, - )) + rem: pr.rem.discard_empty_lines(), + }) } /// Return a [`Span`] describing the document title, if there was one. @@ -61,16 +61,13 @@ impl<'a> HasSpan<'a> for Header<'a> { } } -fn parse_title(i: Span<'_>) -> IResult> { - let line = i - .take_non_empty_line() - .ok_or(nom::Err::Error(nom::error::Error::new( - i, - nom::error::ErrorKind::TakeTill1, - )))?; - - let (title, _) = tag("=")(line.t)?; - let (title, _) = space1(title)?; +fn parse_title(i: Span<'_>) -> Option> { + let line = i.take_non_empty_line()?; + let equal = line.t.take_prefix("=")?; + let ws = equal.rem.take_required_whitespace()?; - Ok((line.rem, title)) + Some(ParseResult { + t: ws.rem, + rem: line.rem, + }) } diff --git a/src/tests/document/header.rs b/src/tests/document/header.rs index ca6a50c8..c62af4b4 100644 --- a/src/tests/document/header.rs +++ b/src/tests/document/header.rs @@ -19,20 +19,10 @@ fn impl_clone() { #[test] fn only_title() { - let (rem, block) = Header::parse(Span::new("= Just the Title")).unwrap(); + let pr = Header::parse(Span::new("= Just the Title")).unwrap(); assert_eq!( - rem, - TSpan { - data: "", - line: 1, - col: 17, - offset: 16 - } - ); - - assert_eq!( - block, + pr.t, THeader { title: Some(TSpan { data: "Just the Title", @@ -49,26 +39,26 @@ fn only_title() { } } ); -} - -#[test] -fn trims_leading_spaces_in_title() { - // This is totally a judgement call on my part. As far as I can tell, - // the language doesn't describe behavior here. - let (rem, block) = Header::parse(Span::new("= Just the Title")).unwrap(); assert_eq!( - rem, + pr.rem, TSpan { data: "", line: 1, - col: 20, - offset: 19 + col: 17, + offset: 16 } ); +} + +#[test] +fn trims_leading_spaces_in_title() { + // This is totally a judgement call on my part. As far as I can tell, + // the language doesn't describe behavior here. + let pr = Header::parse(Span::new("= Just the Title")).unwrap(); assert_eq!( - block, + pr.t, THeader { title: Some(TSpan { data: "Just the Title", @@ -85,14 +75,9 @@ fn trims_leading_spaces_in_title() { } } ); -} - -#[test] -fn trims_trailing_spaces_in_title() { - let (rem, block) = Header::parse(Span::new("= Just the Title ")).unwrap(); assert_eq!( - rem, + pr.rem, TSpan { data: "", line: 1, @@ -100,9 +85,14 @@ fn trims_trailing_spaces_in_title() { offset: 19 } ); +} + +#[test] +fn trims_trailing_spaces_in_title() { + let pr = Header::parse(Span::new("= Just the Title ")).unwrap(); assert_eq!( - block, + pr.t, THeader { title: Some(TSpan { data: "Just the Title", @@ -119,24 +109,24 @@ fn trims_trailing_spaces_in_title() { } } ); -} - -#[test] -fn title_and_attribute() { - let (rem, block) = Header::parse(Span::new("= Just the Title\n:foo: bar\n\nblah")).unwrap(); assert_eq!( - rem, + pr.rem, TSpan { - data: "blah", - line: 4, - col: 1, - offset: 28 + data: "", + line: 1, + col: 20, + offset: 19 } ); +} + +#[test] +fn title_and_attribute() { + let pr = Header::parse(Span::new("= Just the Title\n:foo: bar\n\nblah")).unwrap(); assert_eq!( - block, + pr.t, THeader { title: Some(TSpan { data: "Just the Title", @@ -172,4 +162,14 @@ fn title_and_attribute() { } } ); + + assert_eq!( + pr.rem, + TSpan { + data: "blah", + line: 4, + col: 1, + offset: 28 + } + ); }