History

Add ansic::undo (#301)

Sebastian Thiel committed 4 years ago

Commit 1be8f141 parent 0d1aaf06 files · +151 −1Browse files

Unified · Split

6 files
  1. Cargo.lock +5 −0
  2. git-quote/CHANGELOG.md +1 −1
  3. git-quote/Cargo.toml +3 −0
  4. git-quote/src/ansi_c.rs +110 −0
  5. git-quote/src/lib.rs +3 −0
  6. git-quote/tests/quote.rs +29 −0
Cargo.lock+5−0
@@ -1332,6 +1332,11 @@ dependencies = [ · 1331 unmodified lines
1332[[package]]1332[[package]]
1333name = "git-quote"1333name = "git-quote"
1334version = "0.0.0"1334version = "0.0.0"
1335dependencies = [
1336 "bstr",
1337 "btoi",
1338 "quick-error",
1339]
1335 1340
1336[[package]]1341[[package]]
1337name = "git-ref"1342name = "git-ref"
git-quote/CHANGELOG.md+1−1
@@ -7,4 +7,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 · 6 unmodified lines
7 7
8## Unreleased8## Unreleased
9 9
10An empty crate without any content to reserve the name for the gitoxide project.10Initial release with ansi_c unquoting capability.
git-quote/Cargo.toml+3−0
@@ -13,3 +13,6 @@ doctest = false · 12 unmodified lines
13# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html13# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
14 14
15[dependencies]15[dependencies]
16bstr = { version = "0.2.13", default-features = false, features = ["std"]}
17quick-error = "2.0.0"
18btoi = "0.4.2"
git-quote/src/ansi_c.rs+110−0
@@ -0,0 +1,110 @@
1pub mod undo {
2 use bstr::{BStr, BString};
3 use quick_error::quick_error;
4
5 quick_error! {
6 #[derive(Debug)]
7 pub enum Error {
8 InvalidInput { message: String, input: BString } {
9 display("{}: {:?}", message, input)
10 }
11 UnsupportedEscapeByte { byte: u8, input: BString } {
12 display("Invalid escaped value {} in input {:?}", byte, input)
13 }
14 }
15 }
16
17 impl Error {
18 pub(crate) fn new(message: impl ToString, input: &BStr) -> Error {
19 Error::InvalidInput {
20 message: message.to_string(),
21 input: input.into(),
22 }
23 }
24 }
25}
26
27use std::{borrow::Cow, io::Read};
28
29use bstr::{BStr, BString, ByteSlice};
30
31/// Unquote the given ansi-c quoted `input` string.
32///
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.
35///
36/// See [the tests][tests] for quotation examples.
37///
38/// [tests]: https://github.com/Byron/gitoxide/blob/e355b4ad133075152312816816af5ce72cf79cff/git-odb/src/alternate/unquote.rs#L110-L118
39pub fn undo(input: &BStr) -> Result<Cow<'_, BStr>, undo::Error> {
40 if !input.starts_with(b"\"") {
41 return Ok(input.into());
42 }
43 if input.len() < 2 {
44 return Err(undo::Error::new("Input must be surrounded by double quotes", input));
45 }
46 let original = input.as_bstr();
47 let mut input = &input[1..];
48 let mut out = BString::default();
49 fn consume_one_past(input: &mut &BStr, position: usize) -> Result<u8, undo::Error> {
50 *input = input
51 .get(position + 1..)
52 .ok_or_else(|| undo::Error::new("Unexpected end of input", input))?
53 .as_bstr();
54 let next = input[0];
55 *input = input.get(1..).unwrap_or_default().as_bstr();
56 Ok(next)
57 }
58 loop {
59 match input.find_byteset(b"\"\\") {
60 Some(position) => {
61 out.extend_from_slice(&input[..position]);
62 match input[position] {
63 b'"' => break,
64 b'\\' => {
65 let next = consume_one_past(&mut input, position)?;
66 match next {
67 b'n' => out.push(b'\n'),
68 b'r' => out.push(b'\r'),
69 b't' => out.push(b'\t'),
70 b'a' => out.push(7),
71 b'b' => out.push(8),
72 b'v' => out.push(0xb),
73 b'f' => out.push(0xc),
74 b'"' => out.push(b'"'),
75 b'\\' => out.push(b'\\'),
76 b'0' | b'1' | b'2' | b'3' => {
77 let mut buf = [next; 3];
78 input
79 .get(..2)
80 .ok_or_else(|| {
81 undo::Error::new(
82 "Unexpected end of input when fetching two more octal bytes",
83 input,
84 )
85 })?
86 .read_exact(&mut buf[1..])
87 .expect("impossible to fail as numbers match");
88 let byte = btoi::btou_radix(&buf, 8).map_err(|e| undo::Error::new(e, original))?;
89 out.push(byte);
90 input = &input[2..];
91 }
92 _ => {
93 return Err(undo::Error::UnsupportedEscapeByte {
94 byte: next,
95 input: original.into(),
96 })
97 }
98 }
99 }
100 _ => unreachable!("cannot find character that we didn't search for"),
101 }
102 }
103 None => {
104 out.extend_from_slice(input);
105 break;
106 }
107 }
108 }
109 Ok(out.into())
110}
git-quote/src/lib.rs+3−0
@@ -1 +1,4 @@
1#![forbid(unsafe_code, rust_2018_idioms)]1#![forbid(unsafe_code, rust_2018_idioms)]
2
3///
4pub mod ansi_c;
git-quote/tests/quote.rs+29−0
@@ -0,0 +1,29 @@
1mod ansi_c {
2 mod undo {
3 use bstr::ByteSlice;
4
5 macro_rules! test {
6 ($name:ident, $input:literal, $expected:literal) => {
7 #[test]
8 fn $name() {
9 assert_eq!(
10 git_quote::ansi_c::undo($input.as_bytes().as_bstr()).expect("valid input"),
11 std::borrow::Cow::Borrowed($expected.as_bytes().as_bstr())
12 );
13 }
14 };
15 }
16
17 test!(unquoted_remains_unchanged, "hello", "hello");
18 test!(empty_surrounded_by_quotes, "\"\"", "");
19 test!(surrounded_only_by_quotes, "\"hello\"", "hello");
20 test!(typical_escapes, r#""\n\r\t""#, b"\n\r\t");
21 test!(untypical_escapes, r#""\a\b\f\v""#, b"\x07\x08\x0c\x0b");
22 test!(literal_escape_and_double_quote, r#""\"\\""#, br#""\"#);
23 test!(
24 unicode_byte_escapes_by_number,
25 r#""\346\277\261\351\207\216\t\347\264\224""#,
26 "濱野\t純"
27 );
28 }
29}