use crate::types::{int, slice, string};
pub fn Contains(s: impl AsRef<str>, substr: impl AsRef<str>) -> bool {
s.as_ref().contains(substr.as_ref())
}
pub fn HasPrefix(s: impl AsRef<str>, prefix: impl AsRef<str>) -> bool {
s.as_ref().starts_with(prefix.as_ref())
}
pub fn HasSuffix(s: impl AsRef<str>, suffix: impl AsRef<str>) -> bool {
s.as_ref().ends_with(suffix.as_ref())
}
pub fn Index(s: impl AsRef<str>, substr: impl AsRef<str>) -> int {
match s.as_ref().find(substr.as_ref()) {
Some(i) => i as int,
None => -1,
}
}
pub fn LastIndex(s: impl AsRef<str>, substr: impl AsRef<str>) -> int {
match s.as_ref().rfind(substr.as_ref()) {
Some(i) => i as int,
None => -1,
}
}
pub fn Count(s: impl AsRef<str>, substr: impl AsRef<str>) -> int {
let s = s.as_ref();
let substr = substr.as_ref();
if substr.is_empty() {
return (s.chars().count() + 1) as int;
}
s.matches(substr).count() as int
}
pub fn Split(s: impl AsRef<str>, sep: impl AsRef<str>) -> slice<string> {
let s = s.as_ref();
let sep = sep.as_ref();
if sep.is_empty() {
return s.chars().map(|c| c.to_string()).collect();
}
s.split(sep).map(String::from).collect()
}
pub fn SplitN(s: impl AsRef<str>, sep: impl AsRef<str>, n: int) -> slice<string> {
if n == 0 {
return slice::new();
}
let s = s.as_ref();
let sep = sep.as_ref();
if n < 0 {
return Split(s, sep);
}
s.splitn(n as usize, sep).map(String::from).collect()
}
pub fn Join(elems: &[string], sep: impl AsRef<str>) -> string {
elems.join(sep.as_ref())
}
pub fn Replace(s: impl AsRef<str>, old: impl AsRef<str>, new: impl AsRef<str>, n: int) -> string {
let s = s.as_ref();
let old = old.as_ref();
let new = new.as_ref();
if n < 0 {
s.replace(old, new)
} else {
s.replacen(old, new, n as usize)
}
}
pub fn ReplaceAll(s: impl AsRef<str>, old: impl AsRef<str>, new: impl AsRef<str>) -> string {
s.as_ref().replace(old.as_ref(), new.as_ref())
}
pub fn ToUpper(s: impl AsRef<str>) -> string {
s.as_ref().to_uppercase()
}
pub fn ToLower(s: impl AsRef<str>) -> string {
s.as_ref().to_lowercase()
}
pub fn TrimSpace(s: impl AsRef<str>) -> string {
s.as_ref().trim().to_string()
}
pub fn TrimPrefix(s: impl AsRef<str>, prefix: impl AsRef<str>) -> string {
let s = s.as_ref();
s.strip_prefix(prefix.as_ref()).unwrap_or(s).to_string()
}
pub fn TrimSuffix(s: impl AsRef<str>, suffix: impl AsRef<str>) -> string {
let s = s.as_ref();
s.strip_suffix(suffix.as_ref()).unwrap_or(s).to_string()
}
pub fn Trim(s: impl AsRef<str>, cutset: impl AsRef<str>) -> string {
let cutset = cutset.as_ref().to_string();
s.as_ref().trim_matches(|c: char| cutset.contains(c)).to_string()
}
pub fn Fields(s: impl AsRef<str>) -> slice<string> {
s.as_ref().split_whitespace().map(String::from).collect()
}
pub fn Repeat(s: impl AsRef<str>, count: int) -> string {
if count < 0 {
panic!("strings: negative Repeat count");
}
s.as_ref().repeat(count as usize)
}
pub fn EqualFold(s: impl AsRef<str>, t: impl AsRef<str>) -> bool {
s.as_ref().eq_ignore_ascii_case(t.as_ref())
}
#[derive(Debug, Clone, Default)]
pub struct Builder {
inner: string,
}
impl Builder {
pub fn new() -> Self { Builder::default() }
pub fn WriteString(&mut self, s: impl AsRef<str>) -> (int, crate::errors::error) {
let s = s.as_ref();
self.inner.push_str(s);
(s.len() as int, crate::errors::nil)
}
pub fn WriteByte(&mut self, b: crate::types::byte) -> crate::errors::error {
if b < 0x80 {
self.inner.push(b as char);
crate::errors::nil
} else {
crate::errors::New("strings.Builder: non-ASCII byte; use WriteRune")
}
}
pub fn WriteRune(&mut self, r: char) -> (int, crate::errors::error) {
let n = r.len_utf8();
self.inner.push(r);
(n as int, crate::errors::nil)
}
pub fn String(&self) -> string {
self.inner.clone()
}
pub fn Len(&self) -> int {
self.inner.len() as int
}
pub fn Reset(&mut self) {
self.inner.clear();
}
pub fn Grow(&mut self, n: int) {
if n > 0 {
self.inner.reserve(n as usize);
}
}
pub fn len(&self) -> usize {
self.inner.len()
}
}
impl std::fmt::Display for Builder {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.inner)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn contains_and_prefix() {
assert!(Contains("hello world", "world"));
assert!(!Contains("hello", "xyz"));
assert!(HasPrefix("foobar", "foo"));
assert!(HasSuffix("foobar", "bar"));
}
#[test]
fn index_returns_minus_one_when_absent() {
assert_eq!(Index("hello", "ll"), 2);
assert_eq!(Index("hello", "z"), -1);
assert_eq!(LastIndex("banana", "an"), 3);
}
#[test]
fn count_substr_and_empty() {
assert_eq!(Count("banana", "a"), 3);
assert_eq!(Count("xx", ""), 3); }
#[test]
fn split_and_join() {
let v = Split("a,b,c", ",");
assert_eq!(v, vec!["a", "b", "c"]);
assert_eq!(Join(&v, "-"), "a-b-c");
}
#[test]
fn split_n_caps_results() {
let v = SplitN("a,b,c,d", ",", 2);
assert_eq!(v, vec!["a", "b,c,d"]);
let v = SplitN("a,b,c", ",", -1);
assert_eq!(v.len(), 3);
let v = SplitN("a,b,c", ",", 0);
assert!(v.is_empty());
}
#[test]
fn replace_and_replace_all() {
assert_eq!(Replace("aaa", "a", "b", 2), "bba");
assert_eq!(ReplaceAll("aaa", "a", "b"), "bbb");
}
#[test]
fn case_change() {
assert_eq!(ToUpper("hello"), "HELLO");
assert_eq!(ToLower("HELLO"), "hello");
}
#[test]
fn trim_variants() {
assert_eq!(TrimSpace(" hi "), "hi");
assert_eq!(TrimPrefix("foobar", "foo"), "bar");
assert_eq!(TrimSuffix("foobar", "bar"), "foo");
assert_eq!(Trim("---abc--", "-"), "abc");
}
#[test]
fn fields_splits_on_whitespace() {
assert_eq!(Fields(" a b\tc\n"), vec!["a", "b", "c"]);
}
#[test]
fn repeat_and_equalfold() {
assert_eq!(Repeat("ab", 3), "ababab");
assert!(EqualFold("HELLO", "hello"));
assert!(!EqualFold("hello", "world"));
}
#[test]
fn builder_writes_and_resets() {
let mut b = Builder::new();
b.WriteString("hello ");
b.WriteString("world");
b.WriteByte(b'!');
b.WriteRune('λ');
assert_eq!(b.String(), "hello world!λ");
assert_eq!(b.Len(), "hello world!λ".len() as int);
b.Reset();
assert_eq!(b.Len(), 0);
}
#[test]
fn builder_writerune_returns_bytes_written() {
let mut b = Builder::new();
let (n, _) = b.WriteRune('a');
assert_eq!(n, 1);
let (n, _) = b.WriteRune('λ');
assert_eq!(n, 2);
let (n, _) = b.WriteRune('漢');
assert_eq!(n, 3);
}
}