1
//! String-manipulation utilities
2

            
3
/// Return the position of one string slice within another.
4
///
5
/// If `needle` is indeed part of `haystack`, returns some offset
6
/// `off`, such that `needle` is the same as
7
/// `&haystack[off..needle.len()]`.
8
///
9
/// Returns None if `needle` is not a part of `haystack`.
10
///
11
/// Remember, offsets are in bytes, not in characters.
12
///
13
/// # Example
14
/// ```ignore
15
/// use tor_netdoc::util::str_offset;
16
/// let quote = "A rose is a rose is a rose."; // -- Gertrude Stein
17
/// assert_eq!(&quote[2..6], "rose");
18
/// assert_eq!(str_offset(quote, &quote[2..6]).unwrap(), 2);
19
/// assert_eq!(&quote[12..16], "rose");
20
/// assert_eq!(str_offset(quote, &quote[12..16]).unwrap(), 12);
21
/// assert_eq!(&quote[22..26], "rose");
22
/// assert_eq!(str_offset(quote, &quote[22..26]).unwrap(), 22);
23
///
24
/// assert_eq!(str_offset(quote, "rose"), None);
25
///
26
/// assert_eq!(str_offset(&quote[1..], &quote[2..6]), Some(1));
27
/// assert_eq!(str_offset(&quote[1..5], &quote[2..6]), None);
28
/// ```
29
756
pub(crate) fn str_offset(haystack: &str, needle: &str) -> Option<usize> {
30
756
    let needle_start_u = needle.as_ptr() as usize;
31
756
    let needle_end_u = needle_start_u + needle.len();
32
756
    let haystack_start_u = haystack.as_ptr() as usize;
33
756
    let haystack_end_u = haystack_start_u + haystack.len();
34
756
    if haystack_start_u <= needle_start_u && needle_end_u <= haystack_end_u {
35
753
        Some(needle_start_u - haystack_start_u)
36
    } else {
37
3
        None
38
    }
39
756
}
40

            
41
/// An extent within a given string slice.
42
///
43
/// This whole type is probably naughty and shouldn't exist.  We use
44
/// it only within this crate, to remember where it was that we found
45
/// parsed objects within the strings we got them from.
46
#[derive(Clone, Debug)]
47
pub(crate) struct Extent {
48
    /// At what position within the original string is this extent, in bytes?
49
    offset: usize,
50
    /// How long is this extend, in bytes?
51
    length: usize,
52
    /// What was the original string?
53
    ///
54
    /// If this doesn't match, there's been an error.
55
    sliceptr: *const u8,
56
    /// How long was the original string?
57
    ///
58
    /// If this doesn't match, there's been an error.
59
    slicelen: usize,
60
}
61

            
62
impl Extent {
63
    /// Construct a new extent to represent the position of `needle`
64
    /// within `haystack`.
65
    ///
66
    /// Return None if `needle` is not in fact a slice of `haystack`.
67
204
    pub(crate) fn new(haystack: &str, needle: &str) -> Option<Extent> {
68
204
        str_offset(haystack, needle).map(|offset| Extent {
69
203
            offset,
70
203
            length: needle.len(),
71
203
            sliceptr: haystack.as_ptr(),
72
203
            slicelen: haystack.len(),
73
204
        })
74
204
    }
75
    /// Reconstruct the original `needle` within `haystack`.
76
    ///
77
    /// Return None if we're sure that the haystack doesn't match the one
78
    /// we were originally given.
79
    ///
80
    /// Note that it is possible for this to give a bogus result if
81
    /// provided a new haystack that happens to be at the same
82
    /// position in memory as the original one.
83
155
    pub(crate) fn reconstruct<'a>(&self, haystack: &'a str) -> Option<&'a str> {
84
155
        if self.sliceptr != haystack.as_ptr() || self.slicelen != haystack.len() {
85
1
            None
86
        } else {
87
154
            haystack.get(self.offset..self.offset + self.length)
88
        }
89
155
    }
90
}
91

            
92
#[cfg(test)]
93
mod test {
94
    #![allow(clippy::unwrap_used)]
95

            
96
    #[test]
97
    fn test_str_offset() {
98
        use super::str_offset;
99
        let quote = "A rose is a rose is a rose."; // -- Gertrude Stein
100
        assert_eq!(&quote[2..6], "rose");
101
        assert_eq!(str_offset(quote, &quote[2..6]).unwrap(), 2);
102
        assert_eq!(&quote[12..16], "rose");
103
        assert_eq!(str_offset(quote, &quote[12..16]).unwrap(), 12);
104
        assert_eq!(&quote[22..26], "rose");
105
        assert_eq!(str_offset(quote, &quote[22..26]).unwrap(), 22);
106

            
107
        assert_eq!(str_offset(quote, "rose"), None);
108

            
109
        assert_eq!(str_offset(&quote[1..], &quote[2..6]), Some(1));
110
        assert_eq!(str_offset(&quote[1..5], &quote[2..6]), None);
111
    }
112

            
113
    #[test]
114
    fn test_extent() {
115
        use super::Extent;
116
        let quote = "What is a winter wedding a winter wedding."; // -- ibid
117
        assert_eq!(&quote[10..16], "winter");
118
        let ex = Extent::new(quote, &quote[10..16]).unwrap();
119
        let s = ex.reconstruct(quote).unwrap();
120
        assert_eq!(s, "winter");
121

            
122
        assert!(Extent::new(quote, "winter").is_none());
123
        assert!(ex.reconstruct("Hello world").is_none());
124
    }
125
}