• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

zbraniecki / icu4x / 13958601093

19 Mar 2025 04:17PM UTC coverage: 74.164% (-1.5%) from 75.71%
13958601093

push

github

web-flow
Clean up properties docs (#6315)

58056 of 78281 relevant lines covered (74.16%)

819371.32 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.18
/components/pattern/src/frontend/mod.rs
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
#[cfg(feature = "databake")]
6
mod databake;
7
#[cfg(feature = "serde")]
8
pub(crate) mod serde;
9
use crate::common::*;
10
#[cfg(feature = "alloc")]
11
use crate::Error;
12
#[cfg(feature = "alloc")]
13
use crate::Parser;
14
#[cfg(feature = "alloc")]
15
use crate::ParserOptions;
16
#[cfg(feature = "alloc")]
17
use alloc::{borrow::ToOwned, boxed::Box, str::FromStr, string::String};
18
use core::{convert::Infallible, fmt, marker::PhantomData};
19
use writeable::{adapters::TryWriteableInfallibleAsWriteable, PartsWrite, TryWriteable, Writeable};
20

21
/// A string pattern with placeholders.
22
///
23
/// There are 2 generic parameters: `Backend` and `Store`.
24
///
25
/// # Backend
26
///
27
/// This determines the nature of placeholders and serialized encoding of the pattern.
28
///
29
/// The following backends are available:
30
///
31
/// - [`SinglePlaceholder`] for patterns with up to one placeholder: `"{0} days ago"`
32
/// - [`DoublePlaceholder`] for patterns with up to two placeholders: `"{0} days, {1} hours ago"`
33
/// - [`MultiNamedPlaceholder`] for patterns with named placeholders: `"{name} sent you a message"`
34
///
35
/// # Format to Parts
36
///
37
/// [`Pattern`] propagates [`Part`]s from inner writeables. In addition, it supports annotating
38
/// [`Part`]s for individual literals or placeholders via the [`PlaceholderValueProvider`] trait.
39
///
40
/// # Examples
41
///
42
/// Interpolating a [`SinglePlaceholder`] pattern:
43
///
44
/// ```
45
/// use core::str::FromStr;
46
/// use icu_pattern::Pattern;
47
/// use icu_pattern::SinglePlaceholder;
48
/// use writeable::assert_writeable_eq;
49
///
50
/// let pattern = Pattern::<SinglePlaceholder>::try_from_str(
51
///     "Hello, {0}!",
52
///     Default::default(),
53
/// )
54
/// .unwrap();
55
///
56
/// assert_writeable_eq!(pattern.interpolate(["Alice"]), "Hello, Alice!");
57
/// ```
58
///
59
/// [`SinglePlaceholder`]: crate::SinglePlaceholder
60
/// [`DoublePlaceholder`]: crate::DoublePlaceholder
61
/// [`MultiNamedPlaceholder`]: crate::MultiNamedPlaceholder
62
/// [`Part`]: writeable::Part
63
#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
64
#[repr(transparent)]
65
pub struct Pattern<B: PatternBackend> {
66
    _backend: PhantomData<B>,
67
    /// The encoded storage
68
    pub store: B::Store,
69
}
70

71
impl<B: PatternBackend> PartialEq for Pattern<B> {
72
    fn eq(&self, other: &Self) -> bool {
3,419✔
73
        self.store == other.store
3,419✔
74
    }
3,419✔
75
}
76

77
impl<B: PatternBackend> core::fmt::Debug for Pattern<B> {
78
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
×
79
        f.debug_struct("Pattern")
×
80
            .field("_backend", &self._backend)
81
            .field("store", &&self.store)
×
82
            .finish()
83
    }
×
84
}
85

86
impl<B: PatternBackend> Default for &'static Pattern<B> {
87
    fn default() -> Self {
88
        Pattern::from_ref_store_unchecked(B::empty())
89
    }
90
}
91

92
#[cfg(feature = "alloc")]
93
impl<B: PatternBackend> Default for Box<Pattern<B>>
94
where
95
    Box<B::Store>: From<&'static B::Store>,
96
{
97
    fn default() -> Self {
1,001✔
98
        Pattern::from_boxed_store_unchecked(Box::from(B::empty()))
1,001✔
99
    }
1,001✔
100
}
101

102
#[test]
103
fn test_defaults() {
2✔
104
    assert_eq!(
2✔
105
        Box::<Pattern::<crate::SinglePlaceholder>>::default(),
1✔
106
        Pattern::try_from_items(core::iter::empty()).unwrap()
1✔
107
    );
108
    assert_eq!(
2✔
109
        Box::<Pattern::<crate::DoublePlaceholder>>::default(),
1✔
110
        Pattern::try_from_items(core::iter::empty()).unwrap()
1✔
111
    );
112
    assert_eq!(
2✔
113
        Box::<Pattern::<crate::MultiNamedPlaceholder>>::default(),
1✔
114
        Pattern::try_from_items(core::iter::empty()).unwrap()
1✔
115
    );
116
}
2✔
117

118
#[cfg(feature = "alloc")]
119
impl<B: PatternBackend> ToOwned for Pattern<B>
120
where
121
    Box<B::Store>: for<'a> From<&'a B::Store>,
122
{
123
    type Owned = Box<Pattern<B>>;
124

125
    fn to_owned(&self) -> Self::Owned {
14✔
126
        Self::from_boxed_store_unchecked(Box::from(&self.store))
14✔
127
    }
14✔
128
}
129

130
#[cfg(feature = "alloc")]
131
impl<B: PatternBackend> Clone for Box<Pattern<B>>
132
where
133
    Box<B::Store>: for<'a> From<&'a B::Store>,
134
{
135
    fn clone(&self) -> Self {
2,904✔
136
        Pattern::from_boxed_store_unchecked(Box::from(&self.store))
2,904✔
137
    }
2,904✔
138
}
139

140
impl<B: PatternBackend> Pattern<B> {
141
    #[cfg(feature = "alloc")]
142
    pub(crate) const fn from_boxed_store_unchecked(store: Box<B::Store>) -> Box<Self> {
31,017✔
143
        // Safety: Pattern is repr(transparent) over B::Store
144
        unsafe { core::mem::transmute(store) }
145
    }
31,017✔
146

147
    #[doc(hidden)] // databake
148
    pub const fn from_ref_store_unchecked(store: &B::Store) -> &Self {
5,559✔
149
        // Safety: Pattern is repr(transparent) over B::Store
150
        unsafe { &*(store as *const B::Store as *const Self) }
151
    }
5,559✔
152
}
153

154
#[cfg(feature = "alloc")]
155
impl<B> Pattern<B>
156
where
157
    B: PatternBackend,
158
{
159
    /// Creates a pattern from an iterator of pattern items.
160
    ///
161
    /// ✨ *Enabled with the `alloc` Cargo feature.*
162
    ///
163
    /// # Examples
164
    ///
165
    /// ```
166
    /// use icu_pattern::Pattern;
167
    /// use icu_pattern::PatternItemCow;
168
    /// use icu_pattern::SinglePlaceholder;
169
    /// use icu_pattern::SinglePlaceholderKey;
170
    /// use std::borrow::Cow;
171
    ///
172
    /// Pattern::<SinglePlaceholder>::try_from_items(
173
    ///     [
174
    ///         PatternItemCow::Placeholder(SinglePlaceholderKey::Singleton),
175
    ///         PatternItemCow::Literal(Cow::Borrowed(" days")),
176
    ///     ]
177
    ///     .into_iter(),
178
    /// )
179
    /// .expect("valid pattern items");
180
    /// ```
181
    pub fn try_from_items<'a, I>(items: I) -> Result<Box<Self>, Error>
44✔
182
    where
183
        I: Iterator<Item = PatternItemCow<'a, B::PlaceholderKeyCow<'a>>>,
184
    {
185
        let store = B::try_from_items(items.map(Ok))?;
44✔
186
        #[cfg(debug_assertions)]
187
        match B::validate_store(core::borrow::Borrow::borrow(&store)) {
44✔
188
            Ok(()) => (),
189
            Err(e) => {
×
190
                debug_assert!(false, "{:?}", e);
×
191
            }
192
        };
193
        Ok(Self::from_boxed_store_unchecked(store))
44✔
194
    }
44✔
195
}
196

197
#[cfg(feature = "alloc")]
198
impl<'a, B> Pattern<B>
199
where
200
    B: PatternBackend,
201
    B::PlaceholderKeyCow<'a>: FromStr,
202
    <B::PlaceholderKeyCow<'a> as FromStr>::Err: fmt::Debug,
203
{
204
    /// Creates a pattern by parsing a syntax string.
205
    ///
206
    /// ✨ *Enabled with the `alloc` Cargo feature.*
207
    ///
208
    /// # Examples
209
    ///
210
    /// ```
211
    /// use icu_pattern::Pattern;
212
    /// use icu_pattern::SinglePlaceholder;
213
    ///
214
    /// // Create a pattern from a valid string:
215
    /// Pattern::<SinglePlaceholder>::try_from_str("{0} days", Default::default())
216
    ///     .expect("valid pattern");
217
    ///
218
    /// // Error on an invalid pattern:
219
    /// Pattern::<SinglePlaceholder>::try_from_str("{0 days", Default::default())
220
    ///     .expect_err("mismatched braces");
221
    /// ```
222
    pub fn try_from_str(pattern: &str, options: ParserOptions) -> Result<Box<Self>, Error> {
27,069✔
223
        let parser = Parser::new(pattern, options);
27,069✔
224
        let store = B::try_from_items(parser)?;
27,069✔
225
        #[cfg(debug_assertions)]
226
        match B::validate_store(core::borrow::Borrow::borrow(&store)) {
27,065✔
227
            Ok(()) => (),
228
            Err(e) => {
×
229
                debug_assert!(false, "{:?} for pattern {:?}", e, pattern);
×
230
            }
231
        };
232
        Ok(Self::from_boxed_store_unchecked(store))
27,051✔
233
    }
27,059✔
234
}
235

236
impl<B> Pattern<B>
237
where
238
    B: PatternBackend,
239
{
240
    /// Returns an iterator over the [`PatternItem`]s in this pattern.
241
    pub fn iter(&self) -> impl Iterator<Item = PatternItem<B::PlaceholderKey<'_>>> + '_ {
20✔
242
        B::iter_items(&self.store)
20✔
243
    }
20✔
244

245
    /// Returns a [`TryWriteable`] that interpolates items from the given replacement provider
246
    /// into this pattern string.
247
    pub fn try_interpolate<'a, P>(
248
        &'a self,
249
        value_provider: P,
250
    ) -> impl TryWriteable<Error = B::Error<'a>> + fmt::Display + 'a
251
    where
252
        P: PlaceholderValueProvider<B::PlaceholderKey<'a>, Error = B::Error<'a>> + 'a,
253
    {
254
        WriteablePattern::<B, P> {
255
            store: &self.store,
256
            value_provider,
257
        }
258
    }
259

260
    #[cfg(feature = "alloc")]
261
    /// Interpolates the pattern directly to a string, returning the string or an error.
262
    ///
263
    /// In addition to the error, the lossy fallback string is returned in the failure case.
264
    ///
265
    /// ✨ *Enabled with the `alloc` Cargo feature.*
266
    pub fn try_interpolate_to_string<'a, P>(
267
        &'a self,
268
        value_provider: P,
269
    ) -> Result<String, (B::Error<'a>, String)>
270
    where
271
        P: PlaceholderValueProvider<B::PlaceholderKey<'a>, Error = B::Error<'a>> + 'a,
272
    {
273
        self.try_interpolate(value_provider)
274
            .try_write_to_string()
275
            .map(|s| s.into_owned())
276
            .map_err(|(e, s)| (e, s.into_owned()))
277
    }
278
}
279

280
impl<B> Pattern<B>
281
where
282
    for<'b> B: PatternBackend<Error<'b> = Infallible>,
283
{
284
    /// Returns a [`Writeable`] that interpolates items from the given replacement provider
285
    /// into this pattern string.
286
    pub fn interpolate<'a, P>(&'a self, value_provider: P) -> impl Writeable + fmt::Display + 'a
11,428✔
287
    where
288
        P: PlaceholderValueProvider<B::PlaceholderKey<'a>, Error = B::Error<'a>> + 'a,
289
    {
290
        TryWriteableInfallibleAsWriteable(WriteablePattern::<B, P> {
11,428✔
291
            store: &self.store,
292
            value_provider,
293
        })
294
    }
11,428✔
295

296
    #[cfg(feature = "alloc")]
297
    /// Interpolates the pattern directly to a string.
298
    ///
299
    /// ✨ *Enabled with the `alloc` Cargo feature.*
300
    pub fn interpolate_to_string<'a, P>(&'a self, value_provider: P) -> String
301
    where
302
        P: PlaceholderValueProvider<B::PlaceholderKey<'a>, Error = B::Error<'a>> + 'a,
303
    {
304
        self.interpolate(value_provider)
305
            .write_to_string()
306
            .into_owned()
307
    }
308
}
309

310
struct WriteablePattern<'a, B: PatternBackend, P> {
311
    store: &'a B::Store,
312
    value_provider: P,
313
}
314

315
impl<'a, B, P> TryWriteable for WriteablePattern<'a, B, P>
316
where
317
    B: PatternBackend,
318
    P: PlaceholderValueProvider<B::PlaceholderKey<'a>, Error = B::Error<'a>>,
319
{
320
    type Error = B::Error<'a>;
321

322
    fn try_write_to_parts<S: PartsWrite + ?Sized>(
10,862✔
323
        &self,
324
        sink: &mut S,
325
    ) -> Result<Result<(), Self::Error>, fmt::Error> {
326
        let mut error = None;
10,862✔
327
        let it = B::iter_items(self.store);
10,862✔
328
        #[cfg(debug_assertions)]
329
        let (size_hint, mut actual_len) = (it.size_hint(), 0);
10,862✔
330
        for item in it {
32,719✔
331
            match item {
21,917✔
332
                PatternItem::Literal(s) => {
11,351✔
333
                    self.value_provider.map_literal(s).write_to_parts(sink)?;
11,247✔
334
                }
335
                PatternItem::Placeholder(key) => {
387✔
336
                    let element_writeable = self.value_provider.value_for(key);
10,670✔
337
                    if let Err(e) = element_writeable.try_write_to_parts(sink)? {
21,549✔
338
                        // Keep the first error if there was one
339
                        error.get_or_insert(e);
×
340
                    }
341
                }
10,671✔
342
            }
343
            #[cfg(debug_assertions)]
344
            {
345
                actual_len += 1;
21,922✔
346
            }
347
        }
10,798✔
348
        #[cfg(debug_assertions)]
349
        {
350
            debug_assert!(actual_len >= size_hint.0);
10,798✔
351
            if let Some(max_len) = size_hint.1 {
10,798✔
352
                debug_assert!(actual_len <= max_len);
10,796✔
353
            }
354
        }
355
        if let Some(e) = error {
10,798✔
356
            Ok(Err(e))
×
357
        } else {
358
            Ok(Ok(()))
10,794✔
359
        }
360
    }
10,794✔
361
}
362

363
impl<'a, B, P> fmt::Display for WriteablePattern<'a, B, P>
364
where
365
    B: PatternBackend,
366
    P: PlaceholderValueProvider<B::PlaceholderKey<'a>, Error = B::Error<'a>>,
367
{
368
    #[inline]
369
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
370
        // Discard the TryWriteable error (lossy mode)
371
        self.try_write_to(f).map(|_| ())
372
    }
373
}
374

375
#[test]
376
fn test_try_from_str_inference() {
2✔
377
    use crate::SinglePlaceholder;
378
    let _: Box<Pattern<SinglePlaceholder>> =
379
        Pattern::try_from_str("{0} days", Default::default()).unwrap();
1✔
380
    let _ = Pattern::<SinglePlaceholder>::try_from_str("{0} days", Default::default()).unwrap();
1✔
381
}
2✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc