Skip to main content

chrono_node/core/utils/
pattern.rs

1//! upstream `utils/pattern.ts`.
2
3/// JS `string.length`: UTF-16 code units. Use it wherever upstream compares or sorts by length.
4pub fn js_len(s: &str) -> usize {
5    s.encode_utf16().count()
6}
7
8/// `repeatedTimeunitPattern(prefix, singleTimeunitPattern, connectorPattern)`.
9pub fn repeated_timeunit_pattern(
10    prefix: &str,
11    single_timeunit_pattern: &str,
12    connector_pattern: Option<&str>,
13) -> String {
14    let connector = connector_pattern.unwrap_or(r"\s{0,5},?\s{0,5}");
15    // `.replace(/\((?!\?)/g, "(?:")`: every "(" not followed by "?" becomes non-capturing.
16    let chars: Vec<char> = single_timeunit_pattern.chars().collect();
17    let mut no_capture = String::new();
18    for (i, &c) in chars.iter().enumerate() {
19        if c == '(' && chars.get(i + 1) != Some(&'?') {
20            no_capture.push_str("(?:");
21        } else {
22            no_capture.push(c);
23        }
24    }
25    format!("{prefix}{no_capture}(?:{connector}{no_capture}){{0,10}}")
26}
27
28/// `matchAnyPattern(dictionary)`: pass the dictionary keys **in upstream's key order**.
29/// Longest first (stable sort, so equal lengths keep that order), joined with `|`, dots escaped.
30pub fn match_any_pattern<'a>(terms: impl IntoIterator<Item = &'a str>) -> String {
31    let mut terms: Vec<&str> = terms.into_iter().collect();
32    terms.sort_by_key(|t| std::cmp::Reverse(js_len(t)));
33    format!("(?:{})", terms.join("|").replace('.', r"\."))
34}
35
36/// An upstream `{ [word: string]: V }` dictionary, as `(key, value)` pairs **in upstream key order**.
37pub type Dictionary<V> = [(&'static str, V)];
38
39/// `DICTIONARY[key]` (`None` = `undefined`).
40pub fn dict_get<V: Copy>(dictionary: &Dictionary<V>, key: &str) -> Option<V> {
41    dictionary.iter().find(|(k, _)| *k == key).map(|(_, v)| *v)
42}
43
44/// `Object.keys(DICTIONARY)`.
45pub fn dict_keys<V>(dictionary: &Dictionary<V>) -> impl Iterator<Item = &'static str> + '_ {
46    dictionary.iter().map(|(k, _)| *k)
47}
48
49#[cfg(test)]
50mod tests {
51    use super::*;
52
53    #[test]
54    fn any_pattern_sorts_longest_first_stably() {
55        assert_eq!(
56            match_any_pattern(["mon", "monday", "tue", "sept."]),
57            r"(?:monday|sept\.|mon|tue)"
58        );
59    }
60
61    #[test]
62    fn repeated_pattern() {
63        assert_eq!(
64            repeated_timeunit_pattern("", "(a)(?:b)", Some(",")),
65            "(?:a)(?:b)(?:,(?:a)(?:b)){0,10}"
66        );
67    }
68
69    #[test]
70    fn js_length_counts_utf16() {
71        assert_eq!(js_len("a😀"), 3);
72    }
73}