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

pulibrary / bibdata / 28b3b9e3-0baa-4019-b967-1c4ee5de11f1

25 Mar 2026 11:19PM UTC coverage: 89.6% (-0.7%) from 90.343%
28b3b9e3-0baa-4019-b967-1c4ee5de11f1

Pull #3144

circleci

sandbergja
Rewrite Action Notes in Rust
Pull Request #3144: Rewrite Action Notes in Rust

10 of 97 new or added lines in 3 files covered. (10.31%)

17 existing lines in 1 file now uncovered.

9899 of 11048 relevant lines covered (89.6%)

255.36 hits per line

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

98.53
/lib/bibdata_rs/src/marc/string_normalize.rs
1
use std::{borrow::Cow, sync::LazyLock};
2

3
use regex::Regex;
4

5
/// A re-implementation of traject's trim_punctuation macro.
6
/// Unlike the original, it does not mutate the provided string,
7
/// but rather allocates a new string
8
/// See https://github.com/traject/traject/blob/8957f842d1e0461f2b38ac85e7f9876d3ec757a0/lib/traject/macros/marc21.rb#L241-L271
9
/// for the original implementation
10
pub fn trim_punctuation(string: &str) -> String {
54✔
11
    // comma, slash, semicolon, colon (possibly preceded and followed by whitespace)
12
    static TRAILING: LazyLock<Regex> = LazyLock::new(|| Regex::new(r" *[ ,\/;:] *\z").unwrap());
1✔
13

14
    // trailing period if it is preceded by at least three letters (possibly preceded and followed by whitespace)
15
    static TRAILING_PERIOD: LazyLock<Regex> =
16
        LazyLock::new(|| Regex::new(r"( *\w{3,}) *\.*\z").unwrap());
1✔
17

18
    let no_trailing = TRAILING.replace(string, "");
54✔
19
    let no_trailing_period = TRAILING_PERIOD.replace(&no_trailing, "$1");
54✔
20
    trim_starting_and_ending_brackets(&no_trailing_period)
54✔
21
        .trim()
54✔
22
        .to_owned()
54✔
23
}
54✔
24

25
fn trim_starting_and_ending_brackets(original: &str) -> Cow<'_, str> {
54✔
26
    let trimmed = original
54✔
27
        .strip_prefix('[')
54✔
28
        .map(|start_trimmed| start_trimmed.strip_suffix(']').unwrap_or(start_trimmed))
54✔
29
        .or(original.strip_suffix(']'));
54✔
30
    match trimmed {
4✔
31
        // Make sure the string does not contain any remaining [], since they might have
32
        // been paired with the ones we just removed, in which case the trimmed string
33
        // won't make sense and we should return the original.
34
        Some(trimmed) if !trimmed.contains('[') && !trimmed.contains(']') => {
4✔
35
            Cow::Owned(trimmed.to_owned())
3✔
36
        }
37
        _ => Cow::Borrowed(original),
51✔
38
    }
39
}
54✔
40

41
pub fn strip_non_numeric(string: &str) -> String {
25✔
42
    string
25✔
43
        .chars()
25✔
44
        // remove preceding zeroes
45
        .skip_while(|c| !c.is_numeric() || c == &'0')
150✔
46
        .filter(|c| c.is_numeric())
157✔
47
        .collect()
25✔
48
}
25✔
49

50
pub fn upcase_first(string: &str) -> Cow<'_, str> {
2✔
51
    let mut chars = string.chars();
2✔
52
    match chars.next() {
2✔
53
        Some(first) if first.is_uppercase() => Cow::Borrowed(string),
2✔
54
        Some(first) => Cow::Owned(first.to_uppercase().collect::<String>() + chars.as_str()),
1✔
NEW
55
        None => Cow::Borrowed(Default::default()),
×
56
    }
57
}
2✔
58

59
#[cfg(test)]
60
mod tests {
61
    use super::*;
62

63
    #[test]
64
    fn it_trims_punctuation() {
1✔
65
        assert_eq!("one two three", trim_punctuation("one two three"));
1✔
66
        assert_eq!("one two three", trim_punctuation("one two three,"));
1✔
67
        assert_eq!("one two three", trim_punctuation("one two three/"));
1✔
68
        assert_eq!("one two three", trim_punctuation("one two three;"));
1✔
69
        assert_eq!("one two three", trim_punctuation("one two three:"));
1✔
70
        assert_eq!("one two three", trim_punctuation("one two three ."));
1✔
71
        assert_eq!("one two three", trim_punctuation("one two three."));
1✔
72
        assert_eq!("one two three", trim_punctuation("one two three..."));
1✔
73
        assert_eq!("one two three", trim_punctuation(" one two three."));
1✔
74

75
        assert_eq!("one two [three]", trim_punctuation("one two [three]"));
1✔
76
        assert_eq!("one two three", trim_punctuation("one two three]"));
1✔
77
        assert_eq!("one two three", trim_punctuation("[one two three"));
1✔
78
        assert_eq!("one two three", trim_punctuation("[one two three]"));
1✔
79

80
        assert_eq!("Feminism and art", trim_punctuation("Feminism and art."));
1✔
81
        assert_eq!("Le réve", trim_punctuation("Le réve."));
1✔
82
        assert_eq!("Bill Dueber, Jr.", trim_punctuation("Bill Dueber, Jr."));
1✔
83
    }
1✔
84

85
    #[test]
86
    fn it_strips_non_numeric() {
1✔
87
        assert_eq!(strip_non_numeric("abc"), "");
1✔
88
        assert_eq!(strip_non_numeric("123"), "123");
1✔
89
        assert_eq!(strip_non_numeric("0123"), "123");
1✔
90
        assert_eq!(strip_non_numeric("abc0123"), "123");
1✔
91
        assert_eq!(strip_non_numeric("0abc123"), "123");
1✔
92
        assert_eq!(strip_non_numeric("000abc123"), "123");
1✔
93
        assert_eq!(strip_non_numeric("000abc0123"), "123");
1✔
94
        assert_eq!(strip_non_numeric("1024"), "1024");
1✔
95
        assert_eq!(strip_non_numeric("a1b0c2d4e"), "1024");
1✔
96
        assert_eq!(strip_non_numeric("300"), "300");
1✔
97
        assert_eq!(strip_non_numeric("3abcd00"), "300");
1✔
98
    }
1✔
99

100
    #[test]
101
    fn it_can_upcase_first() {
1✔
102
        assert_eq!(upcase_first("dog"), "Dog");
1✔
103
        assert_eq!(upcase_first("Dog"), "Dog");
1✔
104
    }
1✔
105
}
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