• 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

86.73
/components/pattern/src/single.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
//! Code for the [`SinglePlaceholder`] pattern backend.
6

7
use core::convert::Infallible;
8
use core::{cmp::Ordering, str::FromStr};
9
use writeable::adapters::WriteableAsTryWriteableInfallible;
10
use writeable::Writeable;
11

12
use crate::common::*;
13
use crate::Error;
14

15
#[cfg(feature = "alloc")]
16
use alloc::{boxed::Box, string::String};
17

18
/// A singleton enum for the [`SinglePlaceholder`] pattern backend.
19
///
20
/// # Examples
21
///
22
/// ```
23
/// use core::cmp::Ordering;
24
/// use core::str::FromStr;
25
/// use icu_pattern::PatternItem;
26
/// use icu_pattern::SinglePlaceholder;
27
/// use icu_pattern::SinglePlaceholderKey;
28
/// use icu_pattern::SinglePlaceholderPattern;
29
///
30
/// // Parse the string syntax and check the resulting data store:
31
/// let pattern = SinglePlaceholderPattern::try_from_str(
32
///     "Hello, {0}!",
33
///     Default::default(),
34
/// )
35
/// .unwrap();
36
///
37
/// assert_eq!(
38
///     pattern.iter().cmp(
39
///         [
40
///             PatternItem::Literal("Hello, "),
41
///             PatternItem::Placeholder(SinglePlaceholderKey::Singleton),
42
///             PatternItem::Literal("!")
43
///         ]
44
///         .into_iter()
45
///     ),
46
///     Ordering::Equal
47
/// );
48
/// ```
49
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
1✔
50
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
5✔
51
#[allow(clippy::exhaustive_enums)] // Singleton
52
pub enum SinglePlaceholderKey {
53
    Singleton,
1✔
54
}
55

56
impl FromStr for SinglePlaceholderKey {
57
    type Err = core::convert::Infallible;
58
    fn from_str(_: &str) -> Result<Self, Self::Err> {
26,213✔
59
        Ok(Self::Singleton)
60
    }
26,213✔
61
}
62

63
impl<W> PlaceholderValueProvider<SinglePlaceholderKey> for (W,)
64
where
65
    W: Writeable,
66
{
67
    type Error = Infallible;
68

69
    type W<'a>
70
        = WriteableAsTryWriteableInfallible<&'a W>
71
    where
72
        Self: 'a;
73

74
    type L<'a, 'l>
75
        = &'l str
76
    where
77
        Self: 'a;
78

79
    fn value_for(&self, _key: SinglePlaceholderKey) -> Self::W<'_> {
1,241✔
80
        WriteableAsTryWriteableInfallible(&self.0)
81
    }
1,241✔
82
    #[inline]
83
    fn map_literal<'a, 'l>(&'a self, literal: &'l str) -> Self::L<'a, 'l> {
2,506✔
84
        literal
85
    }
2,506✔
86
}
87

88
impl<W> PlaceholderValueProvider<SinglePlaceholderKey> for [W; 1]
89
where
90
    W: Writeable,
91
{
92
    type Error = Infallible;
93

94
    type W<'a>
95
        = WriteableAsTryWriteableInfallible<&'a W>
96
    where
97
        Self: 'a;
98

99
    type L<'a, 'l>
100
        = &'l str
101
    where
102
        Self: 'a;
103

104
    fn value_for(&self, _key: SinglePlaceholderKey) -> Self::W<'_> {
9,581✔
105
        let [value] = self;
9,581✔
106
        WriteableAsTryWriteableInfallible(value)
107
    }
9,581✔
108
    #[inline]
109
    fn map_literal<'a, 'l>(&'a self, literal: &'l str) -> Self::L<'a, 'l> {
9,518✔
110
        literal
111
    }
9,518✔
112
}
113

114
/// Backend for patterns containing zero or one placeholder.
115
///
116
/// This empty type is not constructible.
117
///
118
/// # Placeholder Keys
119
///
120
/// The placeholder is always [`SinglePlaceholderKey::Singleton`].
121
///
122
/// In [`Pattern::interpolate()`], pass a single-element array or tuple.
123
///
124
/// # Encoding Details
125
///
126
/// The first code point of the string is 1 plus the byte offset of the placeholder counting from
127
/// after that initial code point. If zero, there is no placeholder.
128
///
129
/// # Examples
130
///
131
/// Parsing a pattern into the encoding:
132
///
133
/// ```
134
/// use core::str::FromStr;
135
/// use icu_pattern::Pattern;
136
/// use icu_pattern::SinglePlaceholder;
137
///
138
/// // Parse the string syntax and check the resulting data store:
139
/// let pattern = Pattern::<SinglePlaceholder>::try_from_str(
140
///     "Hello, {0}!",
141
///     Default::default(),
142
/// )
143
/// .unwrap();
144
///
145
/// assert_eq!("\u{8}Hello, !", &pattern.store);
146
/// ```
147
///
148
/// Example patterns supported by this backend:
149
///
150
/// ```
151
/// use core::str::FromStr;
152
/// use icu_pattern::Pattern;
153
/// use icu_pattern::QuoteMode;
154
/// use icu_pattern::SinglePlaceholder;
155
///
156
/// // Single numeric placeholder:
157
/// assert_eq!(
158
///     Pattern::<SinglePlaceholder>::try_from_str(
159
///         "{0} days ago",
160
///         Default::default()
161
///     )
162
///     .unwrap()
163
///     .interpolate_to_string([5]),
164
///     "5 days ago",
165
/// );
166
///
167
/// // Single named placeholder:
168
/// assert_eq!(
169
///     Pattern::<SinglePlaceholder>::try_from_str(
170
///         "{name}",
171
///         Default::default()
172
///     )
173
///     .unwrap()
174
///     .interpolate_to_string(["Alice"]),
175
///     "Alice",
176
/// );
177
///
178
/// // No placeholder (note, the placeholder value is never accessed):
179
/// assert_eq!(
180
///     Pattern::<SinglePlaceholder>::try_from_str(
181
///         "yesterday",
182
///         Default::default()
183
///     )
184
///     .unwrap()
185
///     .interpolate_to_string(["hi"]),
186
///     "yesterday",
187
/// );
188
///
189
/// // Escaped placeholder and a real placeholder:
190
/// assert_eq!(
191
///     Pattern::<SinglePlaceholder>::try_from_str(
192
///         "'{0}' {1}",
193
///         QuoteMode::QuotingSupported.into()
194
///     )
195
///     .unwrap()
196
///     .interpolate_to_string(("hi",)),
197
///     "{0} hi",
198
/// );
199
/// ```
200
///
201
/// [`Pattern::interpolate()`]: crate::Pattern::interpolate
202
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
×
203
#[allow(clippy::exhaustive_enums)] // Empty Enum
204
pub enum SinglePlaceholder {}
205

206
impl crate::private::Sealed for SinglePlaceholder {}
207

208
impl PatternBackend for SinglePlaceholder {
209
    type PlaceholderKey<'a> = SinglePlaceholderKey;
210
    #[cfg(feature = "alloc")]
211
    type PlaceholderKeyCow<'a> = SinglePlaceholderKey;
212
    type Error<'a> = Infallible;
213
    type Store = str;
214
    type Iter<'a> = SinglePlaceholderPatternIterator<'a>;
215

216
    fn validate_store(store: &Self::Store) -> Result<(), Error> {
29,657✔
217
        let placeholder_offset_char = store.chars().next().ok_or(Error::InvalidPattern)?;
29,657✔
218
        let initial_offset = placeholder_offset_char.len_utf8();
29,657✔
219
        let placeholder_offset = placeholder_offset_char as usize;
29,657✔
220
        if placeholder_offset > store.len() - initial_offset + 1 {
29,657✔
221
            return Err(Error::InvalidPattern);
×
222
        }
223
        if placeholder_offset >= 0xD800 {
29,657✔
224
            return Err(Error::InvalidPattern);
×
225
        }
226
        Ok(())
29,657✔
227
    }
29,657✔
228

229
    fn iter_items(store: &Self::Store) -> Self::Iter<'_> {
11,332✔
230
        let placeholder_offset_char = match store.chars().next() {
11,332✔
231
            Some(i) => i,
11,332✔
232
            None => {
233
                debug_assert!(false);
×
234
                '\0'
235
            }
236
        };
237
        let initial_offset = placeholder_offset_char.len_utf8();
11,332✔
238
        SinglePlaceholderPatternIterator {
11,332✔
239
            store,
240
            placeholder_offset: placeholder_offset_char as usize + initial_offset - 1,
11,332✔
241
            current_offset: initial_offset,
242
        }
243
    }
11,332✔
244

245
    #[cfg(feature = "alloc")]
246
    fn try_from_items<
85,775✔
247
        'cow,
248
        'ph,
249
        I: Iterator<Item = Result<PatternItemCow<'cow, Self::PlaceholderKey<'ph>>, Error>>,
250
    >(
251
        items: I,
252
    ) -> Result<Box<str>, Error> {
253
        let mut result = String::new();
85,775✔
254
        let mut seen_placeholder = false;
85,779✔
255
        for item in items {
111,976✔
256
            match item? {
55,646✔
257
                PatternItemCow::Literal(s) => result.push_str(&s),
29,444✔
258
                PatternItemCow::Placeholder(_) if !seen_placeholder => {
26,204✔
259
                    seen_placeholder = true;
26,206✔
260
                    let placeholder_offset =
261
                        u32::try_from(result.len() + 1).map_err(|_| Error::InvalidPattern)?;
26,204✔
262
                    if placeholder_offset >= 0xD800 {
26,204✔
263
                        return Err(Error::InvalidPattern);
×
264
                    }
265
                    let placeholder_offset_char =
266
                        char::try_from(placeholder_offset).map_err(|_| Error::InvalidPattern)?;
26,204✔
267
                    result.insert(0, placeholder_offset_char);
26,204✔
268
                }
269
                PatternItemCow::Placeholder(_) => {
270
                    return Err(Error::InvalidPattern);
×
271
                }
272
            }
273
        }
26,881✔
274
        if !seen_placeholder {
26,881✔
275
            result.insert(0, '\0');
680✔
276
        }
277
        Ok(result.into_boxed_str())
26,881✔
278
    }
26,885✔
279

280
    fn empty() -> &'static Self::Store {
943✔
281
        "\0"
282
    }
943✔
283
}
284

285
#[doc(hidden)] // TODO(#4467): Should be internal
286
#[derive(Debug)]
×
287
pub struct SinglePlaceholderPatternIterator<'a> {
288
    store: &'a str,
289
    placeholder_offset: usize,
×
290
    current_offset: usize,
×
291
}
292

293
// Note: This impl is not exported via public bounds, but it might be in the
294
// future, and the compiler might be able to find it. The code is also
295
// reachable from `Iterator::size_hint`.
296
impl ExactSizeIterator for SinglePlaceholderPatternIterator<'_> {
297
    fn len(&self) -> usize {
11,300✔
298
        let placeholder_offset_char = match self.store.chars().next() {
11,300✔
299
            Some(i) => i,
11,300✔
300
            None => {
301
                debug_assert!(false);
×
302
                '\0'
303
            }
304
        };
305
        let initial_offset = placeholder_offset_char.len_utf8();
11,300✔
306
        let placeholder_offset = placeholder_offset_char as usize + initial_offset - 1;
11,300✔
307
        let store_len = self.store.len();
11,300✔
308
        if placeholder_offset < initial_offset {
11,300✔
309
            // No placeholder
310
            if initial_offset < store_len {
465✔
311
                // No placeholder, non-empty literal
312
                1
465✔
313
            } else {
314
                // No placeholder, empty literal
315
                0
×
316
            }
317
        } else if placeholder_offset == initial_offset {
10,835✔
318
            // Has placeholder, empty prefix
319
            if initial_offset < store_len {
7,223✔
320
                // Has placeholder, empty prefix, non-empty suffix
321
                2
6,731✔
322
            } else {
323
                // Has placeholder, empty prefix, empty suffix
324
                1
492✔
325
            }
326
        } else if placeholder_offset < store_len {
3,612✔
327
            // Has placeholder, non-empty prefix, non-empty suffix
328
            3
1,241✔
329
        } else {
330
            // Has placeholder, non-empty prefix, empty suffix
331
            2
2,371✔
332
        }
333
    }
11,300✔
334
}
335

336
impl<'a> Iterator for SinglePlaceholderPatternIterator<'a> {
337
    type Item = PatternItem<'a, SinglePlaceholderKey>;
338
    fn next(&mut self) -> Option<Self::Item> {
34,180✔
339
        match self.current_offset.cmp(&self.placeholder_offset) {
34,180✔
340
            Ordering::Less => {
341
                // Prefix
342
                let literal_str = match self.store.get(self.current_offset..self.placeholder_offset)
3,620✔
343
                {
344
                    Some(s) => s,
3,620✔
345
                    None => {
346
                        debug_assert!(false, "offsets are in range");
×
347
                        ""
348
                    }
349
                };
350
                self.current_offset = self.placeholder_offset;
3,620✔
351
                Some(PatternItem::Literal(literal_str))
3,620✔
352
            }
3,620✔
353
            Ordering::Equal => {
354
                // Placeholder
355
                self.placeholder_offset = 0;
10,832✔
356
                Some(PatternItem::Placeholder(SinglePlaceholderKey::Singleton))
10,832✔
357
            }
358
            Ordering::Greater => {
359
                // Suffix or end of string
360
                let literal_str = match self.store.get(self.current_offset..) {
19,728✔
361
                    Some(s) => s,
19,728✔
362
                    None => {
363
                        debug_assert!(false, "offsets are in range");
×
364
                        ""
365
                    }
366
                };
367
                if literal_str.is_empty() {
19,728✔
368
                    // End of string
369
                    None
11,299✔
370
                } else {
371
                    // Suffix
372
                    self.current_offset = self.store.len();
8,429✔
373
                    Some(PatternItem::Literal(literal_str))
8,429✔
374
                }
375
            }
376
        }
377
    }
34,180✔
378

379
    fn size_hint(&self) -> (usize, Option<usize>) {
11,325✔
380
        let len = self.len();
11,325✔
381
        (len, Some(len))
11,325✔
382
    }
11,325✔
383
}
384

385
// TODO(#1668):  Add more tests
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