tor_netdoc/util/str.rs
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!("e[2..6], "rose");
18/// assert_eq!(str_offset(quote, "e[2..6]).unwrap(), 2);
19/// assert_eq!("e[12..16], "rose");
20/// assert_eq!(str_offset(quote, "e[12..16]).unwrap(), 12);
21/// assert_eq!("e[22..26], "rose");
22/// assert_eq!(str_offset(quote, "e[22..26]).unwrap(), 22);
23///
24/// assert_eq!(str_offset(quote, "rose"), None);
25///
26/// assert_eq!(str_offset("e[1..], "e[2..6]), Some(1));
27/// assert_eq!(str_offset("e[1..5], "e[2..6]), None);
28/// ```
29pub(crate) fn str_offset(haystack: &str, needle: &str) -> Option<usize> {
30 let needle_start_u = needle.as_ptr() as usize;
31 let needle_end_u = needle_start_u + needle.len();
32 let haystack_start_u = haystack.as_ptr() as usize;
33 let haystack_end_u = haystack_start_u + haystack.len();
34 if haystack_start_u <= needle_start_u && needle_end_u <= haystack_end_u {
35 Some(needle_start_u - haystack_start_u)
36 } else {
37 None
38 }
39}
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)]
47pub(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
62impl 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 pub(crate) fn new(haystack: &str, needle: &str) -> Option<Extent> {
68 str_offset(haystack, needle).map(|offset| Extent {
69 offset,
70 length: needle.len(),
71 sliceptr: haystack.as_ptr(),
72 slicelen: haystack.len(),
73 })
74 }
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 pub(crate) fn reconstruct<'a>(&self, haystack: &'a str) -> Option<&'a str> {
84 if self.sliceptr != haystack.as_ptr() || self.slicelen != haystack.len() {
85 None
86 } else {
87 haystack.get(self.offset..self.offset + self.length)
88 }
89 }
90}
91
92#[cfg(test)]
93mod test {
94 // @@ begin test lint list maintained by maint/add_warning @@
95 #![allow(clippy::bool_assert_comparison)]
96 #![allow(clippy::clone_on_copy)]
97 #![allow(clippy::dbg_macro)]
98 #![allow(clippy::mixed_attributes_style)]
99 #![allow(clippy::print_stderr)]
100 #![allow(clippy::print_stdout)]
101 #![allow(clippy::single_char_pattern)]
102 #![allow(clippy::unwrap_used)]
103 #![allow(clippy::unchecked_duration_subtraction)]
104 #![allow(clippy::useless_vec)]
105 #![allow(clippy::needless_pass_by_value)]
106 //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
107
108 #[test]
109 fn test_str_offset() {
110 use super::str_offset;
111 let quote = "A rose is a rose is a rose."; // -- Gertrude Stein
112 assert_eq!("e[2..6], "rose");
113 assert_eq!(str_offset(quote, "e[2..6]).unwrap(), 2);
114 assert_eq!("e[12..16], "rose");
115 assert_eq!(str_offset(quote, "e[12..16]).unwrap(), 12);
116 assert_eq!("e[22..26], "rose");
117 assert_eq!(str_offset(quote, "e[22..26]).unwrap(), 22);
118
119 assert_eq!(str_offset(quote, "rose"), None);
120
121 assert_eq!(str_offset("e[1..], "e[2..6]), Some(1));
122 assert_eq!(str_offset("e[1..5], "e[2..6]), None);
123 }
124
125 #[test]
126 fn test_extent() {
127 use super::Extent;
128 let quote = "What is a winter wedding a winter wedding."; // -- ibid
129 assert_eq!("e[10..16], "winter");
130 let ex = Extent::new(quote, "e[10..16]).unwrap();
131 let s = ex.reconstruct(quote).unwrap();
132 assert_eq!(s, "winter");
133
134 assert!(Extent::new(quote, "winter").is_none());
135 assert!(ex.reconstruct("Hello world").is_none());
136 }
137}