#![forbid(unsafe_code)]
pub use to_offset::*;
pub trait SubstringReplace where Self: AsRef<str> {
fn substring<S: ToOffset, E: ToOffset>(&self, start: S, end: E) -> &str;
fn substring_start(&self, end: i64) -> &str {
let end_index = if end < 0 { self.char_len().checked_sub(end.abs() as usize).unwrap_or(0) } else { end as usize };
self.substring(0, end_index)
}
fn substring_end(&self, start: i64) -> &str {
let max_index = self.char_len();
let start_index = if start < 0 { max_index.checked_sub(start.abs() as usize).unwrap_or(0) } else { start as usize };
self.substring(start_index, max_index)
}
#[deprecated(since = "0.2.1", note = "Use `substring` instead")]
fn substring_range(&self, start: usize, end: i64) -> &str {
self.substring(start, end)
}
fn substring_replace<S: ToOffset, E: ToOffset>(&self, replacement: &str, start: S, end: E) -> String;
#[deprecated(since = "0.2.1", note = "Use `substring` instead")]
fn substring_replace_range(&self, replacement: &str, start: usize, end: i64) -> String {
self.substring_replace(replacement, start, end)
}
fn substring_replace_start(&self, replacement: &str, end: i64) -> String {
let end_index = if end < 0 { self.char_len().saturating_sub(end.abs() as usize) } else { end as usize };
self.substring_replace(replacement, 0, end_index)
}
fn substring_replace_end(&self, replacement: &str, start: i64) -> String {
let end = self.char_len();
let start_index = if start < 0 { end.saturating_sub(start.abs() as usize) } else { start as usize };
self.substring_replace(replacement, start_index, end)
}
fn substring_remove(&self, start: usize, end: usize) -> String {
self.substring_replace("", start, end)
}
fn substring_offset(&self, position: usize, length: i32) -> &str {
let (start, end) = position_and_offset_to_start_end(position, length);
self.substring(start, end)
}
fn substring_pull(&self, position: usize, length: i32) -> String {
let (start, end) = position_and_offset_to_start_end(position, length);
self.substring_replace("", start, end)
}
fn substring_insert(&self, replacement: &str, start: usize) -> String {
self.substring_replace(replacement, start, start)
}
fn to_start_byte_index(&self, start: usize) -> usize;
fn to_end_byte_index(&self, start: usize) -> usize;
fn char_len(&self) -> usize;
fn char_find(&self, pat: &str) -> Option<usize>;
fn char_rfind(&self, pat: &str) -> Option<usize>;
fn insert_adjacent(&self, insert: &str, pat: &str, before: bool, first: bool) -> String;
fn insert_before_first(&self, insert: &str, pat: &str) -> String {
self.insert_adjacent(insert, pat, true, true)
}
fn insert_before_last(&self, insert: &str, pat: &str) -> String {
self.insert_adjacent(insert, pat, true, false)
}
fn insert_after_first(&self, insert: &str, pat: &str) -> String {
self.insert_adjacent(insert, pat, false, true)
}
fn insert_after_last(&self, insert: &str, pat: &str) -> String {
self.insert_adjacent(insert, pat, false, false)
}
fn insert_between(&self, insert: &str, start_pat: &str, end_pat: &str) -> String {
if let Some(start_index) = self.char_find(start_pat) {
if let Some(end_index) = self.char_rfind(end_pat) {
return self.substring_replace(insert, start_index + 1, end_index);
}
}
self.as_ref().to_string()
}
fn prepend(&self, insert: &str) -> String {
[insert.to_string(), self.as_ref().to_string()].concat()
}
fn append(&self, insert: &str) -> String {
[self.as_ref().to_string(), insert.to_string()].concat()
}
}
impl<S: AsRef<str>> SubstringReplace for S {
fn substring<A: ToOffset, B: ToOffset>(&self, start: A, end: B) -> &str {
let len = self.char_len();
let start_index = start.to_offset(len);
let end_index = end.to_offset(len);
if end_index > start_index {
&self.as_ref()[self.to_start_byte_index(start_index)..self.to_end_byte_index(end_index)]
} else {
""
}
}
fn substring_replace<A: ToOffset, B: ToOffset>(&self, replacement: &str, start: A, end: B) -> String {
let len = self.char_len();
let start_index = start.to_offset(len);
let end_index = end.to_offset(len);
let text = self.as_ref();
[&text[0..self.to_start_byte_index(start_index)], replacement, &text[self.to_end_byte_index(end_index)..]].concat()
}
fn to_start_byte_index(&self, start: usize) -> usize {
char_index_to_byte_index(self.as_ref(), start)
}
fn to_end_byte_index(&self, end: usize) -> usize {
char_index_to_byte_index(self.as_ref(), end)
}
fn char_len(&self) -> usize {
self.as_ref().char_indices().count()
}
fn char_find(&self, pat: &str) -> Option<usize>{
extract_char_index(self.as_ref(), pat, false)
}
fn char_rfind(&self, pat: &str) -> Option<usize>{
extract_char_index(self.as_ref(), pat, true)
}
fn insert_adjacent(&self, insert: &str, pat: &str, before: bool, first: bool) -> String {
if let Some(index) = extract_char_index(self.as_ref(), pat, !first) {
let rel_index = if before {
index
} else {
index + 1
};
self.substring_insert(insert, rel_index)
} else {
self.as_ref().to_string()
}
}
}
fn char_index_to_byte_index(text: &str, char_index: usize) -> usize {
text.char_indices().nth(char_index).map(|(i, _)| i).unwrap_or(text.len())
}
fn position_and_offset_to_start_end(position: usize, length: i32) -> (usize, usize) {
let reverse = length < 0;
let start = if reverse {
position.checked_sub(length.abs() as usize).unwrap_or(0)
} else {
position
};
let start_i32 = if start > i32::MAX as usize { i32::MAX } else { start as i32 };
let end_i32 = start_i32 + length.abs();
let end = if end_i32 < 0 {
0
} else {
end_i32 as usize
};
(start, end)
}
fn extract_char_index(text: &str, pat: &str, reverse: bool) -> Option<usize> {
if pat.is_empty() {
return None;
}
let byte_index = if reverse { text.rfind(pat) } else { text.find(pat) };
byte_index.map(|bi| text[..bi].chars().count())
}