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

zbraniecki / icu4x / 9357137046

03 Jun 2024 08:51PM UTC coverage: 75.121% (-1.1%) from 76.254%
9357137046

push

github

web-flow
Switch locid Value to use Subtag (#4941)

This is part of #1833 switching Value API to use Subtag.

61 of 71 new or added lines in 11 files covered. (85.92%)

3224 existing lines in 178 files now uncovered.

52958 of 70497 relevant lines covered (75.12%)

572757.08 hits per line

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

80.21
/utils/pattern/src/single.rs
1
// This file is part of ICU4X. For terms of use, please see the file
1✔
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::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::from_str("Hello, {0}!").unwrap();
32
///
33
/// assert_eq!(
34
///     pattern.iter().cmp(
35
///         [
36
///             PatternItem::Literal("Hello, "),
37
///             PatternItem::Placeholder(SinglePlaceholderKey::Singleton),
38
///             PatternItem::Literal("!")
39
///         ]
40
///         .into_iter()
41
///     ),
42
///     Ordering::Equal
43
/// );
44
/// ```
45
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
1✔
46
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
20✔
47
#[allow(clippy::exhaustive_enums)] // Singleton
48
pub enum SinglePlaceholderKey {
49
    Singleton,
50
}
51

52
impl FromStr for SinglePlaceholderKey {
53
    type Err = core::convert::Infallible;
54
    fn from_str(_: &str) -> Result<Self, Self::Err> {
73✔
55
        Ok(Self::Singleton)
56
    }
73✔
57
}
58

59
impl<W> PlaceholderValueProvider<SinglePlaceholderKey> for (W,)
60
where
61
    W: Writeable,
62
{
63
    type Error = Infallible;
64
    type W<'a> = WriteableAsTryWriteableInfallible<&'a W> where W: 'a;
65
    const LITERAL_PART: writeable::Part = crate::PATTERN_LITERAL_PART;
66
    fn value_for(&self, _key: SinglePlaceholderKey) -> (Self::W<'_>, writeable::Part) {
9✔
67
        (
9✔
68
            WriteableAsTryWriteableInfallible(&self.0),
9✔
69
            crate::PATTERN_PLACEHOLDER_PART,
70
        )
71
    }
9✔
72
}
73

74
impl<W> PlaceholderValueProvider<SinglePlaceholderKey> for [W; 1]
75
where
76
    W: Writeable,
77
{
78
    type Error = Infallible;
79
    type W<'a> = WriteableAsTryWriteableInfallible<&'a W> where W: 'a;
80
    const LITERAL_PART: writeable::Part = crate::PATTERN_LITERAL_PART;
81
    fn value_for(&self, _key: SinglePlaceholderKey) -> (Self::W<'_>, writeable::Part) {
82
        let [value] = self;
83
        (
84
            WriteableAsTryWriteableInfallible(value),
85
            crate::PATTERN_PLACEHOLDER_PART,
86
        )
87
    }
88
}
89

90
/// Backend for patterns containing zero or one placeholder.
91
///
92
/// This empty type is not constructible.
93
///
94
/// # Placeholder Keys
95
///
96
/// The placeholder is always [`SinglePlaceholderKey::Singleton`].
97
///
98
/// In [`Pattern::interpolate()`], pass a single-element array or tuple.
99
///
100
/// # Encoding Details
101
///
102
/// The first code point of the string is 1 plus the byte offset of the placeholder counting from
103
/// after that initial code point. If zero, there is no placeholder.
104
///
105
/// # Examples
106
///
107
/// Parsing a pattern into the encoding:
108
///
109
/// ```
110
/// use core::str::FromStr;
111
/// use icu_pattern::Pattern;
112
/// use icu_pattern::SinglePlaceholder;
113
///
114
/// // Parse the string syntax and check the resulting data store:
115
/// let store = Pattern::<SinglePlaceholder, _>::from_str("Hello, {0}!")
116
///     .unwrap()
117
///     .take_store();
118
///
119
/// assert_eq!("\u{8}Hello, !", store);
120
/// ```
121
///
122
/// Example patterns supported by this backend:
123
///
124
/// ```
125
/// use core::str::FromStr;
126
/// use icu_pattern::Pattern;
127
/// use icu_pattern::SinglePlaceholder;
128
///
129
/// // Single numeric placeholder:
130
/// assert_eq!(
131
///     Pattern::<SinglePlaceholder, _>::from_str("{0} days ago")
132
///         .unwrap()
133
///         .interpolate_to_string([5]),
134
///     "5 days ago",
135
/// );
136
///
137
/// // Single named placeholder:
138
/// assert_eq!(
139
///     Pattern::<SinglePlaceholder, _>::from_str("{name}")
140
///         .unwrap()
141
///         .interpolate_to_string(["Alice"]),
142
///     "Alice",
143
/// );
144
///
145
/// // No placeholder (note, the placeholder value is never accessed):
146
/// assert_eq!(
147
///     Pattern::<SinglePlaceholder, _>::from_str("yesterday")
148
///         .unwrap()
149
///         .interpolate_to_string(["hi"]),
150
///     "yesterday",
151
/// );
152
///
153
/// // Escaped placeholder and a real placeholder:
154
/// assert_eq!(
155
///     Pattern::<SinglePlaceholder, _>::from_str("'{0}' {1}")
156
///         .unwrap()
157
///         .interpolate_to_string(("hi",)),
158
///     "{0} hi",
159
/// );
160
/// ```
161
///
162
/// [`Pattern::interpolate()`]: crate::Pattern::interpolate
163
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
×
164
#[allow(clippy::exhaustive_enums)] // Empty Enum
165
pub enum SinglePlaceholder {}
166

167
impl crate::private::Sealed for SinglePlaceholder {}
168

169
impl PatternBackend for SinglePlaceholder {
170
    type PlaceholderKey<'a> = SinglePlaceholderKey;
171
    #[cfg(feature = "alloc")]
172
    type PlaceholderKeyCow<'a> = SinglePlaceholderKey;
173
    type Error<'a> = Infallible;
174
    type Store = str;
175
    type Iter<'a> = SinglePlaceholderPatternIterator<'a>;
176

177
    fn validate_store(store: &Self::Store) -> Result<(), Error> {
88✔
178
        let placeholder_offset_char = store.chars().next().ok_or(Error::InvalidPattern)?;
88✔
179
        let initial_offset = placeholder_offset_char.len_utf8();
88✔
180
        let placeholder_offset = placeholder_offset_char as usize;
88✔
181
        if placeholder_offset > store.len() - initial_offset + 1 {
88✔
182
            return Err(Error::InvalidPattern);
1✔
183
        }
184
        if placeholder_offset >= 0xD800 {
87✔
185
            return Err(Error::InvalidPattern);
×
186
        }
187
        Ok(())
87✔
188
    }
88✔
189

190
    fn iter_items(store: &Self::Store) -> Self::Iter<'_> {
43✔
191
        let placeholder_offset_char = match store.chars().next() {
43✔
192
            Some(i) => i,
43✔
193
            None => {
194
                debug_assert!(false);
×
UNCOV
195
                '\0'
×
196
            }
197
        };
198
        let initial_offset = placeholder_offset_char.len_utf8();
43✔
199
        SinglePlaceholderPatternIterator {
43✔
200
            store,
201
            placeholder_offset: placeholder_offset_char as usize + initial_offset - 1,
43✔
202
            current_offset: initial_offset,
203
        }
204
    }
43✔
205

206
    #[cfg(feature = "alloc")]
207
    fn try_from_items<
65✔
208
        'cow,
209
        'ph,
210
        I: Iterator<Item = Result<PatternItemCow<'cow, Self::PlaceholderKey<'ph>>, Error>>,
211
    >(
212
        items: I,
213
    ) -> Result<String, Error> {
214
        let mut result = String::new();
357✔
215
        let mut seen_placeholder = false;
211✔
216
        for item in items {
276✔
217
            match item? {
134✔
218
                PatternItemCow::Literal(s) => result.push_str(&s),
71✔
219
                PatternItemCow::Placeholder(_) if !seen_placeholder => {
65✔
220
                    seen_placeholder = true;
65✔
221
                    let placeholder_offset =
222
                        u32::try_from(result.len() + 1).map_err(|_| Error::InvalidPattern)?;
65✔
223
                    if placeholder_offset >= 0xD800 {
65✔
224
                        return Err(Error::InvalidPattern);
×
225
                    }
226
                    let placeholder_offset_char =
227
                        char::try_from(placeholder_offset).map_err(|_| Error::InvalidPattern)?;
65✔
228
                    result.insert(0, placeholder_offset_char);
65✔
229
                }
230
                PatternItemCow::Placeholder(_) => {
231
                    return Err(Error::InvalidPattern);
×
232
                }
233
            }
234
        }
235
        if !seen_placeholder {
65✔
236
            result.insert(0, '\0');
×
237
        }
238
        Ok(result)
65✔
239
    }
65✔
240
}
241

242
#[doc(hidden)] // TODO(#4467): Should be internal
243
#[derive(Debug)]
×
244
pub struct SinglePlaceholderPatternIterator<'a> {
245
    store: &'a str,
246
    placeholder_offset: usize,
×
247
    current_offset: usize,
×
248
}
249

250
// Note: This impl is not exported via public bounds, but it might be in the
251
// future, and the compiler might be able to find it. The code is also
252
// reachable from `Iterator::size_hint`.
253
impl ExactSizeIterator for SinglePlaceholderPatternIterator<'_> {
254
    fn len(&self) -> usize {
42✔
255
        let placeholder_offset_char = match self.store.chars().next() {
42✔
256
            Some(i) => i,
42✔
257
            None => {
258
                debug_assert!(false);
×
UNCOV
259
                '\0'
×
260
            }
261
        };
262
        let initial_offset = placeholder_offset_char.len_utf8();
42✔
263
        let placeholder_offset = placeholder_offset_char as usize + initial_offset - 1;
42✔
264
        let store_len = self.store.len();
42✔
265
        if placeholder_offset < initial_offset {
42✔
266
            // No placeholder
267
            if initial_offset < store_len {
1✔
268
                // No placeholder, non-empty literal
269
                1
1✔
270
            } else {
271
                // No placeholder, empty literal
272
                0
×
273
            }
274
        } else if placeholder_offset == initial_offset {
41✔
275
            // Has placeholder, empty prefix
276
            if initial_offset < store_len {
17✔
277
                // Has placeholder, empty prefix, non-empty suffix
278
                2
16✔
279
            } else {
280
                // Has placeholder, empty prefix, empty suffix
281
                1
1✔
282
            }
283
        } else if placeholder_offset < store_len {
24✔
284
            // Has placeholder, non-empty prefix, non-empty suffix
285
            3
23✔
286
        } else {
287
            // Has placeholder, non-empty prefix, empty suffix
288
            2
1✔
289
        }
290
    }
42✔
291
}
292

293
impl<'a> Iterator for SinglePlaceholderPatternIterator<'a> {
294
    type Item = PatternItem<'a, SinglePlaceholderKey>;
295
    fn next(&mut self) -> Option<Self::Item> {
150✔
296
        match self.current_offset.cmp(&self.placeholder_offset) {
150✔
297
            Ordering::Less => {
298
                // Prefix
299
                let literal_str = match self.store.get(self.current_offset..self.placeholder_offset)
25✔
300
                {
301
                    Some(s) => s,
25✔
302
                    None => {
303
                        debug_assert!(false, "offsets are in range");
×
UNCOV
304
                        ""
×
UNCOV
305
                    }
×
306
                };
307
                self.current_offset = self.placeholder_offset;
25✔
308
                Some(PatternItem::Literal(literal_str))
25✔
309
            }
25✔
310
            Ordering::Equal => {
311
                // Placeholder
312
                self.placeholder_offset = 0;
42✔
313
                Some(PatternItem::Placeholder(SinglePlaceholderKey::Singleton))
42✔
314
            }
315
            Ordering::Greater => {
316
                // Suffix or end of string
317
                let literal_str = match self.store.get(self.current_offset..) {
83✔
318
                    Some(s) => s,
83✔
319
                    None => {
320
                        debug_assert!(false, "offsets are in range");
×
UNCOV
321
                        ""
×
UNCOV
322
                    }
×
323
                };
324
                if literal_str.is_empty() {
83✔
325
                    // End of string
326
                    None
42✔
327
                } else {
328
                    // Suffix
329
                    self.current_offset = self.store.len();
41✔
330
                    Some(PatternItem::Literal(literal_str))
41✔
331
                }
332
            }
333
        }
334
    }
150✔
335

336
    fn size_hint(&self) -> (usize, Option<usize>) {
41✔
337
        let len = self.len();
41✔
338
        (len, Some(len))
41✔
339
    }
41✔
340
}
341

342
// 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