That way it's possible to continue parsing past the quoted string.
Unified · Split
| @@ -28,23 +28,25 @@ use std::{borrow::Cow, io::Read}; · 27 unmodified lines | ||
| 28 | 28 | |
| 29 | 29 | use bstr::{BStr, BString, ByteSlice}; |
| 30 | 30 | |
| 31 | −/// Unquote the given ansi-c quoted `input` string. | |
| 31 | +/// Unquote the given ansi-c quoted `input` string, returning it and all of the consumed bytes. | |
| 32 | 32 | /// |
| 33 | 33 | /// The `input` is returned unaltered if it doesn't start with a `"` character to indicate |
| 34 | −/// quotation, otherwise a new unqoted string will always be allocated. | |
| 34 | +/// quotation, otherwise a new unquoted string will always be allocated. | |
| 35 | +/// The amount of consumed bytes allow to pass strings that start with a quote, and skip all quoted text for additional processing | |
| 35 | 36 | /// |
| 36 | 37 | /// See [the tests][tests] for quotation examples. |
| 37 | 38 | /// |
| 38 | 39 | /// [tests]: https://github.com/Byron/gitoxide/blob/e355b4ad133075152312816816af5ce72cf79cff/git-odb/src/alternate/unquote.rs#L110-L118 |
| 39 | −pub fn undo(input: &BStr) -> Result<Cow<'_, BStr>, undo::Error> { | |
| 40 | +pub fn undo(input: &BStr) -> Result<(Cow<'_, BStr>, usize), undo::Error> { | |
| 40 | 41 | if !input.starts_with(b"\"") { |
| 41 | − return Ok(input.into()); | |
| 42 | + return Ok((input.into(), input.len())); | |
| 42 | 43 | } |
| 43 | 44 | if input.len() < 2 { |
| 44 | 45 | return Err(undo::Error::new("Input must be surrounded by double quotes", input)); |
| 45 | 46 | } |
| 46 | 47 | let original = input.as_bstr(); |
| 47 | 48 | let mut input = &input[1..]; |
| 49 | + let mut consumed = 1; | |
| 48 | 50 | let mut out = BString::default(); |
| 49 | 51 | fn consume_one_past(input: &mut &BStr, position: usize) -> Result<u8, undo::Error> { |
| 50 | 52 | *input = input |
| @@ -59,10 +61,12 @@ pub fn undo(input: &BStr) -> Result<Cow<'_, BStr>, undo::Error> { · 8 unmodified lines | ||
| 59 | 61 | match input.find_byteset(b"\"\\") { |
| 60 | 62 | Some(position) => { |
| 61 | 63 | out.extend_from_slice(&input[..position]); |
| 64 | + consumed += position + 1; | |
| 62 | 65 | match input[position] { |
| 63 | 66 | b'"' => break, |
| 64 | 67 | b'\\' => { |
| 65 | 68 | let next = consume_one_past(&mut input, position)?; |
| 69 | + consumed += 1; | |
| 66 | 70 | match next { |
| 67 | 71 | b'n' => out.push(b'\n'), |
| 68 | 72 | b'r' => out.push(b'\r'), |
| @@ -88,6 +92,7 @@ pub fn undo(input: &BStr) -> Result<Cow<'_, BStr>, undo::Error> { · 19 unmodified lines | ||
| 88 | 92 | let byte = btoi::btou_radix(&buf, 8).map_err(|e| undo::Error::new(e, original))?; |
| 89 | 93 | out.push(byte); |
| 90 | 94 | input = &input[2..]; |
| 95 | + consumed += 2; | |
| 91 | 96 | } |
| 92 | 97 | _ => { |
| 93 | 98 | return Err(undo::Error::UnsupportedEscapeByte { |
| @@ -102,9 +107,10 @@ pub fn undo(input: &BStr) -> Result<Cow<'_, BStr>, undo::Error> { · 8 unmodified lines | ||
| 102 | 107 | } |
| 103 | 108 | None => { |
| 104 | 109 | out.extend_from_slice(input); |
| 110 | + consumed += input.len(); | |
| 105 | 111 | break; |
| 106 | 112 | } |
| 107 | 113 | } |
| 108 | 114 | } |
| 109 | − Ok(out.into()) | |
| 115 | + Ok((out.into(), consumed)) | |
| 110 | 116 | } |
| @@ -4,40 +4,45 @@ mod ansi_c { · 3 unmodified lines | ||
| 4 | 4 | use git_quote::ansi_c; |
| 5 | 5 | |
| 6 | 6 | macro_rules! test { |
| 7 | − ($name:ident, $input:literal, $expected:literal) => { | |
| 7 | + ($name:ident, $input:literal, $expected:literal, $consumed:literal) => { | |
| 8 | 8 | #[test] |
| 9 | 9 | fn $name() { |
| 10 | 10 | assert_eq!( |
| 11 | 11 | ansi_c::undo($input.as_bytes().as_bstr()).expect("valid input"), |
| 12 | − std::borrow::Cow::Borrowed($expected.as_bytes().as_bstr()) | |
| 12 | + ( | |
| 13 | + std::borrow::Cow::Borrowed($expected.as_bytes().as_bstr()), | |
| 14 | + $consumed | |
| 15 | + ) | |
| 13 | 16 | ); |
| 14 | 17 | } |
| 15 | 18 | }; |
| 16 | 19 | } |
| 17 | 20 | |
| 18 | − test!(unquoted_remains_unchanged, "hello", "hello"); | |
| 19 | − test!(empty_surrounded_by_quotes, "\"\"", ""); | |
| 20 | − test!(surrounded_only_by_quotes, "\"hello\"", "hello"); | |
| 21 | − test!(typical_escapes, r#""\n\r\t""#, b"\n\r\t"); | |
| 22 | − test!(untypical_escapes, r#""\a\b\f\v""#, b"\x07\x08\x0c\x0b"); | |
| 23 | − test!(literal_escape_and_double_quote, r#""\"\\""#, br#""\"#); | |
| 21 | + test!(unquoted_remains_unchanged, "hello", "hello", 5); | |
| 22 | + test!(empty_surrounded_by_quotes, "\"\"", "", 2); | |
| 23 | + test!(surrounded_only_by_quotes, "\"hello\"", "hello", 7); | |
| 24 | + test!(typical_escapes, r#""\n\r\t""#, b"\n\r\t", 8); | |
| 25 | + test!(untypical_escapes, r#""\a\b\f\v""#, b"\x07\x08\x0c\x0b", 10); | |
| 26 | + test!(literal_escape_and_double_quote, r#""\"\\""#, br#""\"#, 6); | |
| 24 | 27 | test!( |
| 25 | 28 | unicode_byte_escapes_by_number, |
| 26 | 29 | r#""\346\277\261\351\207\216\t\347\264\224""#, |
| 27 | − "濱野\t純" | |
| 30 | + "濱野\t純", | |
| 31 | + 40 | |
| 28 | 32 | ); |
| 29 | 33 | test!( |
| 30 | 34 | exclamation_and_tilde_survive_an_escape_with_double_escaping, |
| 31 | 35 | r#""\\!\\#hello there/file.ext""#, |
| 32 | − r"\!\#hello there/file.ext" | |
| 36 | + r"\!\#hello there/file.ext", | |
| 37 | + 28 | |
| 33 | 38 | ); |
| 34 | 39 | |
| 35 | 40 | #[test] |
| 36 | 41 | fn out_of_quote_characters_can_be_passed_and_will_not_be_consumed() { |
| 37 | − assert_eq!( | |
| 38 | − ansi_c::undo(br#""hello there" out of quote"#.as_bstr()).expect("valid input"), | |
| 39 | − std::borrow::Cow::Borrowed(b"hello there".as_bstr()) | |
| 40 | − ); | |
| 42 | + let input = br#""hello there" out of quote"#.as_bstr(); | |
| 43 | + let (unquoted, consumed) = ansi_c::undo(input).expect("valid input"); | |
| 44 | + assert_eq!(unquoted, std::borrow::Cow::Borrowed(b"hello there".as_bstr())); | |
| 45 | + assert_eq!(&input[consumed..], " out of quote"); | |
| 41 | 46 | } |
| 42 | 47 | } |
| 43 | 48 | } |