rescue_blanket/lib.rs
1#![cfg_attr(not(feature = "std"), no_std)]
2
3//! Escape values while they are being formatted
4//!
5//! When processing data, and particularly when forwarding data, in the form of
6//! character sequences (strings, streams, ...), one needs to escape certain
7//! characters or constructs which have some special meaning in the format. This
8//! crate provides [Escaped], a wrapper implementing [Display] such that the
9//! inner value is automatically escaped when formatted.
10//!
11//! The escaping logic can be customized via the [Escaper] trait, or by
12//! supplying an `FnMut(char) -> Display + Clone`.
13//!
14//! Rather than importing [Escaped] directly, users are encouraged to import
15//! [Escapable] instead. This convenience trait augments all [Sized] [Display]
16//! types with functions wrapping the value in an [Escaped] (for unsized types,
17//! a reference to the value may be used instead):
18//!
19//! ```
20//! use rescue_blanket::Escapable;
21//! println!("foo=\"{}\"", "bar=\"baz\"".escaped_with(char::escape_default));
22//! ```
23//!
24//! # Why using `rescue_blanket`?
25//!
26//! There are a number of crates already for escaping strings, and there are
27//! already [str::escape_default], [escape_debug](str::escape_debug) and
28//! [escape_unicode](str::escape_unicode), so why yet another library?
29//!
30//! These functions, and the libraries I found, only work well if the values
31//! you need to escape are already accessible as [str]. However, sometimes your
32//! values are more complex, maybe recursive, and you may not want put escaping
33//! logic inside their [Display] implementation. After all, the need for
34//! escaping arises from the context the value is formatted in, not from the
35//! value itself.
36//!
37//! You could always format complex values into some buffer (e.g. [String]) and
38//! apply escaping on the result, but that requires the additional buffer and
39//! you may want to avoid that. Depending on the [Escaper], the use of [Escaped]
40//! does not involve any additional buffering.
41
42use core::fmt::{self, Display};
43
44
45/// Character-wise processor implementing some escaping logic
46///
47/// Types implementing this trait define how a string or value is escaped, based
48/// on individual `char`s. An impls' [process](Escaper::process) function will
49/// receive one character and produce an appropriate [Output](Escaper::Output)
50/// implementing [Display].
51///
52/// In simple cases, the output will display as either the input character or,
53/// if the character needs to be escaped, an appropriate escape sequence. In
54/// more complex cases, the escaping logic may end up being a state machine of
55/// some kind driven by input `char`s. In order to support such use-cases,
56/// [process](Escaper::process) takes a mutable reference of `self`, allowing it
57/// to carry state across invocations.
58///
59/// # Note
60///
61/// An `Escaper` needs to implement [Clone]. However, escaping of a single
62/// string or value is to be performed on the same instance. Clones do not
63/// expected to share any state.
64///
65/// # Note
66///
67/// A blanket implementation for `FnMut(char) -> impl Display + Clone` is
68/// provided for users' convenience.
69pub trait Escaper: Clone {
70 /// Partial output after escaping
71 ///
72 /// This type represents the output of processing a single input `char`.
73 type Output: Display;
74
75 /// Process a single input character
76 ///
77 /// This function processes a single input `char` and produces as a result
78 /// an appropriate [Output](Escaper::Output). The concatenation of the
79 /// results of [ToString::to_string] via [Display] for each
80 /// [Output](Escaper::Output) results in a correctly escaped `String`.
81 fn process(&mut self, input: char) -> Self::Output;
82}
83
84impl<F: FnMut(char) -> O + Clone, O: Display> Escaper for F {
85 type Output = O;
86
87 fn process(&mut self, input: char) -> Self::Output {
88 self(input)
89 }
90}
91
92
93/// Wrapper for escaping items during formatting
94///
95/// This type wraps an item implementing [Display] together with an [Escaper].
96/// When displayed via its own implementation of [Display], the encapsulated
97/// item will be escaped via the [Escaper] during the formatting process.
98///
99/// # Note
100///
101/// Users of the library will usually prefer importing and using [Escapable]
102/// over using this type directly. An exception may be the construction of
103/// interfaces enforcing some sort of escaping for inputs.
104///
105/// # Examples
106///
107/// ```
108/// let escaped = rescue_blanket::Escaped::new("foo=\"bar\"", char::escape_default);
109/// assert_eq!(escaped.to_string(), "foo=\\\"bar\\\"");
110/// ```
111#[derive(Copy, Clone, Debug)]
112pub struct Escaped<I: fmt::Display, E: Escaper> {
113 item: I,
114 escaper: E,
115}
116
117impl<I: fmt::Display, E: Escaper> Escaped<I, E> {
118 /// Create a new wrapper for the given item with an [Escaper]
119 pub fn new(item: I, escaper: E) -> Self {
120 Self {item, escaper}
121 }
122
123 /// Create a new wrapper for the given item with a default [Escaper]
124 pub fn new_default(item: I) -> Self where E: Default {
125 Self {item, escaper: Default::default()}
126 }
127}
128
129impl<I: fmt::Display, E: Escaper + Default> From<I> for Escaped<I, E> {
130 fn from(item: I) -> Self {
131 Self::new_default(item)
132 }
133}
134
135impl<I: fmt::Display, E: Escaper> Display for Escaped<I, E> {
136 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
137 use fmt::Write;
138
139 let mut out = WriteProxy::new(f, self.escaper.clone());
140 write!(out, "{}", self.item)
141 }
142}
143
144
145/// Escaping [fmt::Write] implementation
146struct WriteProxy<'a, 'b, E: Escaper> {
147 formatter: &'a mut fmt::Formatter<'b>,
148 escaper: E,
149}
150
151impl<'a, 'b, E: Escaper> WriteProxy<'a, 'b, E> {
152 /// Create a new proxy
153 fn new(formatter: &'a mut fmt::Formatter<'b>, escaper: E) -> Self {
154 Self {formatter, escaper}
155 }
156}
157
158impl<E: Escaper> fmt::Write for WriteProxy<'_, '_, E> {
159 fn write_str(&mut self, s: &str) -> fmt::Result {
160 s.chars().try_for_each(|c| self.write_char(c))
161 }
162
163 fn write_char(&mut self, c: char) -> fmt::Result {
164 self.escaper.process(c).fmt(self.formatter)
165 }
166}
167
168
169/// Convenience trait for escaping items
170///
171/// This trait augments types implementing [Display] with functions for wrapping
172/// them in instances of [Escaped], which will escape the value when being
173/// formatted.
174///
175/// # Examples
176///
177/// ```
178/// use rescue_blanket::Escapable;
179/// assert_eq!("foo=\"bar\"".escaped_with(char::escape_default).to_string(), "foo=\\\"bar\\\"");
180/// ```
181pub trait Escapable: Display + Sized {
182 /// Wrap this value in an [Escaped] for escaped formatting
183 ///
184 /// The resulting [Escaped] will escape the value when being formatted via
185 /// [Display] using the given [Escaper].
186 fn escaped_with<E: Escaper>(self, escaper: E) -> Escaped<Self, E>;
187
188 /// Wrap this value in an [Escaped] for escaped formatting
189 ///
190 /// The resulting [Escaped] will escape the value when being formatted via
191 /// [Display] using the given [Escaper].
192 fn escaped_with_default<E: Escaper + Default>(self) -> Escaped<Self, E> {
193 Escaped::new_default(self)
194 }
195
196 /// Wrap this value in an [Escaped] for escaping with [char::escape_default]
197 ///
198 /// The resulting [Escaped] will escape the value when being formatted via
199 /// [Display] using [char::escape_default] as [Escaper].
200 ///
201 /// # Examples
202 ///
203 /// ```
204 /// use rescue_blanket::Escapable;
205 /// let s = "foo=\"bar\"";
206 /// // Compare against str::escape_default()
207 /// assert_eq!(s.escaped_default().to_string(), s.escape_default().to_string());
208 /// ```
209 fn escaped_default(self) -> Escaped<Self, fn(char) -> core::char::EscapeDefault> {
210 self.escaped_with(char::escape_default)
211 }
212
213 /// Wrap this value in an [Escaped] for escaping with [char::escape_debug]
214 ///
215 /// The resulting [Escaped] will escape the value when being formatted via
216 /// [Display] using [char::escape_debug] as [Escaper].
217 ///
218 /// # Examples
219 ///
220 /// ```
221 /// use rescue_blanket::Escapable;
222 /// let s = "foo=\"bar\"";
223 /// // Compare against str::escape_debug()
224 /// assert_eq!(s.escaped_debug().to_string(), s.escape_debug().to_string());
225 /// ```
226 fn escaped_debug(self) -> Escaped<Self, fn(char) -> core::char::EscapeDebug> {
227 self.escaped_with(char::escape_debug)
228 }
229
230 /// Wrap this value in an [Escaped] for escaping with [char::escape_unicode]
231 ///
232 /// The resulting [Escaped] will escape the value when being formatted via
233 /// [Display] using [char::escape_unicode] as [Escaper].
234 ///
235 /// # Examples
236 ///
237 /// ```
238 /// use rescue_blanket::Escapable;
239 /// let s = "foo=\"bar\"";
240 /// // Compare against str::escape_unicode()
241 /// assert_eq!(s.escaped_unicode().to_string(), s.escape_unicode().to_string());
242 /// ```
243 fn escaped_unicode(self) -> Escaped<Self, fn(char) -> core::char::EscapeUnicode> {
244 self.escaped_with(char::escape_unicode)
245 }
246}
247
248impl<T: Display> Escapable for T {
249 fn escaped_with<E: Escaper>(self, escaper: E) -> Escaped<Self, E> {
250 Escaped::new(self, escaper)
251 }
252}
253