use std::fmt;
#[derive(Clone, Copy)]
pub struct StringRef<'a> {
data: &'a str,
}
impl<'a> StringRef<'a> {
#[inline]
pub fn new(s: &'a str) -> Self {
Self { data: s }
}
#[inline]
pub fn empty() -> Self {
Self { data: "" }
}
#[inline]
pub fn size(&self) -> usize {
self.data.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.data.is_empty()
}
#[inline]
pub fn data(&self) -> &'a str {
self.data
}
#[inline]
pub fn bytes(&self) -> &'a [u8] {
self.data.as_bytes()
}
#[inline]
pub fn front(&self) -> u8 {
self.data.as_bytes()[0]
}
#[inline]
pub fn back(&self) -> u8 {
self.data.as_bytes()[self.data.len() - 1]
}
#[inline]
pub fn get(&self, index: usize) -> Option<u8> {
self.data.as_bytes().get(index).copied()
}
#[inline]
pub fn substr(&self, start: usize, n: usize) -> StringRef<'a> {
if start >= self.data.len() {
return StringRef::empty();
}
let end = (start + n).min(self.data.len());
StringRef::new(&self.data[start..end])
}
#[inline]
pub fn slice(&self, start: usize, end: usize) -> StringRef<'a> {
let start = start.min(self.data.len());
let end = end.min(self.data.len());
if start >= end {
return StringRef::empty();
}
StringRef::new(&self.data[start..end])
}
#[inline]
pub fn take_front(&self, n: usize) -> StringRef<'a> {
let end = n.min(self.data.len());
StringRef::new(&self.data[..end])
}
#[inline]
pub fn take_back(&self, n: usize) -> StringRef<'a> {
let n = n.min(self.data.len());
StringRef::new(&self.data[self.data.len() - n..])
}
#[inline]
pub fn drop_front(&self, n: usize) -> StringRef<'a> {
let start = n.min(self.data.len());
StringRef::new(&self.data[start..])
}
#[inline]
pub fn drop_back(&self, n: usize) -> StringRef<'a> {
let n = n.min(self.data.len());
StringRef::new(&self.data[..self.data.len() - n])
}
#[inline]
pub fn find_char(&self, c: char) -> Option<usize> {
self.data.find(c)
}
#[inline]
pub fn find(&self, needle: &str) -> Option<usize> {
self.data.find(needle)
}
#[inline]
pub fn rfind_char(&self, c: char) -> Option<usize> {
self.data.rfind(c)
}
#[inline]
pub fn rfind(&self, needle: &str) -> Option<usize> {
self.data.rfind(needle)
}
#[inline]
pub fn find_first_of(&self, chars: &str) -> Option<usize> {
self.data.find(|c: char| chars.contains(c))
}
#[inline]
pub fn find_first_not_of(&self, chars: &str) -> Option<usize> {
self.data.find(|c: char| !chars.contains(c))
}
#[inline]
pub fn contains(&self, needle: &str) -> bool {
self.data.contains(needle)
}
#[inline]
pub fn contains_char(&self, c: char) -> bool {
self.data.contains(c)
}
#[inline]
pub fn starts_with(&self, prefix: &str) -> bool {
self.data.starts_with(prefix)
}
#[inline]
pub fn ends_with(&self, suffix: &str) -> bool {
self.data.ends_with(suffix)
}
#[inline]
pub fn equals_insensitive(&self, other: &str) -> bool {
self.data.eq_ignore_ascii_case(other)
}
#[inline]
pub fn compare(&self, other: &str) -> std::cmp::Ordering {
self.data.cmp(other)
}
#[inline]
pub fn compare_insensitive(&self, other: &str) -> std::cmp::Ordering {
self.data
.to_ascii_lowercase()
.cmp(&other.to_ascii_lowercase())
}
#[inline]
pub fn lower(&self) -> String<'a> {
String::Owned(self.data.to_ascii_lowercase())
}
#[inline]
pub fn upper(&self) -> String<'a> {
String::Owned(self.data.to_ascii_uppercase())
}
#[inline]
pub fn ltrim(&self) -> StringRef<'a> {
let trimmed = self.data.trim_start_matches(|c: char| {
c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\x0b' || c == '\x0c'
});
StringRef::new(trimmed)
}
#[inline]
pub fn rtrim(&self) -> StringRef<'a> {
let trimmed = self.data.trim_end_matches(|c: char| {
c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\x0b' || c == '\x0c'
});
StringRef::new(trimmed)
}
#[inline]
pub fn trim(&self) -> StringRef<'a> {
self.ltrim().rtrim()
}
#[inline]
pub fn split_char(&self, c: char) -> (StringRef<'a>, StringRef<'a>) {
match self.data.find(c) {
Some(pos) => (
StringRef::new(&self.data[..pos]),
StringRef::new(&self.data[pos + 1..]),
),
None => (*self, StringRef::empty()),
}
}
#[inline]
pub fn rsplit_char(&self, c: char) -> (StringRef<'a>, StringRef<'a>) {
match self.data.rfind(c) {
Some(pos) => (
StringRef::new(&self.data[..pos]),
StringRef::new(&self.data[pos + 1..]),
),
None => (*self, StringRef::empty()),
}
}
#[inline]
pub fn split_str(&self, separator: &str) -> (StringRef<'a>, StringRef<'a>) {
match self.data.find(separator) {
Some(pos) => (
StringRef::new(&self.data[..pos]),
StringRef::new(&self.data[pos + separator.len()..]),
),
None => (*self, StringRef::empty()),
}
}
#[inline]
pub fn split_iter(&self, c: char) -> impl Iterator<Item = StringRef<'a>> + '_ {
self.data.split(c).map(StringRef::new)
}
#[inline]
pub fn get_as_integer(&self, radix: u32) -> Result<i64, std::string::String> {
if self.data.is_empty() {
return Err("empty string".into());
}
let s = self.data.trim();
let (radix, s) = match radix {
0 => {
if s.starts_with("0x") || s.starts_with("0X") {
(16, &s[2..])
} else if s.starts_with('0') && s.len() > 1 {
(8, &s[1..])
} else {
(10, s)
}
}
r => {
if r == 16 && (s.starts_with("0x") || s.starts_with("0X")) {
(r, &s[2..])
} else {
(r, s)
}
}
};
i64::from_str_radix(s, radix).map_err(|e| format!("{}", e))
}
#[inline]
pub fn get_as_unsigned_integer(&self, radix: u32) -> Result<u64, std::string::String> {
if self.data.is_empty() {
return Err("empty string".into());
}
u64::from_str_radix(self.data.trim(), radix).map_err(|e| format!("{}", e))
}
#[inline]
pub fn consume_front_char(&self, c: char) -> Option<StringRef<'a>> {
if self.data.starts_with(c) {
Some(StringRef::new(&self.data[c.len_utf8()..]))
} else {
None
}
}
#[inline]
pub fn consume_front(&self, prefix: &str) -> Option<StringRef<'a>> {
self.data.strip_prefix(prefix).map(StringRef::new)
}
#[inline]
pub fn consume_back(&self, suffix: &str) -> Option<StringRef<'a>> {
self.data.strip_suffix(suffix).map(StringRef::new)
}
#[inline]
pub fn count_char(&self, c: char) -> usize {
self.data.matches(c).count()
}
#[inline]
pub fn count(&self, needle: &str) -> usize {
self.data.matches(needle).count()
}
#[inline]
pub fn to_owned(&self) -> std::string::String {
self.data.to_string()
}
}
impl<'a> fmt::Display for StringRef<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.data)
}
}
impl<'a> fmt::Debug for StringRef<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "StringRef(\"{}\")", self.data)
}
}
impl<'a> PartialEq for StringRef<'a> {
fn eq(&self, other: &Self) -> bool {
self.data == other.data
}
}
impl<'a> Eq for StringRef<'a> {}
impl<'a> PartialEq<str> for StringRef<'a> {
fn eq(&self, other: &str) -> bool {
self.data == other
}
}
impl<'a> PartialEq<&str> for StringRef<'a> {
fn eq(&self, other: &&str) -> bool {
self.data == *other
}
}
impl<'a> PartialOrd for StringRef<'a> {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl<'a> Ord for StringRef<'a> {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.data.cmp(other.data)
}
}
impl<'a> std::hash::Hash for StringRef<'a> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.data.hash(state);
}
}
impl<'a> From<&'a str> for StringRef<'a> {
fn from(s: &'a str) -> Self {
StringRef::new(s)
}
}
impl<'a> AsRef<str> for StringRef<'a> {
fn as_ref(&self) -> &str {
self.data
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum String<'a> {
Owned(std::string::String),
Borrowed(&'a str),
}
impl<'a> fmt::Display for String<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
String::Owned(s) => write!(f, "{}", s),
String::Borrowed(s) => write!(f, "{}", s),
}
}
}