doge_runtime/stdlib/
strings.rs1use crate::error::{DogeError, DogeResult};
2use crate::value::Value;
3
4fn str_arg<'a>(fname: &str, v: &'a Value) -> DogeResult<&'a str> {
7 crate::stdlib::str_arg("strings", fname, v)
8}
9
10pub fn strings_beeg(s: &Value) -> DogeResult {
12 Ok(Value::str(str_arg("beeg", s)?.to_uppercase()))
13}
14
15pub fn strings_smoll(s: &Value) -> DogeResult {
17 Ok(Value::str(str_arg("smoll", s)?.to_lowercase()))
18}
19
20pub fn strings_trim(s: &Value) -> DogeResult {
22 Ok(Value::str(str_arg("trim", s)?.trim()))
23}
24
25pub fn strings_split(s: &Value, sep: &Value) -> DogeResult {
29 let s = str_arg("split", s)?;
30 let sep = str_arg("split", sep)?;
31 if sep.is_empty() {
32 return Err(DogeError::value_error("cannot split on an empty Str"));
33 }
34 Ok(Value::list(s.split(sep).map(Value::str).collect()))
35}
36
37pub fn strings_join(parts: &Value, sep: &Value) -> DogeResult {
40 let items = match parts {
41 Value::List(items) => items.borrow(),
42 _ => return Err(DogeError::type_error("strings.join needs a List of Str")),
43 };
44 let mut pieces = Vec::with_capacity(items.len());
45 for item in items.iter() {
46 match item {
47 Value::Str(piece) => pieces.push(piece.to_string()),
48 _ => return Err(DogeError::type_error("strings.join needs a List of Str")),
49 }
50 }
51 let sep = str_arg("join", sep)?;
52 Ok(Value::str(pieces.join(sep)))
53}
54
55pub fn strings_contains(s: &Value, needle: &Value) -> DogeResult {
57 let s = str_arg("contains", s)?;
58 let needle = str_arg("contains", needle)?;
59 Ok(Value::Bool(s.contains(needle)))
60}
61
62pub fn strings_index(s: &Value, sub: &Value) -> DogeResult {
66 let s = str_arg("index", s)?;
67 let sub = str_arg("index", sub)?;
68 let offset = match s.find(sub) {
69 Some(byte_pos) => s[..byte_pos].chars().count() as i64,
70 None => -1,
71 };
72 Ok(Value::int(offset))
73}
74
75pub fn strings_replace(s: &Value, from: &Value, to: &Value) -> DogeResult {
78 let s = str_arg("replace", s)?;
79 let from = str_arg("replace", from)?;
80 let to = str_arg("replace", to)?;
81 if from.is_empty() {
82 return Err(DogeError::value_error("cannot replace an empty Str"));
83 }
84 Ok(Value::str(s.replace(from, to)))
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90 use crate::error::ErrorKind;
91
92 #[test]
93 fn case_and_trim() {
94 assert!(matches!(strings_beeg(&Value::str("wow")).unwrap(), Value::Str(s) if &*s == "WOW"));
95 assert!(
96 matches!(strings_smoll(&Value::str("WOW")).unwrap(), Value::Str(s) if &*s == "wow")
97 );
98 assert!(
99 matches!(strings_trim(&Value::str(" hi ")).unwrap(), Value::Str(s) if &*s == "hi")
100 );
101 }
102
103 #[test]
104 fn split_keeps_empty_pieces_and_rejects_empty_sep() {
105 let parts = strings_split(&Value::str("a,,b"), &Value::str(",")).unwrap();
106 match parts {
107 Value::List(items) => assert_eq!(items.borrow().len(), 3),
108 _ => panic!("expected a list"),
109 }
110 assert_eq!(
111 strings_split(&Value::str("a"), &Value::str(""))
112 .unwrap_err()
113 .kind,
114 ErrorKind::ValueError
115 );
116 }
117
118 #[test]
119 fn join_needs_all_strs() {
120 let parts = Value::list(vec![Value::str("much"), Value::str("wow")]);
121 assert!(
122 matches!(strings_join(&parts, &Value::str(" ")).unwrap(), Value::Str(s) if &*s == "much wow")
123 );
124 let mixed = Value::list(vec![Value::str("a"), Value::int(1)]);
125 assert_eq!(
126 strings_join(&mixed, &Value::str(" ")).unwrap_err().kind,
127 ErrorKind::TypeError
128 );
129 }
130
131 #[test]
132 fn contains_and_replace() {
133 assert!(matches!(
134 strings_contains(&Value::str("kabosu"), &Value::str("bos")).unwrap(),
135 Value::Bool(true)
136 ));
137 assert!(
138 matches!(strings_replace(&Value::str("a-b-c"), &Value::str("-"), &Value::str("_")).unwrap(), Value::Str(s) if &*s == "a_b_c")
139 );
140 assert_eq!(
141 strings_replace(&Value::str("x"), &Value::str(""), &Value::str("y"))
142 .unwrap_err()
143 .kind,
144 ErrorKind::ValueError
145 );
146 }
147
148 #[test]
149 fn index_returns_char_offset_or_minus_one() {
150 use bigdecimal::ToPrimitive;
151 let at = |s, sub| match strings_index(&Value::str(s), &Value::str(sub)).unwrap() {
152 Value::Int(n) => n.to_i64().unwrap(),
153 _ => panic!("expected an Int"),
154 };
155 assert_eq!(at("kabosu", "bos"), 2);
156 assert_eq!(at("kabosu", "zzz"), -1);
157 assert_eq!(at("héllo", "llo"), 2);
159 }
160
161 #[test]
162 fn non_str_subject_is_a_type_error() {
163 assert_eq!(
164 strings_trim(&Value::int(1)).unwrap_err().kind,
165 ErrorKind::TypeError
166 );
167 }
168}