1// This file is part of ICU4X. For terms of use, please see the file
2// called LICENSE at the top level of the ICU4X source tree
3// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
4
5// https://github.com/unicode-org/icu4x/blob/main/docs/process/boilerplate.md#library-annotations
6#![cfg_attr(all(not(test), not(doc)), no_std)]
7#![cfg_attr(
8 not(test),
9 deny(
10 clippy::indexing_slicing,
11 clippy::unwrap_used,
12 clippy::expect_used,
13 clippy::panic,
14 clippy::exhaustive_structs,
15 clippy::exhaustive_enums,
16 missing_debug_implementations,
17 )
18)]
19
20//! `writeable` is a utility crate of the [`ICU4X`] project.
21//!
22//! It includes [`Writeable`], a core trait representing an object that can be written to a
23//! sink implementing `std::fmt::Write`. It is an alternative to `std::fmt::Display` with the
24//! addition of a function indicating the number of bytes to be written.
25//!
26//! `Writeable` improves upon `std::fmt::Display` in two ways:
27//!
28//! 1. More efficient, since the sink can pre-allocate bytes.
29//! 2. Smaller code, since the format machinery can be short-circuited.
30//!
31//! # Examples
32//!
33//! ```
34//! use std::fmt;
35//! use writeable::assert_writeable_eq;
36//! use writeable::LengthHint;
37//! use writeable::Writeable;
38//!
39//! struct WelcomeMessage<'s> {
40//! pub name: &'s str,
41//! }
42//!
43//! impl<'s> Writeable for WelcomeMessage<'s> {
44//! fn write_to<W: fmt::Write + ?Sized>(&self, sink: &mut W) -> fmt::Result {
45//! sink.write_str("Hello, ")?;
46//! sink.write_str(self.name)?;
47//! sink.write_char('!')?;
48//! Ok(())
49//! }
50//!
51//! fn writeable_length_hint(&self) -> LengthHint {
52//! // "Hello, " + '!' + length of name
53//! LengthHint::exact(8 + self.name.len())
54//! }
55//! }
56//!
57//! let message = WelcomeMessage { name: "Alice" };
58//! assert_writeable_eq!(&message, "Hello, Alice!");
59//!
60//! // Types implementing `Writeable` are recommended to also implement `fmt::Display`.
61//! // This can be simply done by redirecting to the `Writeable` implementation:
62//! writeable::impl_display_with_writeable!(WelcomeMessage<'_>);
63//! ```
64//!
65//! [`ICU4X`]: ../icu/index.html
66
67extern crate alloc;
68
69mod impls;
70mod ops;
71
72use alloc::borrow::Cow;
73use alloc::string::String;
74use alloc::vec::Vec;
75use core::fmt;
76
77/// A hint to help consumers of `Writeable` pre-allocate bytes before they call
78/// [`write_to`](Writeable::write_to).
79///
80/// This behaves like `Iterator::size_hint`: it is a tuple where the first element is the
81/// lower bound, and the second element is the upper bound. If the upper bound is `None`
82/// either there is no known upper bound, or the upper bound is larger than `usize`.
83///
84/// `LengthHint` implements std`::ops::{Add, Mul}` and similar traits for easy composition.
85/// During computation, the lower bound will saturate at `usize::MAX`, while the upper
86/// bound will become `None` if `usize::MAX` is exceeded.
87#[derive(Debug, PartialEq, Eq, Copy, Clone)]
88#[non_exhaustive]
89pub struct LengthHint(pub usize, pub Option<usize>);
90
91impl LengthHint {
92 pub fn undefined() -> Self {
93 Self(0, None)
94 }
95
96 /// `write_to` will use exactly n bytes.
97 pub fn exact(n: usize) -> Self {
98 Self(n, Some(n))
99 }
100
101 /// `write_to` will use at least n bytes.
102 pub fn at_least(n: usize) -> Self {
103 Self(n, None)
104 }
105
106 /// `write_to` will use at most n bytes.
107 pub fn at_most(n: usize) -> Self {
108 Self(0, Some(n))
109 }
110
111 /// `write_to` will use between `n` and `m` bytes.
112 pub fn between(n: usize, m: usize) -> Self {
113 Self(Ord::min(n, m), Some(Ord::max(n, m)))
114 }
115
116 /// Returns a recommendation for the number of bytes to pre-allocate.
117 /// If an upper bound exists, this is used, otherwise the lower bound
118 /// (which might be 0).
119 ///
120 /// # Examples
121 ///
122 /// ```
123 /// use writeable::Writeable;
124 ///
125 /// fn pre_allocate_string(w: &impl Writeable) -> String {
126 /// String::with_capacity(w.writeable_length_hint().capacity())
127 /// }
128 /// ```
129 pub fn capacity(&self) -> usize {
130 self.1.unwrap_or(self.0)
131 }
132
133 /// Returns whether the `LengthHint` indicates that the string is exactly 0 bytes long.
134 pub fn is_zero(&self) -> bool {
135 self.1 == Some(0)
136 }
137}
138
139/// [`Part`]s are used as annotations for formatted strings. For example, a string like
140/// `Alice, Bob` could assign a `NAME` part to the substrings `Alice` and `Bob`, and a
141/// `PUNCTUATION` part to `, `. This allows for example to apply styling only to names.
142///
143/// `Part` contains two fields, whose usage is left up to the producer of the [`Writeable`].
144/// Conventionally, the `category` field will identify the formatting logic that produces
145/// the string/parts, whereas the `value` field will have semantic meaning. `NAME` and
146/// `PUNCTUATION` could thus be defined as
147/// ```
148/// # use writeable::Part;
149/// const NAME: Part = Part {
150/// category: "userlist",
151/// value: "name",
152/// };
153/// const PUNCTUATION: Part = Part {
154/// category: "userlist",
155/// value: "punctuation",
156/// };
157/// ```
158///
159/// That said, consumers should not usually have to inspect `Part` internals. Instead,
160/// formatters should expose the `Part`s they produces as constants.
161#[derive(Clone, Copy, Debug, PartialEq)]
162#[allow(clippy::exhaustive_structs)] // stable
163pub struct Part {
164 pub category: &'static str,
165 pub value: &'static str,
166}
167
168/// A sink that supports annotating parts of the string with `Part`s.
169pub trait PartsWrite: fmt::Write {
170 type SubPartsWrite: PartsWrite + ?Sized;
171
172 fn with_part(
173 &mut self,
174 part: Part,
175 f: impl FnMut(&mut Self::SubPartsWrite) -> fmt::Result,
176 ) -> fmt::Result;
177}
178
179/// `Writeable` is an alternative to `std::fmt::Display` with the addition of a length function.
180pub trait Writeable {
181 /// Writes a string to the given sink. Errors from the sink are bubbled up.
182 /// The default implementation delegates to `write_to_parts`, and discards any
183 /// `Part` annotations.
184 fn write_to<W: fmt::Write + ?Sized>(&self, sink: &mut W) -> fmt::Result {
185 struct CoreWriteAsPartsWrite<W: fmt::Write + ?Sized>(W);
186 impl<W: fmt::Write + ?Sized> fmt::Write for CoreWriteAsPartsWrite<W> {
187 fn write_str(&mut self, s: &str) -> fmt::Result {
188 self.0.write_str(s)
189 }
190
191 fn write_char(&mut self, c: char) -> fmt::Result {
192 self.0.write_char(c)
193 }
194 }
195
196 impl<W: fmt::Write + ?Sized> PartsWrite for CoreWriteAsPartsWrite<W> {
197 type SubPartsWrite = CoreWriteAsPartsWrite<W>;
198
199 fn with_part(
200 &mut self,
201 _part: Part,
202 mut f: impl FnMut(&mut Self::SubPartsWrite) -> fmt::Result,
203 ) -> fmt::Result {
204 f(self)
205 }
206 }
207
208 self.write_to_parts(&mut CoreWriteAsPartsWrite(sink))
209 }
210
211 /// Write bytes and `Part` annotations to the given sink. Errors from the
212 /// sink are bubbled up. The default implementation delegates to `write_to`,
213 /// and doesn't produce any `Part` annotations.
214 fn write_to_parts<S: PartsWrite + ?Sized>(&self, sink: &mut S) -> fmt::Result {
215 self.write_to(sink)
216 }
217
218 /// Returns a hint for the number of UTF-8 bytes that will be written to the sink.
219 ///
220 /// Override this method if it can be computed quickly.
221 fn writeable_length_hint(&self) -> LengthHint {
222 LengthHint::undefined()
223 }
224
225 /// Creates a new `String` with the data from this `Writeable`. Like `ToString`,
226 /// but smaller and faster.
227 ///
228 /// The default impl allocates an owned `String`. However, if it is possible to return a
229 /// borrowed string, overwrite this method to return a `Cow::Borrowed`.
230 ///
231 /// To remove the `Cow` wrapper, call `.into_owned()` or `.as_str()` as appropriate.
232 ///
233 /// # Examples
234 ///
235 /// Inspect a `Writeable` before writing it to the sink:
236 ///
237 /// ```
238 /// use core::fmt::{Result, Write};
239 /// use writeable::Writeable;
240 ///
241 /// fn write_if_ascii<W, S>(w: &W, sink: &mut S) -> Result
242 /// where
243 /// W: Writeable + ?Sized,
244 /// S: Write + ?Sized,
245 /// {
246 /// let s = w.write_to_string();
247 /// if s.is_ascii() {
248 /// sink.write_str(&s)
249 /// } else {
250 /// Ok(())
251 /// }
252 /// }
253 /// ```
254 ///
255 /// Convert the `Writeable` into a fully owned `String`:
256 ///
257 /// ```
258 /// use writeable::Writeable;
259 ///
260 /// fn make_string(w: &impl Writeable) -> String {
261 /// w.write_to_string().into_owned()
262 /// }
263 /// ```
264 fn write_to_string(&self) -> Cow<str> {
265 let hint = self.writeable_length_hint();
266 if hint.is_zero() {
267 return Cow::Borrowed("");
268 }
269 let mut output = String::with_capacity(hint.capacity());
270 let _ = self.write_to(&mut output);
271 Cow::Owned(output)
272 }
273}
274
275/// Implements [`Display`](core::fmt::Display) for types that implement [`Writeable`].
276///
277/// It's recommended to do this for every [`Writeable`] type, as it will add
278/// support for `core::fmt` features like [`fmt!`](std::fmt),
279/// [`print!`](std::print), [`write!`](std::write), etc.
280#[macro_export]
281macro_rules! impl_display_with_writeable {
282 ($type:ty) => {
283 /// This trait is implemented for compatibility with [`fmt!`](alloc::fmt).
284 /// To create a string, [`Writeable::write_to_string`] is usually more efficient.
285 impl core::fmt::Display for $type {
286 #[inline]
287 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
288 $crate::Writeable::write_to(&self, f)
289 }
290 }
291 };
292}
293
294/// Testing macros for types implementing Writeable. The first argument should be a
295/// `Writeable`, the second argument a string, and the third argument (*_parts_eq only)
296/// a list of parts (`[(usize, usize, Part)]`).
297///
298/// The macros tests for equality of string content, parts (*_parts_eq only), and
299/// verify the size hint.
300///
301/// # Examples
302///
303/// ```
304/// # use writeable::Writeable;
305/// # use writeable::LengthHint;
306/// # use writeable::Part;
307/// # use writeable::assert_writeable_eq;
308/// # use writeable::assert_writeable_parts_eq;
309/// # use std::fmt::{self, Write};
310///
311/// const WORD: Part = Part {
312/// category: "foo",
313/// value: "word",
314/// };
315///
316/// struct Demo;
317/// impl Writeable for Demo {
318/// fn write_to_parts<S: writeable::PartsWrite + ?Sized>(
319/// &self,
320/// sink: &mut S,
321/// ) -> fmt::Result {
322/// sink.with_part(WORD, |w| w.write_str("foo"))
323/// }
324/// fn writeable_length_hint(&self) -> LengthHint {
325/// LengthHint::exact(3)
326/// }
327/// }
328///
329/// writeable::impl_display_with_writeable!(Demo);
330///
331/// assert_writeable_eq!(&Demo, "foo");
332/// assert_writeable_eq!(&Demo, "foo", "Message: {}", "Hello World");
333///
334/// assert_writeable_parts_eq!(&Demo, "foo", [(0, 3, WORD)]);
335/// assert_writeable_parts_eq!(
336/// &Demo,
337/// "foo",
338/// [(0, 3, WORD)],
339/// "Message: {}",
340/// "Hello World"
341/// );
342/// ```
343#[macro_export]
344macro_rules! assert_writeable_eq {
345 ($actual_writeable:expr, $expected_str:expr $(,)?) => {
346 $crate::assert_writeable_eq!($actual_writeable, $expected_str, "");
347 };
348 ($actual_writeable:expr, $expected_str:expr, $($arg:tt)+) => {{
349 let actual_writeable = &$actual_writeable;
350 let (actual_str, _) = $crate::writeable_to_parts_for_test(actual_writeable).unwrap();
351 assert_eq!(actual_str, $expected_str, $($arg)*);
352 assert_eq!(actual_str, $crate::Writeable::write_to_string(actual_writeable), $($arg)+);
353 let length_hint = $crate::Writeable::writeable_length_hint(actual_writeable);
354 assert!(
355 length_hint.0 <= actual_str.len(),
356 "hint lower bound {} larger than actual length {}: {}",
357 length_hint.0, actual_str.len(), format!($($arg)*),
358 );
359 if let Some(upper) = length_hint.1 {
360 assert!(
361 actual_str.len() <= upper,
362 "hint upper bound {} smaller than actual length {}: {}",
363 length_hint.0, actual_str.len(), format!($($arg)*),
364 );
365 }
366 assert_eq!(actual_writeable.to_string(), $expected_str);
367 }};
368}
369
370/// See [`assert_writeable_eq`].
371#[macro_export]
372macro_rules! assert_writeable_parts_eq {
373 ($actual_writeable:expr, $expected_str:expr, $expected_parts:expr $(,)?) => {
374 $crate::assert_writeable_parts_eq!($actual_writeable, $expected_str, $expected_parts, "");
375 };
376 ($actual_writeable:expr, $expected_str:expr, $expected_parts:expr, $($arg:tt)+) => {{
377 let actual_writeable = &$actual_writeable;
378 let (actual_str, actual_parts) = $crate::writeable_to_parts_for_test(actual_writeable).unwrap();
379 assert_eq!(actual_str, $expected_str, $($arg)+);
380 assert_eq!(actual_str, $crate::Writeable::write_to_string(actual_writeable), $($arg)+);
381 assert_eq!(actual_parts, $expected_parts, $($arg)+);
382 let length_hint = $crate::Writeable::writeable_length_hint(actual_writeable);
383 assert!(length_hint.0 <= actual_str.len(), $($arg)+);
384 if let Some(upper) = length_hint.1 {
385 assert!(actual_str.len() <= upper, $($arg)+);
386 }
387 assert_eq!(actual_writeable.to_string(), $expected_str);
388 }};
389}
390
391#[doc(hidden)]
392#[allow(clippy::type_complexity)]
393pub fn writeable_to_parts_for_test<W: Writeable>(
394 writeable: &W,
395) -> Result<(String, Vec<(usize, usize, Part)>), fmt::Error> {
396 struct State {
397 string: alloc::string::String,
398 parts: Vec<(usize, usize, Part)>,
399 }
400
401 impl fmt::Write for State {
402 fn write_str(&mut self, s: &str) -> fmt::Result {
403 self.string.write_str(s)
404 }
405 fn write_char(&mut self, c: char) -> fmt::Result {
406 self.string.write_char(c)
407 }
408 }
409
410 impl PartsWrite for State {
411 type SubPartsWrite = Self;
412 fn with_part(
413 &mut self,
414 part: Part,
415 mut f: impl FnMut(&mut Self::SubPartsWrite) -> fmt::Result,
416 ) -> fmt::Result {
417 let start = self.string.len();
418 f(self)?;
419 let end = self.string.len();
420 if start < end {
421 self.parts.push((start, end, part));
422 }
423 Ok(())
424 }
425 }
426
427 let mut state = State {
428 string: alloc::string::String::new(),
429 parts: Vec::new(),
430 };
431 writeable.write_to_parts(&mut state)?;
432
433 // Sort by first open and last closed
434 state
435 .parts
436 .sort_unstable_by_key(|(begin, end, _)| (*begin, end.wrapping_neg()));
437 Ok((state.string, state.parts))
438}
439