1#![cfg(feature = "fish")]
2
3use crate::{Quotable, QuoteInto};
4
5#[derive(Debug, Clone, Copy)]
42pub struct Fish;
43
44impl QuoteInto<Vec<u8>> for Fish {
45 fn quote_into<'q, S: Into<Quotable<'q>>>(s: S, out: &mut Vec<u8>) {
46 Self::quote_into_vec(s, out);
47 }
48}
49
50impl QuoteInto<String> for Fish {
51 fn quote_into<'q, S: Into<Quotable<'q>>>(s: S, out: &mut String) {
52 Self::quote_into_vec(s, unsafe { out.as_mut_vec() })
53 }
54}
55
56#[cfg(unix)]
57impl QuoteInto<std::ffi::OsString> for Fish {
58 fn quote_into<'q, S: Into<Quotable<'q>>>(s: S, out: &mut std::ffi::OsString) {
59 use std::os::unix::ffi::OsStringExt;
60 let s = Self::quote_vec(s);
61 let s = std::ffi::OsString::from_vec(s);
62 out.push(s);
63 }
64}
65
66#[cfg(feature = "bstr")]
67impl QuoteInto<bstr::BString> for Fish {
68 fn quote_into<'q, S: Into<Quotable<'q>>>(s: S, out: &mut bstr::BString) {
69 let s = Self::quote_vec(s);
70 out.extend(s);
71 }
72}
73
74impl Fish {
75 pub fn quote_vec<'a, S: Into<Quotable<'a>>>(s: S) -> Vec<u8> {
92 match s.into() {
93 Quotable::Bytes(bytes) => match bytes::escape_prepare(bytes) {
94 bytes::Prepared::Empty => vec![b'\'', b'\''],
95 bytes::Prepared::Inert => bytes.into(),
96 bytes::Prepared::Escape(esc) => {
97 let mut sout = Vec::new();
98 bytes::escape_chars(esc, &mut sout);
99 sout
100 }
101 },
102 Quotable::Text(text) => match text::escape_prepare(text) {
103 text::Prepared::Empty => vec![b'\'', b'\''],
104 text::Prepared::Inert => text.into(),
105 text::Prepared::Escape(esc) => {
106 let mut sout = Vec::new();
107 text::escape_chars(esc, &mut sout);
108 sout
109 }
110 },
111 }
112 }
113
114 pub fn quote_into_vec<'a, S: Into<Quotable<'a>>>(s: S, sout: &mut Vec<u8>) {
130 match s.into() {
131 Quotable::Bytes(bytes) => match bytes::escape_prepare(bytes) {
132 bytes::Prepared::Empty => sout.extend(b"''"),
133 bytes::Prepared::Inert => sout.extend(bytes),
134 bytes::Prepared::Escape(esc) => bytes::escape_chars(esc, sout),
135 },
136 Quotable::Text(text) => match text::escape_prepare(text) {
137 text::Prepared::Empty => sout.extend(b"''"),
138 text::Prepared::Inert => sout.extend(text.as_bytes()),
139 text::Prepared::Escape(esc) => text::escape_chars(esc, sout),
140 },
141 }
142 }
143}
144
145mod bytes {
148 use super::u8_to_hex_escape_uppercase_x;
149 use crate::ascii::Char;
150
151 pub enum Prepared {
152 Empty,
153 Inert,
154 Escape(Vec<Char>),
155 }
156
157 pub fn escape_prepare(sin: &[u8]) -> Prepared {
158 let esc: Vec<_> = sin.iter().map(Char::from).collect();
159 if esc.is_empty() {
162 Prepared::Empty
163 } else if esc.iter().all(Char::is_inert) {
164 Prepared::Inert
165 } else {
166 Prepared::Escape(esc)
167 }
168 }
169
170 pub fn escape_chars(esc: Vec<Char>, sout: &mut Vec<u8>) {
171 #[derive(PartialEq)]
172 enum QuoteStyle {
173 Inside,
174 Outside,
175 Whatever,
176 }
177 use QuoteStyle::*;
178
179 let mut inside_quotes_now = false;
180 let mut push_literal = |style: QuoteStyle, literal: &[u8]| {
181 match (inside_quotes_now, style) {
182 (true, Outside) => {
183 sout.push(b'\'');
184 inside_quotes_now = false;
185 }
186 (false, Inside) => {
187 sout.push(b'\'');
188 inside_quotes_now = true;
189 }
190 _ => (),
191 }
192 sout.extend(literal);
193 };
194 for mode in esc {
195 use Char::*;
196 match mode {
197 Bell => push_literal(Outside, b"\\a"),
198 Backspace => push_literal(Outside, b"\\b"),
199 Escape => push_literal(Outside, b"\\e"),
200 FormFeed => push_literal(Outside, b"\\f"),
201 NewLine => push_literal(Outside, b"\\n"),
202 CarriageReturn => push_literal(Outside, b"\\r"),
203 HorizontalTab => push_literal(Outside, b"\\t"),
204 VerticalTab => push_literal(Outside, b"\\v"),
205 Control(ch) => push_literal(Outside, &u8_to_hex_escape_uppercase_x(ch)),
206 Backslash => push_literal(Whatever, b"\\\\"),
207 SingleQuote => push_literal(Whatever, b"\\'"),
208 DoubleQuote => push_literal(Inside, b"\""),
209 Delete => push_literal(Outside, b"\\X7F"),
210 PrintableInert(ch) => push_literal(Whatever, &ch.to_le_bytes()),
211 Printable(ch) => push_literal(Inside, &ch.to_le_bytes()),
212 Extended(ch) => push_literal(Outside, &u8_to_hex_escape_uppercase_x(ch)),
213 }
214 }
215 if inside_quotes_now {
216 sout.push(b'\'');
217 }
218 }
219}
220
221mod text {
224 use super::u8_to_hex_escape_uppercase_x;
225 use crate::utf8::Char;
226
227 pub enum Prepared {
228 Empty,
229 Inert,
230 Escape(Vec<Char>),
231 }
232
233 pub fn escape_prepare(sin: &str) -> Prepared {
234 let esc: Vec<_> = sin.chars().map(Char::from).collect();
235 if esc.is_empty() {
238 Prepared::Empty
239 } else if esc.iter().all(Char::is_inert) {
240 Prepared::Inert
241 } else {
242 Prepared::Escape(esc)
243 }
244 }
245
246 pub fn escape_chars(esc: Vec<Char>, sout: &mut Vec<u8>) {
247 #[derive(PartialEq)]
248 enum QuoteStyle {
249 Inside,
250 Outside,
251 Whatever,
252 }
253 use QuoteStyle::*;
254
255 let mut inside_quotes_now = false;
256 let mut push_literal = |style: QuoteStyle, literal: &[u8]| {
257 match (inside_quotes_now, style) {
258 (true, Outside) => {
259 sout.push(b'\'');
260 inside_quotes_now = false;
261 }
262 (false, Inside) => {
263 sout.push(b'\'');
264 inside_quotes_now = true;
265 }
266 _ => (),
267 }
268 sout.extend(literal);
269 };
270 let buf = &mut [0u8; 4];
271 for mode in esc {
272 use Char::*;
273 match mode {
274 Bell => push_literal(Outside, b"\\a"),
275 Backspace => push_literal(Outside, b"\\b"),
276 Escape => push_literal(Outside, b"\\e"),
277 FormFeed => push_literal(Outside, b"\\f"),
278 NewLine => push_literal(Outside, b"\\n"),
279 CarriageReturn => push_literal(Outside, b"\\r"),
280 HorizontalTab => push_literal(Outside, b"\\t"),
281 VerticalTab => push_literal(Outside, b"\\v"),
282 Control(ch) => push_literal(Outside, &u8_to_hex_escape_uppercase_x(ch)),
283 Backslash => push_literal(Whatever, b"\\\\"),
284 SingleQuote => push_literal(Whatever, b"\\'"),
285 DoubleQuote => push_literal(Inside, b"\""),
286 Delete => push_literal(Outside, b"\\X7F"),
287 PrintableInert(ch) => push_literal(Whatever, &ch.to_le_bytes()),
288 Printable(ch) => push_literal(Inside, &ch.to_le_bytes()),
289 Utf8(char) => push_literal(Inside, char.encode_utf8(buf).as_bytes()),
290 }
291 }
292 if inside_quotes_now {
293 sout.push(b'\'');
294 }
295 }
296}
297
298#[inline]
313fn u8_to_hex_escape_uppercase_x(ch: u8) -> [u8; 4] {
314 const HEX_DIGITS: &[u8] = b"0123456789ABCDEF";
315 [
316 b'\\',
317 b'X',
318 HEX_DIGITS[(ch >> 4) as usize],
319 HEX_DIGITS[(ch & 0xF) as usize],
320 ]
321}
322
323#[cfg(test)]
324#[test]
325fn test_u8_to_hex_escape_uppercase_x() {
326 for ch in u8::MIN..=u8::MAX {
327 let expected = format!("\\X{ch:02X}");
328 let observed = u8_to_hex_escape_uppercase_x(ch);
329 let observed = std::str::from_utf8(&observed).unwrap();
330 assert_eq!(observed, &expected);
331 }
332}