• 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

86.46
/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,572✔
59
        Ok(Self::Singleton)
60
    }
26,572✔
61
}
62

63
impl<W> PlaceholderValueProvider<SinglePlaceholderKey> for (W,)
64
where
65
    W: Writeable,
66
{
67
    type Error = Infallible;
68
    type W<'a>
69
        = WriteableAsTryWriteableInfallible<&'a W>
70
    where
71
        W: 'a;
72
    const LITERAL_PART: writeable::Part = crate::PATTERN_LITERAL_PART;
73
    fn value_for(&self, _key: SinglePlaceholderKey) -> (Self::W<'_>, writeable::Part) {
1,252✔
74
        (
1,252✔
75
            WriteableAsTryWriteableInfallible(&self.0),
76
            crate::PATTERN_PLACEHOLDER_PART,
77
        )
78
    }
1,252✔
79
}
80

81
impl<W> PlaceholderValueProvider<SinglePlaceholderKey> for [W; 1]
82
where
83
    W: Writeable,
84
{
85
    type Error = Infallible;
86
    type W<'a>
87
        = WriteableAsTryWriteableInfallible<&'a W>
88
    where
89
        W: 'a;
90
    const LITERAL_PART: writeable::Part = crate::PATTERN_LITERAL_PART;
91
    fn value_for(&self, _key: SinglePlaceholderKey) -> (Self::W<'_>, writeable::Part) {
8,414✔
92
        let [value] = self;
8,414✔
93
        (
8,414✔
94
            WriteableAsTryWriteableInfallible(value),
95
            crate::PATTERN_PLACEHOLDER_PART,
96
        )
97
    }
8,414✔
98
}
99

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

192
impl crate::private::Sealed for SinglePlaceholder {}
193

194
impl PatternBackend for SinglePlaceholder {
195
    type PlaceholderKey<'a> = SinglePlaceholderKey;
196
    #[cfg(feature = "alloc")]
197
    type PlaceholderKeyCow<'a> = SinglePlaceholderKey;
198
    type Error<'a> = Infallible;
199
    type Store = str;
200
    type Iter<'a> = SinglePlaceholderPatternIterator<'a>;
201

202
    fn validate_store(store: &Self::Store) -> Result<(), Error> {
29,964✔
203
        let placeholder_offset_char = store.chars().next().ok_or(Error::InvalidPattern)?;
29,964✔
204
        let initial_offset = placeholder_offset_char.len_utf8();
29,964✔
205
        let placeholder_offset = placeholder_offset_char as usize;
29,964✔
206
        if placeholder_offset > store.len() - initial_offset + 1 {
29,964✔
207
            return Err(Error::InvalidPattern);
×
208
        }
209
        if placeholder_offset >= 0xD800 {
29,964✔
210
            return Err(Error::InvalidPattern);
×
211
        }
212
        Ok(())
29,964✔
213
    }
29,964✔
214

215
    fn iter_items(store: &Self::Store) -> Self::Iter<'_> {
10,165✔
216
        let placeholder_offset_char = match store.chars().next() {
10,165✔
217
            Some(i) => i,
10,165✔
218
            None => {
219
                debug_assert!(false);
×
220
                '\0'
221
            }
222
        };
223
        let initial_offset = placeholder_offset_char.len_utf8();
10,165✔
224
        SinglePlaceholderPatternIterator {
10,165✔
225
            store,
226
            placeholder_offset: placeholder_offset_char as usize + initial_offset - 1,
10,165✔
227
            current_offset: initial_offset,
228
        }
229
    }
10,165✔
230

231
    #[cfg(feature = "alloc")]
232
    fn try_from_items<
87,407✔
233
        'cow,
234
        'ph,
235
        I: Iterator<Item = Result<PatternItemCow<'cow, Self::PlaceholderKey<'ph>>, Error>>,
236
    >(
237
        items: I,
238
    ) -> Result<Box<str>, Error> {
239
        let mut result = String::new();
87,407✔
240
        let mut seen_placeholder = false;
87,411✔
241
        for item in items {
113,966✔
242
            match item? {
56,635✔
243
                PatternItemCow::Literal(s) => result.push_str(&s),
30,074✔
244
                PatternItemCow::Placeholder(_) if !seen_placeholder => {
26,564✔
245
                    seen_placeholder = true;
26,566✔
246
                    let placeholder_offset =
247
                        u32::try_from(result.len() + 1).map_err(|_| Error::InvalidPattern)?;
26,564✔
248
                    if placeholder_offset >= 0xD800 {
26,563✔
249
                        return Err(Error::InvalidPattern);
×
250
                    }
251
                    let placeholder_offset_char =
252
                        char::try_from(placeholder_offset).map_err(|_| Error::InvalidPattern)?;
26,563✔
253
                    result.insert(0, placeholder_offset_char);
26,560✔
254
                }
255
                PatternItemCow::Placeholder(_) => {
256
                    return Err(Error::InvalidPattern);
×
257
                }
258
            }
259
        }
27,237✔
260
        if !seen_placeholder {
27,237✔
261
            result.insert(0, '\0');
680✔
262
        }
263
        Ok(result.into_boxed_str())
27,237✔
264
    }
27,243✔
265

266
    fn empty() -> &'static Self::Store {
913✔
267
        "\0"
268
    }
913✔
269
}
270

271
#[doc(hidden)] // TODO(#4467): Should be internal
272
#[derive(Debug)]
×
273
pub struct SinglePlaceholderPatternIterator<'a> {
274
    store: &'a str,
275
    placeholder_offset: usize,
×
276
    current_offset: usize,
×
277
}
278

279
// Note: This impl is not exported via public bounds, but it might be in the
280
// future, and the compiler might be able to find it. The code is also
281
// reachable from `Iterator::size_hint`.
282
impl ExactSizeIterator for SinglePlaceholderPatternIterator<'_> {
283
    fn len(&self) -> usize {
10,145✔
284
        let placeholder_offset_char = match self.store.chars().next() {
10,145✔
285
            Some(i) => i,
10,145✔
286
            None => {
287
                debug_assert!(false);
×
288
                '\0'
289
            }
290
        };
291
        let initial_offset = placeholder_offset_char.len_utf8();
10,145✔
292
        let placeholder_offset = placeholder_offset_char as usize + initial_offset - 1;
10,145✔
293
        let store_len = self.store.len();
10,145✔
294
        if placeholder_offset < initial_offset {
10,145✔
295
            // No placeholder
296
            if initial_offset < store_len {
468✔
297
                // No placeholder, non-empty literal
298
                1
468✔
299
            } else {
300
                // No placeholder, empty literal
301
                0
×
302
            }
303
        } else if placeholder_offset == initial_offset {
9,677✔
304
            // Has placeholder, empty prefix
305
            if initial_offset < store_len {
6,382✔
306
                // Has placeholder, empty prefix, non-empty suffix
307
                2
5,886✔
308
            } else {
309
                // Has placeholder, empty prefix, empty suffix
310
                1
496✔
311
            }
312
        } else if placeholder_offset < store_len {
3,295✔
313
            // Has placeholder, non-empty prefix, non-empty suffix
314
            3
1,315✔
315
        } else {
316
            // Has placeholder, non-empty prefix, empty suffix
317
            2
1,980✔
318
        }
319
    }
10,145✔
320
}
321

322
impl<'a> Iterator for SinglePlaceholderPatternIterator<'a> {
323
    type Item = PatternItem<'a, SinglePlaceholderKey>;
324
    fn next(&mut self) -> Option<Self::Item> {
30,782✔
325
        match self.current_offset.cmp(&self.placeholder_offset) {
30,782✔
326
            Ordering::Less => {
327
                // Prefix
328
                let literal_str = match self.store.get(self.current_offset..self.placeholder_offset)
3,300✔
329
                {
330
                    Some(s) => s,
3,300✔
331
                    None => {
332
                        debug_assert!(false, "offsets are in range");
×
333
                        ""
334
                    }
335
                };
336
                self.current_offset = self.placeholder_offset;
3,300✔
337
                Some(PatternItem::Literal(literal_str))
3,300✔
338
            }
3,300✔
339
            Ordering::Equal => {
340
                // Placeholder
341
                self.placeholder_offset = 0;
9,674✔
342
                Some(PatternItem::Placeholder(SinglePlaceholderKey::Singleton))
9,674✔
343
            }
344
            Ordering::Greater => {
345
                // Suffix or end of string
346
                let literal_str = match self.store.get(self.current_offset..) {
17,808✔
347
                    Some(s) => s,
17,808✔
348
                    None => {
349
                        debug_assert!(false, "offsets are in range");
×
350
                        ""
351
                    }
352
                };
353
                if literal_str.is_empty() {
17,808✔
354
                    // End of string
355
                    None
10,146✔
356
                } else {
357
                    // Suffix
358
                    self.current_offset = self.store.len();
7,662✔
359
                    Some(PatternItem::Literal(literal_str))
7,662✔
360
                }
361
            }
362
        }
363
    }
30,782✔
364

365
    fn size_hint(&self) -> (usize, Option<usize>) {
10,157✔
366
        let len = self.len();
10,157✔
367
        (len, Some(len))
10,157✔
368
    }
10,157✔
369
}
370

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