1pub mod undo {
3 use bstr::{BStr, BString};
4 use quick_error::quick_error;
5
6 quick_error! {
7 #[derive(Debug)]
9 #[allow(missing_docs)]
10 pub enum Error {
11 InvalidInput { message: String, input: BString } {
12 display("{}: {:?}", message, input)
13 }
14 UnsupportedEscapeByte { byte: u8, input: BString } {
15 display("Invalid escaped value {} in input {:?}", byte, input)
16 }
17 }
18 }
19
20 impl Error {
21 pub(crate) fn new(message: impl ToString, input: &BStr) -> Error {
22 Error::InvalidInput {
23 message: message.to_string(),
24 input: input.into(),
25 }
26 }
27 }
28}
29
30use std::{borrow::Cow, io::Read};
31
32use bstr::{BStr, BString, ByteSlice};
33
34pub fn undo(input: &BStr) -> Result<(Cow<'_, BStr>, usize), undo::Error> {
44 if !input.starts_with(b"\"") {
45 return Ok((input.into(), input.len()));
46 }
47 if input.len() < 2 {
48 return Err(undo::Error::new("Input must be surrounded by double quotes", input));
49 }
50 let original = input.as_bstr();
51 let mut input = &input[1..];
52 let mut consumed = 1;
53 let mut out = BString::default();
54 fn consume_one_past(input: &mut &BStr, position: usize) -> Result<u8, undo::Error> {
55 *input = input
56 .get(position + 1..)
57 .ok_or_else(|| undo::Error::new("Unexpected end of input", input))?
58 .as_bstr();
59 let next = input[0];
60 *input = input.get(1..).unwrap_or_default().as_bstr();
61 Ok(next)
62 }
63 loop {
64 match input.find_byteset(b"\"\\") {
65 Some(position) => {
66 out.extend_from_slice(&input[..position]);
67 consumed += position + 1;
68 match input[position] {
69 b'"' => break,
70 b'\\' => {
71 let next = consume_one_past(&mut input, position)?;
72 consumed += 1;
73 match next {
74 b'n' => out.push(b'\n'),
75 b'r' => out.push(b'\r'),
76 b't' => out.push(b'\t'),
77 b'a' => out.push(7),
78 b'b' => out.push(8),
79 b'v' => out.push(0xb),
80 b'f' => out.push(0xc),
81 b'"' => out.push(b'"'),
82 b'\\' => out.push(b'\\'),
83 b'0' | b'1' | b'2' | b'3' => {
84 let mut buf = [next; 3];
85 input
86 .get(..2)
87 .ok_or_else(|| {
88 undo::Error::new(
89 "Unexpected end of input when fetching two more octal bytes",
90 input,
91 )
92 })?
93 .read_exact(&mut buf[1..])
94 .expect("impossible to fail as numbers match");
95 let byte = btoi::btou_radix(&buf, 8).map_err(|e| undo::Error::new(e, original))?;
96 out.push(byte);
97 input = &input[2..];
98 consumed += 2;
99 }
100 _ => {
101 return Err(undo::Error::UnsupportedEscapeByte {
102 byte: next,
103 input: original.into(),
104 })
105 }
106 }
107 }
108 _ => unreachable!("cannot find character that we didn't search for"),
109 }
110 }
111 None => {
112 out.extend_from_slice(input);
113 consumed += input.len();
114 break;
115 }
116 }
117 }
118 Ok((out.into(), consumed))
119}