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

zbraniecki / icu4x / 12020603084

23 Nov 2024 08:43PM UTC coverage: 75.71% (+0.2%) from 75.477%
12020603084

push

github

sffc
Touch Cargo.lock

55589 of 73424 relevant lines covered (75.71%)

644270.14 hits per line

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

87.8
/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::{
19
    convert::Infallible,
20
    fmt::{self, Write},
21
    marker::PhantomData,
22
};
23
use writeable::{adapters::TryWriteableInfallibleAsWriteable, PartsWrite, TryWriteable, Writeable};
24

25
/// A string pattern with placeholders.
26
///
27
/// There are 2 generic parameters: `Backend` and `Store`.
28
///
29
/// # Backend
30
///
31
/// This determines the nature of placeholders and serialized encoding of the pattern.
32
///
33
/// The following backends are available:
34
///
35
/// - [`SinglePlaceholder`] for patterns with up to one placeholder: `"{0} days ago"`
36
/// - [`DoublePlaceholder`] for patterns with up to two placeholders: `"{0} days, {1} hours ago"`
37
/// - [`MultiNamedPlaceholder`] for patterns with named placeholders: `"{name} sent you a message"`
38
///
39
/// # Format to Parts
40
///
41
/// [`Pattern`] supports interpolating with [writeable::Part]s, annotations for whether the
42
/// substring was a placeholder or a literal.
43
///
44
/// By default, the substrings are annotated with [`PATTERN_LITERAL_PART`] and
45
/// [`PATTERN_PLACEHOLDER_PART`]. This can be customized with [`PlaceholderValueProvider`].
46
///
47
/// # Examples
48
///
49
/// Interpolating a [`SinglePlaceholder`] pattern with parts:
50
///
51
/// ```
52
/// use core::str::FromStr;
53
/// use icu_pattern::Pattern;
54
/// use icu_pattern::SinglePlaceholder;
55
/// use writeable::assert_writeable_parts_eq;
56
///
57
/// let pattern = Pattern::<SinglePlaceholder>::try_from_str(
58
///     "Hello, {0}!",
59
///     Default::default(),
60
/// )
61
/// .unwrap();
62
///
63
/// assert_writeable_parts_eq!(
64
///     pattern.interpolate(["Alice"]),
65
///     "Hello, Alice!",
66
///     [
67
///         (0, 7, icu_pattern::PATTERN_LITERAL_PART),
68
///         (7, 12, icu_pattern::PATTERN_PLACEHOLDER_PART),
69
///         (12, 13, icu_pattern::PATTERN_LITERAL_PART),
70
///     ]
71
/// );
72
/// ```
73
///
74
/// [`SinglePlaceholder`]: crate::SinglePlaceholder
75
/// [`DoublePlaceholder`]: crate::DoublePlaceholder
76
/// [`MultiNamedPlaceholder`]: crate::MultiNamedPlaceholder
77
#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
78
#[repr(transparent)]
79
pub struct Pattern<B: PatternBackend> {
80
    _backend: PhantomData<B>,
81
    /// The encoded storage
82
    pub store: B::Store,
83
}
84

85
impl<B: PatternBackend> PartialEq for Pattern<B> {
86
    fn eq(&self, other: &Self) -> bool {
3,333✔
87
        self.store == other.store
3,333✔
88
    }
3,333✔
89
}
90

91
impl<B: PatternBackend> core::fmt::Debug for Pattern<B> {
92
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
×
93
        f.debug_struct("Pattern")
×
94
            .field("_backend", &self._backend)
95
            .field("store", &&self.store)
×
96
            .finish()
97
    }
×
98
}
99

100
impl<B: PatternBackend> Default for &'static Pattern<B> {
101
    fn default() -> Self {
102
        Pattern::from_ref_store_unchecked(B::empty())
103
    }
104
}
105

106
#[cfg(feature = "alloc")]
107
impl<B: PatternBackend> Default for Box<Pattern<B>>
108
where
109
    Box<B::Store>: From<&'static B::Store>,
110
{
111
    fn default() -> Self {
933✔
112
        Pattern::from_boxed_store_unchecked(Box::from(B::empty()))
933✔
113
    }
933✔
114
}
115

116
#[test]
117
fn test_defaults() {
2✔
118
    assert_eq!(
2✔
119
        Box::<Pattern::<crate::SinglePlaceholder>>::default(),
1✔
120
        Pattern::try_from_items(core::iter::empty()).unwrap()
1✔
121
    );
122
    assert_eq!(
2✔
123
        Box::<Pattern::<crate::DoublePlaceholder>>::default(),
1✔
124
        Pattern::try_from_items(core::iter::empty()).unwrap()
1✔
125
    );
126
    assert_eq!(
2✔
127
        Box::<Pattern::<crate::MultiNamedPlaceholder>>::default(),
1✔
128
        Pattern::try_from_items(core::iter::empty()).unwrap()
1✔
129
    );
130
}
2✔
131

132
#[cfg(feature = "alloc")]
133
impl<B: PatternBackend> ToOwned for Pattern<B>
134
where
135
    Box<B::Store>: for<'a> From<&'a B::Store>,
136
{
137
    type Owned = Box<Pattern<B>>;
138

139
    fn to_owned(&self) -> Self::Owned {
12✔
140
        Self::from_boxed_store_unchecked(Box::from(&self.store))
12✔
141
    }
12✔
142
}
143

144
#[cfg(feature = "alloc")]
145
impl<B: PatternBackend> Clone for Box<Pattern<B>>
146
where
147
    Box<B::Store>: for<'a> From<&'a B::Store>,
148
{
149
    fn clone(&self) -> Self {
2,984✔
150
        Pattern::from_boxed_store_unchecked(Box::from(&self.store))
2,984✔
151
    }
2,984✔
152
}
153

154
impl<B: PatternBackend> Pattern<B> {
155
    #[cfg(feature = "alloc")]
156
    pub(crate) const fn from_boxed_store_unchecked(store: Box<B::Store>) -> Box<Self> {
31,376✔
157
        // Safety: Pattern's layout is the same as B::Store's
158
        unsafe { core::mem::transmute(store) }
159
    }
31,376✔
160

161
    #[doc(hidden)] // databake
162
    pub const fn from_ref_store_unchecked(store: &B::Store) -> &Self {
5,498✔
163
        // Safety: Pattern's layout is the same as B::Store's
164
        unsafe { &*(store as *const B::Store as *const Self) }
165
    }
5,498✔
166
}
167

168
#[cfg(feature = "alloc")]
169
impl<B> Pattern<B>
170
where
171
    B: PatternBackend,
172
{
173
    /// Creates a pattern from an iterator of pattern items.
174
    ///
175
    /// ✨ *Enabled with the `alloc` Cargo feature.*
176
    ///
177
    /// # Examples
178
    ///
179
    /// ```
180
    /// use icu_pattern::Pattern;
181
    /// use icu_pattern::PatternItemCow;
182
    /// use icu_pattern::SinglePlaceholder;
183
    /// use icu_pattern::SinglePlaceholderKey;
184
    /// use std::borrow::Cow;
185
    ///
186
    /// Pattern::<SinglePlaceholder>::try_from_items(
187
    ///     [
188
    ///         PatternItemCow::Placeholder(SinglePlaceholderKey::Singleton),
189
    ///         PatternItemCow::Literal(Cow::Borrowed(" days")),
190
    ///     ]
191
    ///     .into_iter(),
192
    /// )
193
    /// .expect("valid pattern items");
194
    /// ```
195
    pub fn try_from_items<'a, I>(items: I) -> Result<Box<Self>, Error>
29✔
196
    where
197
        I: Iterator<Item = PatternItemCow<'a, B::PlaceholderKeyCow<'a>>>,
198
    {
199
        let store = B::try_from_items(items.map(Ok))?;
29✔
200
        #[cfg(debug_assertions)]
201
        match B::validate_store(core::borrow::Borrow::borrow(&store)) {
29✔
202
            Ok(()) => (),
203
            Err(e) => {
×
204
                debug_assert!(false, "{:?}", e);
×
205
            }
206
        };
207
        Ok(Self::from_boxed_store_unchecked(store))
29✔
208
    }
29✔
209
}
210

211
#[cfg(feature = "alloc")]
212
impl<'a, B> Pattern<B>
213
where
214
    B: PatternBackend,
215
    B::PlaceholderKeyCow<'a>: FromStr,
216
    <B::PlaceholderKeyCow<'a> as FromStr>::Err: fmt::Debug,
217
{
218
    /// Creates a pattern by parsing a syntax string.
219
    ///
220
    /// ✨ *Enabled with the `alloc` Cargo feature.*
221
    ///
222
    /// # Examples
223
    ///
224
    /// ```
225
    /// use icu_pattern::Pattern;
226
    /// use icu_pattern::SinglePlaceholder;
227
    ///
228
    /// // Create a pattern from a valid string:
229
    /// Pattern::<SinglePlaceholder>::try_from_str("{0} days", Default::default())
230
    ///     .expect("valid pattern");
231
    ///
232
    /// // Error on an invalid pattern:
233
    /// Pattern::<SinglePlaceholder>::try_from_str("{0 days", Default::default())
234
    ///     .expect_err("mismatched braces");
235
    /// ```
236
    pub fn try_from_str(pattern: &str, options: ParserOptions) -> Result<Box<Self>, Error> {
27,424✔
237
        let parser = Parser::new(pattern, options);
27,424✔
238
        let store = B::try_from_items(parser)?;
27,424✔
239
        #[cfg(debug_assertions)]
240
        match B::validate_store(core::borrow::Borrow::borrow(&store)) {
27,420✔
241
            Ok(()) => (),
242
            Err(e) => {
×
243
                debug_assert!(false, "{:?} for pattern {:?}", e, pattern);
×
244
            }
245
        };
246
        Ok(Self::from_boxed_store_unchecked(store))
27,418✔
247
    }
27,422✔
248
}
249

250
impl<B> Pattern<B>
251
where
252
    B: PatternBackend,
253
{
254
    /// Returns an iterator over the [`PatternItem`]s in this pattern.
255
    pub fn iter(&self) -> impl Iterator<Item = PatternItem<B::PlaceholderKey<'_>>> + '_ {
20✔
256
        B::iter_items(&self.store)
20✔
257
    }
20✔
258

259
    /// Returns a [`TryWriteable`] that interpolates items from the given replacement provider
260
    /// into this pattern string.
261
    pub fn try_interpolate<'a, P>(
262
        &'a self,
263
        value_provider: P,
264
    ) -> impl TryWriteable<Error = B::Error<'a>> + fmt::Display + 'a
265
    where
266
        P: PlaceholderValueProvider<B::PlaceholderKey<'a>, Error = B::Error<'a>> + 'a,
267
    {
268
        WriteablePattern::<B, P> {
269
            store: &self.store,
270
            value_provider,
271
        }
272
    }
273

274
    #[cfg(feature = "alloc")]
275
    /// Interpolates the pattern directly to a string, returning the string or an error.
276
    ///
277
    /// In addition to the error, the lossy fallback string is returned in the failure case.
278
    ///
279
    /// ✨ *Enabled with the `alloc` Cargo feature.*
280
    pub fn try_interpolate_to_string<'a, P>(
281
        &'a self,
282
        value_provider: P,
283
    ) -> Result<String, (B::Error<'a>, String)>
284
    where
285
        P: PlaceholderValueProvider<B::PlaceholderKey<'a>, Error = B::Error<'a>> + 'a,
286
    {
287
        self.try_interpolate(value_provider)
288
            .try_write_to_string()
289
            .map(|s| s.into_owned())
290
            .map_err(|(e, s)| (e, s.into_owned()))
291
    }
292
}
293

294
impl<B> Pattern<B>
295
where
296
    for<'b> B: PatternBackend<Error<'b> = Infallible>,
297
{
298
    /// Returns a [`Writeable`] that interpolates items from the given replacement provider
299
    /// into this pattern string.
300
    pub fn interpolate<'a, P>(&'a self, value_provider: P) -> impl Writeable + fmt::Display + 'a
10,218✔
301
    where
302
        P: PlaceholderValueProvider<B::PlaceholderKey<'a>, Error = B::Error<'a>> + 'a,
303
    {
304
        TryWriteableInfallibleAsWriteable(WriteablePattern::<B, P> {
10,218✔
305
            store: &self.store,
306
            value_provider,
307
        })
308
    }
10,218✔
309

310
    #[cfg(feature = "alloc")]
311
    /// Interpolates the pattern directly to a string.
312
    ///
313
    /// ✨ *Enabled with the `alloc` Cargo feature.*
314
    pub fn interpolate_to_string<'a, P>(&'a self, value_provider: P) -> String
315
    where
316
        P: PlaceholderValueProvider<B::PlaceholderKey<'a>, Error = B::Error<'a>> + 'a,
317
    {
318
        self.interpolate(value_provider)
319
            .write_to_string()
320
            .into_owned()
321
    }
322
}
323

324
struct WriteablePattern<'a, B: PatternBackend, P> {
325
    store: &'a B::Store,
326
    value_provider: P,
327
}
328

329
impl<'a, B, P> TryWriteable for WriteablePattern<'a, B, P>
330
where
331
    B: PatternBackend,
332
    P: PlaceholderValueProvider<B::PlaceholderKey<'a>, Error = B::Error<'a>>,
333
{
334
    type Error = B::Error<'a>;
335

336
    fn try_write_to_parts<S: PartsWrite + ?Sized>(
9,693✔
337
        &self,
338
        sink: &mut S,
339
    ) -> Result<Result<(), Self::Error>, fmt::Error> {
340
        let mut error = None;
9,693✔
341
        let it = B::iter_items(self.store);
9,693✔
342
        #[cfg(debug_assertions)]
343
        let (size_hint, mut actual_len) = (it.size_hint(), 0);
9,693✔
344
        for item in it {
29,198✔
345
            match item {
19,577✔
346
                PatternItem::Literal(s) => {
10,209✔
347
                    sink.with_part(P::LITERAL_PART, |sink| sink.write_str(s))?;
20,299✔
348
                }
349
                PatternItem::Placeholder(key) => {
284✔
350
                    let (element_writeable, part) = self.value_provider.value_for(key);
9,432✔
351
                    sink.with_part(part, |sink| {
28,581✔
352
                        if let Err(e) = element_writeable.try_write_to_parts(sink)? {
9,443✔
353
                            // Keep the first error if there was one
354
                            error.get_or_insert(e);
×
355
                        }
9,443✔
356
                        Ok(())
9,443✔
357
                    })?;
9,443✔
358
                }
9,439✔
359
            }
360
            #[cfg(debug_assertions)]
361
            {
362
                actual_len += 1;
19,583✔
363
            }
364
        }
9,619✔
365
        #[cfg(debug_assertions)]
366
        {
367
            debug_assert!(actual_len >= size_hint.0);
9,619✔
368
            if let Some(max_len) = size_hint.1 {
9,619✔
369
                debug_assert!(actual_len <= max_len);
9,615✔
370
            }
371
        }
372
        if let Some(e) = error {
9,619✔
373
            Ok(Err(e))
×
374
        } else {
375
            Ok(Ok(()))
9,611✔
376
        }
377
    }
9,611✔
378
}
379

380
impl<'a, B, P> fmt::Display for WriteablePattern<'a, B, P>
381
where
382
    B: PatternBackend,
383
    P: PlaceholderValueProvider<B::PlaceholderKey<'a>, Error = B::Error<'a>>,
384
{
385
    #[inline]
386
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
387
        // Discard the TryWriteable error (lossy mode)
388
        self.try_write_to(f).map(|_| ())
389
    }
390
}
391

392
#[test]
393
fn test_try_from_str_inference() {
2✔
394
    use crate::SinglePlaceholder;
395
    let _: Box<Pattern<SinglePlaceholder>> =
396
        Pattern::try_from_str("{0} days", Default::default()).unwrap();
1✔
397
    let _ = Pattern::<SinglePlaceholder>::try_from_str("{0} days", Default::default()).unwrap();
1✔
398
}
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