pub fn to_upper<S: AsRef<str>>(s: S) -> String {
s.as_ref().to_uppercase()
}
pub fn to_lower<S: AsRef<str>>(s: S) -> String {
s.as_ref().to_lowercase()
}
pub fn trim<S: AsRef<str>>(s: S) -> String {
s.as_ref().trim().to_string()
}
pub fn trim_start<S: AsRef<str>>(s: S) -> String {
s.as_ref().trim_start().to_string()
}
pub fn trim_end<S: AsRef<str>>(s: S) -> String {
s.as_ref().trim_end().to_string()
}
pub fn split<S: AsRef<str>>(s: S, delimiter: &str) -> Vec<String> {
s.as_ref().split(delimiter).map(|s| s.to_string()).collect()
}
pub fn split_lines<S: AsRef<str>>(text: S) -> Vec<String> {
let mut lines: Vec<String> = Vec::new();
let mut current = String::new();
for ch in text.as_ref().chars() {
if ch == '\n' {
lines.push(current);
current = String::new();
} else if ch != '\r' {
current.push(ch);
}
}
if !current.is_empty() || text.as_ref().ends_with('\n') {
lines.push(current);
}
lines
}
pub fn split_whitespace<S: AsRef<str>>(line: S) -> Vec<String> {
let mut parts: Vec<String> = Vec::new();
let mut current = String::new();
let mut in_word = false;
for ch in line.as_ref().chars() {
if ch == ' ' || ch == '\t' {
if in_word {
parts.push(current);
current = String::new();
in_word = false;
}
} else {
current.push(ch);
in_word = true;
}
}
if in_word {
parts.push(current);
}
parts
}
pub fn parse_i32<S: AsRef<str>>(s: S) -> i32 {
s.as_ref().trim().parse().unwrap_or(0)
}
pub fn parse_f32<S: AsRef<str>>(s: S) -> f32 {
s.as_ref().trim().parse().unwrap_or(0.0)
}
pub fn parse_bool<S: AsRef<str>>(s: S) -> bool {
s.as_ref().trim() == "true"
}
pub fn byte_at<S: AsRef<str>>(s: S, index: usize) -> u8 {
s.as_ref().as_bytes().get(index).copied().unwrap_or(0)
}
pub fn join(parts: &[String], delimiter: &str) -> String {
parts.join(delimiter)
}
pub fn chars<S: AsRef<str>>(s: S) -> Vec<char> {
s.as_ref().chars().collect()
}
pub fn from_chars(parts: &[char]) -> String {
parts.iter().collect()
}
pub fn substring_chars<S: AsRef<str>>(s: S, start: usize, end: usize) -> String {
s.as_ref()
.chars()
.skip(start)
.take(end.saturating_sub(start))
.collect()
}
pub fn contains<S: AsRef<str>>(s: S, substring: &str) -> bool {
s.as_ref().contains(substring)
}
pub fn starts_with<S: AsRef<str>>(s: S, prefix: &str) -> bool {
s.as_ref().starts_with(prefix)
}
pub fn ends_with<S: AsRef<str>>(s: S, suffix: &str) -> bool {
s.as_ref().ends_with(suffix)
}
pub fn replace<S: AsRef<str>>(s: S, from: &str, to: &str) -> String {
s.as_ref().replace(from, to)
}
pub fn substring<S: AsRef<str>>(s: S, start: usize, end: usize) -> String {
s.as_ref()
.chars()
.skip(start)
.take(end.saturating_sub(start))
.collect()
}
pub fn len<S: AsRef<str>>(s: S) -> usize {
s.as_ref().len()
}
pub fn is_empty<S: AsRef<str>>(s: S) -> bool {
s.as_ref().is_empty()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_case_conversion() {
assert_eq!(to_upper("hello"), "HELLO");
assert_eq!(to_lower("WORLD"), "world");
}
#[test]
fn test_trim() {
assert_eq!(trim(" hello "), "hello");
}
#[test]
fn test_split_join() {
let parts = split("a,b,c", ",");
assert_eq!(parts, vec!["a", "b", "c"]);
assert_eq!(join(&parts, "-"), "a-b-c");
}
#[test]
fn test_split_lines() {
let lines = split_lines("a\nb\r\nc\n");
assert_eq!(lines, vec!["a", "b", "c", ""]);
}
#[test]
fn test_split_whitespace() {
let parts = split_whitespace(" hello\tworld ");
assert_eq!(parts, vec!["hello", "world"]);
}
#[test]
fn test_parse_i32() {
assert_eq!(parse_i32("-42"), -42);
assert_eq!(parse_i32(" 7 "), 7);
assert_eq!(parse_i32("bad"), 0);
}
#[test]
fn test_parse_f32() {
assert!((parse_f32("3.14") - 3.14).abs() < 0.001);
assert!((parse_f32("-2.5") - (-2.5)).abs() < 0.001);
}
#[test]
fn test_parse_bool() {
assert!(parse_bool("true"));
assert!(!parse_bool("false"));
}
#[test]
fn test_byte_at() {
assert_eq!(byte_at("ABC", 0), b'A');
assert_eq!(byte_at("ABC", 2), b'C');
assert_eq!(byte_at("ABC", 99), 0);
}
#[test]
fn test_chars_and_from_chars() {
let c = chars("hello");
assert_eq!(c, vec!['h', 'e', 'l', 'l', 'o']);
assert_eq!(from_chars(&c), "hello");
assert_eq!(substring_chars("hello", 1, 4), "ell");
}
}