diff --git a/src/ast.rs b/src/ast.rs index ae036c2..e782df2 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -2,8 +2,11 @@ use std::borrow::Cow; use std::fmt; use chrono::{DateTime, FixedOffset}; +use nom_locate::LocatedSpan; -use crate::parser::{parse_multiple_patches, parse_single_patch, ParseError}; +use crate::parser::{ + parse_multiple_patches, parse_single_patch, parse_single_patch_with_remaining, ParseError, +}; /// A complete patch summarizing the differences between two files #[derive(Debug, Clone, Eq, PartialEq)] @@ -82,6 +85,12 @@ impl<'a> Patch<'a> { parse_single_patch(s) } + pub fn from_single_with_remaining( + s: &'a str, + ) -> Result<(Self, Option>), ParseError<'a>> { + parse_single_patch_with_remaining(s) + } + /// Attempt to parse as many patches as possible from the given string. This is useful for when /// you have a complete diff of many files. /// diff --git a/src/parser.rs b/src/parser.rs index 2ca42c7..a16e38f 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -12,6 +12,7 @@ use nom::{ multi::{many0, many1}, sequence::{delimited, preceded, terminated, tuple}, }; +use nom_locate::LocatedSpan; use crate::ast::*; @@ -75,16 +76,27 @@ fn consume_content_line(input: Input<'_>) -> IResult, (&str, bool)> { pub(crate) fn parse_single_patch(s: &str) -> Result, ParseError<'_>> { let (remaining_input, patch) = patch(Input::new(s))?; - // Parser should return an error instead of producing remaining input - assert!( - remaining_input.fragment().is_empty(), - "bug: failed to parse entire input. \ - Remaining: '{}'", - remaining_input.fragment() - ); + if !remaining_input.fragment().is_empty() { + return Err(ParseError { + line: remaining_input.location_line(), + offset: 0, + fragment: remaining_input.fragment(), + kind: nom::error::ErrorKind::Fail, + }); + } Ok(patch) } +pub(crate) fn parse_single_patch_with_remaining( + s: &str, +) -> Result<(Patch<'_>, Option>), ParseError<'_>> { + let (remaining_input, patch) = patch(Input::new(s))?; + if remaining_input.fragment().is_empty() { + return Ok((patch, None)); + } + Ok((patch, Some(remaining_input))) +} + pub(crate) fn parse_multiple_patches(s: &str) -> Result>, ParseError<'_>> { let (remaining_input, patches) = multiple_patches(Input::new(s))?; debug_assert!(