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

zbraniecki / icu4x / 9014530096

08 May 2024 07:27PM UTC coverage: 76.402% (+0.2%) from 76.234%
9014530096

push

github

web-flow
Add missing std pointer-like impls for DataProvider, DynamicDataProvider (#4880)

0 of 3 new or added lines in 1 file covered. (0.0%)

3218 existing lines in 167 files now uncovered.

53328 of 69799 relevant lines covered (76.4%)

504343.42 hits per line

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

90.67
/utils/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
mod serde;
9

10
use core::{
11
    convert::Infallible,
12
    fmt::{self, Write},
13
    marker::PhantomData,
14
};
15

16
use writeable::{adapters::TryWriteableInfallibleAsWriteable, PartsWrite, TryWriteable, Writeable};
17

18
use crate::common::*;
19
use crate::Error;
20

21
#[cfg(feature = "alloc")]
22
use crate::{Parser, ParserOptions};
23
#[cfg(feature = "alloc")]
24
use alloc::{borrow::ToOwned, str::FromStr, string::String};
25

26
/// A string pattern with placeholders.
27
///
28
/// There are 2 generic parameters: `Backend` and `Store`.
29
///
30
/// # Backend
31
///
32
/// This determines the nature of placeholders and serialized encoding of the pattern.
33
///
34
/// The following backends are available:
35
///
36
/// - [`SinglePlaceholder`] for patterns with one placeholder: `"{0} days ago"`
37
///
38
/// # Store
39
///
40
/// The data structure has a flexible backing data store. The only requirement for most
41
/// functionality is that it implement `AsRef<str>` (backend-dependent).
42
///
43
/// Example stores:
44
///
45
/// - `&str` for a fully borrowed pattern
46
/// - `String` for a fully owned pattern
47
/// - `Cow<str>` for an owned-or-borrowed pattern
48
///
49
/// # Format to Parts
50
///
51
/// [`Pattern`] supports interpolating with [writeable::Part]s, annotations for whether the
52
/// substring was a placeholder or a literal.
53
///
54
/// By default, the substrings are annotated with [`PATTERN_LITERAL_PART`] and
55
/// [`PATTERN_PLACEHOLDER_PART`]. This can be customized with [`PlaceholderValueProvider`].
56
///
57
/// # Examples
58
///
59
/// Interpolating a [`SinglePlaceholder`] pattern with parts:
60
///
61
/// ```
62
/// use icu_pattern::Pattern;
63
/// use icu_pattern::SinglePlaceholder;
64
/// use writeable::assert_writeable_parts_eq;
65
///
66
/// let pattern =
67
///     Pattern::<SinglePlaceholder, _>::try_from_str("Hello, {0}!").unwrap();
68
///
69
/// assert_writeable_parts_eq!(
70
///     pattern.interpolate(["Alice"]),
71
///     "Hello, Alice!",
72
///     [
73
///         (0, 7, icu_pattern::PATTERN_LITERAL_PART),
74
///         (7, 12, icu_pattern::PATTERN_PLACEHOLDER_PART),
75
///         (12, 13, icu_pattern::PATTERN_LITERAL_PART),
76
///     ]
77
/// );
78
/// ```
79
///
80
/// [`SinglePlaceholder`]: crate::SinglePlaceholder
81
#[derive(Debug, Clone, PartialEq)]
146✔
82
#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
83
#[cfg_attr(
84
    feature = "zerofrom",
UNCOV
85
    derive(zerofrom::ZeroFrom),
×
86
    zerofrom(may_borrow(Store))
87
)]
88
pub struct Pattern<Backend, Store: ?Sized> {
89
    _backend: PhantomData<Backend>,
73✔
90
    store: Store,
73✔
91
}
92

93
impl<Backend, Store> Pattern<Backend, Store> {
94
    pub fn take_store(self) -> Store {
70✔
95
        self.store
69✔
96
    }
70✔
97

98
    /// Creates a pattern from a serialized backing store without checking invariants.
99
    /// Most users should prefer [`Pattern::try_from_store()`].
100
    ///
101
    /// The store is expected to come from a valid `Pattern` with this `Backend`,
102
    /// such as by calling [`Pattern::take_store()`]. If the store is not valid,
103
    /// unexpected behavior may occur.
104
    ///
105
    /// To parse a pattern string, use [`Self::try_from_str()`].
106
    ///
107
    /// # Examples
108
    ///
109
    /// ```
110
    /// use icu_pattern::Pattern;
111
    /// use icu_pattern::SinglePlaceholder;
112
    /// use writeable::assert_writeable_eq;
113
    ///
114
    /// // Create a pattern from a valid string:
115
    /// let allocated_pattern =
116
    ///     Pattern::<SinglePlaceholder, String>::try_from_str("{0} days")
117
    ///         .expect("valid pattern");
118
    ///
119
    /// // Transform the store and create a new Pattern. This is valid because
120
    /// // we call `.take_store()` and `.from_store_unchecked()` on patterns
121
    /// // with the same backend (`SinglePlaceholder`).
122
    /// let store = allocated_pattern.take_store();
123
    /// let borrowed_pattern: Pattern<SinglePlaceholder, &str> =
124
    ///     Pattern::from_store_unchecked(&store);
125
    ///
126
    /// assert_writeable_eq!(borrowed_pattern.interpolate([5]), "5 days");
127
    /// ```
128
    pub const fn from_store_unchecked(store: Store) -> Self {
39✔
129
        Self {
39✔
130
            _backend: PhantomData,
131
            store,
38✔
132
        }
133
    }
39✔
134
}
135

136
impl<B, Store> Pattern<B, Store>
137
where
138
    B: PatternBackend,
139
    Store: AsRef<B::Store>,
140
{
141
    /// Creates a pattern from a serialized backing store.
142
    ///
143
    /// To parse a pattern string, use [`Self::try_from_str()`].
144
    ///
145
    /// # Examples
146
    ///
147
    /// ```
148
    /// use icu_pattern::Pattern;
149
    /// use icu_pattern::SinglePlaceholder;
150
    ///
151
    /// // Create a pattern from a valid store:
152
    /// Pattern::<SinglePlaceholder, _>::try_from_store("\x01 days")
153
    ///     .expect("valid pattern");
154
    ///
155
    /// // Error on an invalid pattern:
156
    /// Pattern::<SinglePlaceholder, _>::try_from_store("\x09 days")
157
    ///     .expect_err("9 is out of bounds");
158
    /// ```
159
    pub fn try_from_store(store: Store) -> Result<Self, Error> {
37✔
160
        B::validate_store(store.as_ref())?;
37✔
161
        Ok(Self {
27✔
162
            _backend: PhantomData,
163
            store,
27✔
164
        })
165
    }
37✔
166
}
167

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

215
#[cfg(feature = "alloc")]
216
impl<'a, B> Pattern<B, <B::Store as ToOwned>::Owned>
217
where
218
    B: PatternBackend,
219
    B::PlaceholderKeyCow<'a>: FromStr,
220
    B::Store: ToOwned,
221
    <B::PlaceholderKeyCow<'a> as FromStr>::Err: fmt::Debug,
222
{
223
    /// Creates a pattern by parsing a syntax string.
224
    ///
225
    /// To construct from a serialized pattern string, use [`Self::try_from_store()`].
226
    ///
227
    /// ✨ *Enabled with the `alloc` Cargo feature.*
228
    ///
229
    /// # Examples
230
    ///
231
    /// ```
232
    /// use icu_pattern::Pattern;
233
    /// use icu_pattern::SinglePlaceholder;
234
    ///
235
    /// // Create a pattern from a valid string:
236
    /// Pattern::<SinglePlaceholder, _>::try_from_str("{0} days")
237
    ///     .expect("valid pattern");
238
    ///
239
    /// // Error on an invalid pattern:
240
    /// Pattern::<SinglePlaceholder, _>::try_from_str("{0 days")
241
    ///     .expect_err("mismatched braces");
242
    /// ```
243
    pub fn try_from_str(pattern: &str) -> Result<Self, Error> {
162✔
244
        let parser = Parser::new(
162✔
245
            pattern,
162✔
246
            ParserOptions {
162✔
247
                allow_raw_letters: true,
248
            },
249
        );
250
        let store = B::try_from_items(parser)?;
162✔
251
        #[cfg(debug_assertions)]
252
        match B::validate_store(core::borrow::Borrow::borrow(&store)) {
158✔
253
            Ok(()) => (),
UNCOV
254
            Err(e) => {
×
UNCOV
255
                debug_assert!(false, "{:?} for pattern {:?}", e, pattern);
×
256
            }
257
        };
258
        Ok(Self {
156✔
259
            _backend: PhantomData,
260
            store,
156✔
261
        })
262
    }
160✔
263
}
264

265
#[cfg(feature = "alloc")]
266
impl<'a, B> FromStr for Pattern<B, <B::Store as ToOwned>::Owned>
267
where
268
    B: PatternBackend,
269
    B::PlaceholderKeyCow<'a>: FromStr,
270
    B::Store: ToOwned,
271
    <B::PlaceholderKeyCow<'a> as FromStr>::Err: fmt::Debug,
272
{
273
    type Err = Error;
274
    fn from_str(pattern: &str) -> Result<Self, Self::Err> {
105✔
275
        Self::try_from_str(pattern)
105✔
276
    }
105✔
277
}
278

279
impl<B, Store> Pattern<B, Store>
280
where
281
    B: PatternBackend,
282
    Store: AsRef<B::Store> + ?Sized,
283
{
284
    /// Returns an iterator over the [`PatternItem`]s in this pattern.
285
    pub fn iter(&self) -> impl Iterator<Item = PatternItem<B::PlaceholderKey<'_>>> + '_ {
20✔
286
        B::iter_items(self.store.as_ref())
20✔
287
    }
20✔
288

289
    /// Returns a [`TryWriteable`] that interpolates items from the given replacement provider
290
    /// into this pattern string.
291
    pub fn try_interpolate<'a, P>(
292
        &'a self,
293
        value_provider: P,
294
    ) -> impl TryWriteable<Error = B::Error<'a>> + fmt::Display + 'a
295
    where
296
        P: PlaceholderValueProvider<B::PlaceholderKey<'a>, Error = B::Error<'a>> + 'a,
297
    {
298
        WriteablePattern::<B, P> {
299
            store: self.store.as_ref(),
300
            value_provider,
301
        }
302
    }
303

304
    #[cfg(feature = "alloc")]
305
    /// Interpolates the pattern directly to a string, returning the string or an error.
306
    ///
307
    /// In addition to the error, the lossy fallback string is returned in the failure case.
308
    ///
309
    /// ✨ *Enabled with the `alloc` Cargo feature.*
310
    pub fn try_interpolate_to_string<'a, P>(
311
        &'a self,
312
        value_provider: P,
313
    ) -> Result<String, (B::Error<'a>, String)>
314
    where
315
        P: PlaceholderValueProvider<B::PlaceholderKey<'a>, Error = B::Error<'a>> + 'a,
316
    {
317
        self.try_interpolate(value_provider)
318
            .try_write_to_string()
319
            .map(|s| s.into_owned())
320
            .map_err(|(e, s)| (e, s.into_owned()))
321
    }
322
}
323

324
impl<B, Store> Pattern<B, Store>
325
where
326
    for<'b> B: PatternBackend<Error<'b> = Infallible>,
327
    Store: AsRef<B::Store> + ?Sized,
328
{
329
    /// Returns a [`Writeable`] that interpolates items from the given replacement provider
330
    /// into this pattern string.
331
    pub fn interpolate<'a, P>(&'a self, value_provider: P) -> impl Writeable + fmt::Display + 'a
38✔
332
    where
333
        P: PlaceholderValueProvider<B::PlaceholderKey<'a>, Error = B::Error<'a>> + 'a,
334
    {
335
        TryWriteableInfallibleAsWriteable(WriteablePattern::<B, P> {
38✔
336
            store: self.store.as_ref(),
38✔
337
            value_provider,
29✔
338
        })
339
    }
38✔
340

341
    #[cfg(feature = "alloc")]
342
    /// Interpolates the pattern directly to a string.
343
    ///
344
    /// ✨ *Enabled with the `alloc` Cargo feature.*
345
    pub fn interpolate_to_string<'a, P>(&'a self, value_provider: P) -> String
346
    where
347
        P: PlaceholderValueProvider<B::PlaceholderKey<'a>, Error = B::Error<'a>> + 'a,
348
    {
349
        self.interpolate(value_provider)
350
            .write_to_string()
351
            .into_owned()
352
    }
353
}
354

355
struct WriteablePattern<'a, B: PatternBackend, P> {
356
    store: &'a B::Store,
357
    value_provider: P,
358
}
359

360
impl<'a, B, P> TryWriteable for WriteablePattern<'a, B, P>
361
where
362
    B: PatternBackend,
363
    P: PlaceholderValueProvider<B::PlaceholderKey<'a>, Error = B::Error<'a>>,
364
{
365
    type Error = B::Error<'a>;
366

367
    fn try_write_to_parts<S: PartsWrite + ?Sized>(
40✔
368
        &self,
369
        sink: &mut S,
370
    ) -> Result<Result<(), Self::Error>, fmt::Error> {
371
        let mut error = None;
40✔
372
        let it = B::iter_items(self.store);
40✔
373
        #[cfg(debug_assertions)]
374
        let (size_hint, mut actual_len) = (it.size_hint(), 0);
40✔
375
        for item in it {
149✔
376
            match item {
109✔
377
                PatternItem::Literal(s) => {
48✔
378
                    sink.with_part(P::LITERAL_PART, |sink| sink.write_str(s))?;
96✔
379
                }
380
                PatternItem::Placeholder(key) => {
52✔
381
                    let (element_writeable, part) = self.value_provider.value_for(key);
61✔
382
                    sink.with_part(part, |sink| {
122✔
383
                        if let Err(e) = element_writeable.try_write_to_parts(sink)? {
61✔
384
                            // Keep the first error if there was one
UNCOV
385
                            error.get_or_insert(e);
×
386
                        }
61✔
387
                        Ok(())
61✔
388
                    })?;
61✔
389
                }
61✔
390
            }
391
            #[cfg(debug_assertions)]
392
            {
393
                actual_len += 1;
109✔
394
            }
395
        }
396
        #[cfg(debug_assertions)]
397
        {
398
            debug_assert!(actual_len >= size_hint.0);
40✔
399
            if let Some(max_len) = size_hint.1 {
40✔
400
                debug_assert!(actual_len <= max_len);
40✔
401
            }
402
        }
403
        if let Some(e) = error {
40✔
UNCOV
404
            Ok(Err(e))
×
405
        } else {
406
            Ok(Ok(()))
40✔
407
        }
408
    }
40✔
409
}
410

411
impl<'a, B, P> fmt::Display for WriteablePattern<'a, B, P>
412
where
413
    B: PatternBackend,
414
    P: PlaceholderValueProvider<B::PlaceholderKey<'a>, Error = B::Error<'a>>,
415
{
416
    #[inline]
417
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
418
        // Discard the TryWriteable error (lossy mode)
419
        self.try_write_to(f).map(|_| ())
420
    }
421
}
422

423
#[test]
424
fn test_try_from_str_inference() {
2✔
425
    use crate::SinglePlaceholder;
426
    let _: Pattern<SinglePlaceholder, String> = Pattern::try_from_str("{0} days").unwrap();
1✔
427
    let _ = Pattern::<SinglePlaceholder, String>::try_from_str("{0} days").unwrap();
1✔
428
}
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

© 2025 Coveralls, Inc