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 | use bstr::{BStr, BString, ByteSlice}; | 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 | /// The `input` is returned unaltered if it doesn't start with a `"` character to indicate | 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 | /// See [the tests][tests] for quotation examples. | 37 | /// See [the tests][tests] for quotation examples. |
| 37 | /// | 38 | /// |
| 38 | /// [tests]: https://github.com/Byron/gitoxide/blob/e355b4ad133075152312816816af5ce72cf79cff/git-odb/src/alternate/unquote.rs#L110-L118 | 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 | if !input.starts_with(b"\"") { | 41 | if !input.starts_with(b"\"") { |
| 41 | return Ok(input.into()); | 42 | return Ok((input.into(), input.len())); |
| 42 | } | 43 | } |
| 43 | if input.len() < 2 { | 44 | if input.len() < 2 { |
| 44 | return Err(undo::Error::new("Input must be surrounded by double quotes", input)); | 45 | return Err(undo::Error::new("Input must be surrounded by double quotes", input)); |
| 45 | } | 46 | } |
| 46 | let original = input.as_bstr(); | 47 | let original = input.as_bstr(); |
| 47 | let mut input = &input[1..]; | 48 | let mut input = &input[1..]; |
| 49 | let mut consumed = 1; | ||
| 48 | let mut out = BString::default(); | 50 | let mut out = BString::default(); |
| 49 | fn consume_one_past(input: &mut &BStr, position: usize) -> Result<u8, undo::Error> { | 51 | fn consume_one_past(input: &mut &BStr, position: usize) -> Result<u8, undo::Error> { |
| 50 | *input = input | 52 | *input = input |
| @@ -59,10 +61,12 @@ pub fn undo(input: &BStr) -> Result<Cow<'_, BStr>, undo::Error> { · 8 unmodified lines | |||
| 59 | match input.find_byteset(b"\"\\") { | 61 | match input.find_byteset(b"\"\\") { |
| 60 | Some(position) => { | 62 | Some(position) => { |
| 61 | out.extend_from_slice(&input[..position]); | 63 | out.extend_from_slice(&input[..position]); |
| 64 | consumed += position + 1; | ||
| 62 | match input[position] { | 65 | match input[position] { |
| 63 | b'"' => break, | 66 | b'"' => break, |
| 64 | b'\\' => { | 67 | b'\\' => { |
| 65 | let next = consume_one_past(&mut input, position)?; | 68 | let next = consume_one_past(&mut input, position)?; |
| 69 | consumed += 1; | ||
| 66 | match next { | 70 | match next { |
| 67 | b'n' => out.push(b'\n'), | 71 | b'n' => out.push(b'\n'), |
| 68 | b'r' => out.push(b'\r'), | 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 | let byte = btoi::btou_radix(&buf, 8).map_err(|e| undo::Error::new(e, original))?; | 92 | let byte = btoi::btou_radix(&buf, 8).map_err(|e| undo::Error::new(e, original))?; |
| 89 | out.push(byte); | 93 | out.push(byte); |
| 90 | input = &input[2..]; | 94 | input = &input[2..]; |
| 95 | consumed += 2; | ||
| 91 | } | 96 | } |
| 92 | _ => { | 97 | _ => { |
| 93 | return Err(undo::Error::UnsupportedEscapeByte { | 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 | None => { | 108 | None => { |
| 104 | out.extend_from_slice(input); | 109 | out.extend_from_slice(input); |
| 110 | consumed += input.len(); | ||
| 105 | break; | 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 | use git_quote::ansi_c; | 4 | use git_quote::ansi_c; |
| 5 | 5 | ||
| 6 | macro_rules! test { | 6 | macro_rules! test { |
| 7 | ($name:ident, $input:literal, $expected:literal) => { | 7 | ($name:ident, $input:literal, $expected:literal, $consumed:literal) => { |
| 8 | #[test] | 8 | #[test] |
| 9 | fn $name() { | 9 | fn $name() { |
| 10 | assert_eq!( | 10 | assert_eq!( |
| 11 | ansi_c::undo($input.as_bytes().as_bstr()).expect("valid input"), | 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"); | 21 | test!(unquoted_remains_unchanged, "hello", "hello", 5); |
| 19 | test!(empty_surrounded_by_quotes, "\"\"", ""); | 22 | test!(empty_surrounded_by_quotes, "\"\"", "", 2); |
| 20 | test!(surrounded_only_by_quotes, "\"hello\"", "hello"); | 23 | test!(surrounded_only_by_quotes, "\"hello\"", "hello", 7); |
| 21 | test!(typical_escapes, r#""\n\r\t""#, b"\n\r\t"); | 24 | test!(typical_escapes, r#""\n\r\t""#, b"\n\r\t", 8); |
| 22 | test!(untypical_escapes, r#""\a\b\f\v""#, b"\x07\x08\x0c\x0b"); | 25 | test!(untypical_escapes, r#""\a\b\f\v""#, b"\x07\x08\x0c\x0b", 10); |
| 23 | test!(literal_escape_and_double_quote, r#""\"\\""#, br#""\"#); | 26 | test!(literal_escape_and_double_quote, r#""\"\\""#, br#""\"#, 6); |
| 24 | test!( | 27 | test!( |
| 25 | unicode_byte_escapes_by_number, | 28 | unicode_byte_escapes_by_number, |
| 26 | r#""\346\277\261\351\207\216\t\347\264\224""#, | 29 | r#""\346\277\261\351\207\216\t\347\264\224""#, |
| 27 | "濱野\t純" | 30 | "濱野\t純", |
| 31 | 40 | ||
| 28 | ); | 32 | ); |
| 29 | test!( | 33 | test!( |
| 30 | exclamation_and_tilde_survive_an_escape_with_double_escaping, | 34 | exclamation_and_tilde_survive_an_escape_with_double_escaping, |
| 31 | r#""\\!\\#hello there/file.ext""#, | 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 | #[test] | 40 | #[test] |
| 36 | fn out_of_quote_characters_can_be_passed_and_will_not_be_consumed() { | 41 | fn out_of_quote_characters_can_be_passed_and_will_not_be_consumed() { |
| 37 | assert_eq!( | 42 | let input = br#""hello there" out of quote"#.as_bstr(); |
| 38 | ansi_c::undo(br#""hello there" out of quote"#.as_bstr()).expect("valid input"), | 43 | let (unquoted, consumed) = ansi_c::undo(input).expect("valid input"); |
| 39 | std::borrow::Cow::Borrowed(b"hello there".as_bstr()) | 44 | assert_eq!(unquoted, std::borrow::Cow::Borrowed(b"hello there".as_bstr())); |
| 40 | ); | 45 | assert_eq!(&input[consumed..], " out of quote"); |
| 41 | } | 46 | } |
| 42 | } | 47 | } |
| 43 | } | 48 | } |