1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
//! String-manipulation utilities
/// Return the position of one string slice within another.
///
/// If `needle` is indeed part of `haystack`, returns some offset
/// `off`, such that `needle` is the same as
/// `&haystack[off..needle.len()]`.
///
/// Returns None if `needle` is not a part of `haystack`.
///
/// Remember, offsets are in bytes, not in characters.
///
/// # Example
/// ```ignore
/// use tor_netdoc::util::str_offset;
/// let quote = "A rose is a rose is a rose."; // -- Gertrude Stein
/// assert_eq!("e[2..6], "rose");
/// assert_eq!(str_offset(quote, "e[2..6]).unwrap(), 2);
/// assert_eq!("e[12..16], "rose");
/// assert_eq!(str_offset(quote, "e[12..16]).unwrap(), 12);
/// assert_eq!("e[22..26], "rose");
/// assert_eq!(str_offset(quote, "e[22..26]).unwrap(), 22);
///
/// assert_eq!(str_offset(quote, "rose"), None);
///
/// assert_eq!(str_offset("e[1..], "e[2..6]), Some(1));
/// assert_eq!(str_offset("e[1..5], "e[2..6]), None);
/// ```
pub(crate) fn str_offset(haystack: &str, needle: &str) -> Option<usize> {
let needle_start_u = needle.as_ptr() as usize;
let needle_end_u = needle_start_u + needle.len();
let haystack_start_u = haystack.as_ptr() as usize;
let haystack_end_u = haystack_start_u + haystack.len();
if haystack_start_u <= needle_start_u && needle_end_u <= haystack_end_u {
Some(needle_start_u - haystack_start_u)
} else {
None
}
}
/// An extent within a given string slice.
///
/// This whole type is probably naughty and shouldn't exist. We use
/// it only within this crate, to remember where it was that we found
/// parsed objects within the strings we got them from.
#[derive(Clone, Debug)]
pub(crate) struct Extent {
/// At what position within the original string is this extent, in bytes?
offset: usize,
/// How long is this extend, in bytes?
length: usize,
/// What was the original string?
///
/// If this doesn't match, there's been an error.
sliceptr: *const u8,
/// How long was the original string?
///
/// If this doesn't match, there's been an error.
slicelen: usize,
}
impl Extent {
/// Construct a new extent to represent the position of `needle`
/// within `haystack`.
///
/// Return None if `needle` is not in fact a slice of `haystack`.
pub(crate) fn new(haystack: &str, needle: &str) -> Option<Extent> {
str_offset(haystack, needle).map(|offset| Extent {
offset,
length: needle.len(),
sliceptr: haystack.as_ptr(),
slicelen: haystack.len(),
})
}
/// Reconstruct the original `needle` within `haystack`.
///
/// Return None if we're sure that the haystack doesn't match the one
/// we were originally given.
///
/// Note that it is possible for this to give a bogus result if
/// provided a new haystack that happens to be at the same
/// position in memory as the original one.
pub(crate) fn reconstruct<'a>(&self, haystack: &'a str) -> Option<&'a str> {
if self.sliceptr != haystack.as_ptr() || self.slicelen != haystack.len() {
None
} else {
haystack.get(self.offset..self.offset + self.length)
}
}
}
#[cfg(test)]
mod test {
// @@ begin test lint list maintained by maint/add_warning @@
#![allow(clippy::bool_assert_comparison)]
#![allow(clippy::clone_on_copy)]
#![allow(clippy::dbg_macro)]
#![allow(clippy::print_stderr)]
#![allow(clippy::print_stdout)]
#![allow(clippy::single_char_pattern)]
#![allow(clippy::unwrap_used)]
#![allow(clippy::unchecked_duration_subtraction)]
//! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
#[test]
fn test_str_offset() {
use super::str_offset;
let quote = "A rose is a rose is a rose."; // -- Gertrude Stein
assert_eq!("e[2..6], "rose");
assert_eq!(str_offset(quote, "e[2..6]).unwrap(), 2);
assert_eq!("e[12..16], "rose");
assert_eq!(str_offset(quote, "e[12..16]).unwrap(), 12);
assert_eq!("e[22..26], "rose");
assert_eq!(str_offset(quote, "e[22..26]).unwrap(), 22);
assert_eq!(str_offset(quote, "rose"), None);
assert_eq!(str_offset("e[1..], "e[2..6]), Some(1));
assert_eq!(str_offset("e[1..5], "e[2..6]), None);
}
#[test]
fn test_extent() {
use super::Extent;
let quote = "What is a winter wedding a winter wedding."; // -- ibid
assert_eq!("e[10..16], "winter");
let ex = Extent::new(quote, "e[10..16]).unwrap();
let s = ex.reconstruct(quote).unwrap();
assert_eq!(s, "winter");
assert!(Extent::new(quote, "winter").is_none());
assert!(ex.reconstruct("Hello world").is_none());
}
}