Skip to main content

datex_core/values/core_values/
text.rs

1use crate::{
2    prelude::*, shared_values::shared_container::IndexOutOfBoundsError,
3    traits::structural_eq::StructuralEq,
4};
5use core::{
6    fmt::Display,
7    ops::{Add, AddAssign},
8    result::Result,
9};
10use serde::{Deserialize, Serialize};
11
12#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
13pub struct Text(pub String);
14
15impl Display for Text {
16    // TODO #319: escape string content
17    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
18        core::write!(f, "\"{}\"", self.0)
19    }
20}
21
22impl Text {
23    pub fn length(&self) -> usize {
24        self.0.len()
25    }
26    pub fn to_uppercase(&self) -> Text {
27        Text(self.0.to_uppercase())
28    }
29    pub fn to_lowercase(&self) -> Text {
30        Text(self.0.to_lowercase())
31    }
32    pub fn as_str(&self) -> &str {
33        &self.0
34    }
35    pub fn as_string(&self) -> String {
36        self.0.clone()
37    }
38    pub fn char_at(&self, index: i64) -> Result<char, IndexOutOfBoundsError> {
39        let index = self.wrap_index(index);
40        self.0.chars().nth(index).ok_or(IndexOutOfBoundsError {
41            index: index as u32,
42        })
43    }
44
45    #[inline]
46    fn wrap_index(&self, index: i64) -> usize {
47        if index < 0 {
48            let len = self.0.chars().count() as i64;
49            (len + index) as usize
50        } else {
51            index as usize
52        }
53    }
54    #[inline]
55    fn get_valid_index(
56        &self,
57        index: i64,
58    ) -> Result<usize, IndexOutOfBoundsError> {
59        let index = self.wrap_index(index);
60        if (index) < self.0.len() {
61            Ok(index)
62        } else {
63            Err(IndexOutOfBoundsError {
64                index: index as u32,
65            })
66        }
67    }
68
69    pub fn substring(&self, start: usize, end: usize) -> Option<Text> {
70        if start > end || end > self.0.len() {
71            return None;
72        }
73        Some(Text(self.0[start..end].to_string()))
74    }
75    pub fn contains(&self, substring: &str) -> bool {
76        self.0.contains(substring)
77    }
78    pub fn starts_with(&self, prefix: &str) -> bool {
79        self.0.starts_with(prefix)
80    }
81    pub fn ends_with(&self, suffix: &str) -> bool {
82        self.0.ends_with(suffix)
83    }
84    pub fn index_of(&self, substring: &str) -> Option<usize> {
85        self.0.find(substring)
86    }
87    pub fn last_index_of(&self, substring: &str) -> Option<usize> {
88        self.0.rfind(substring)
89    }
90    pub fn is_empty(&self) -> bool {
91        self.0.is_empty()
92    }
93    pub fn trim(&self) -> Text {
94        Text(self.0.trim().to_string())
95    }
96    pub fn trim_start(&self) -> Text {
97        Text(self.0.trim_start().to_string())
98    }
99    pub fn split(&self, delimiter: &str) -> Vec<Text> {
100        self.0
101            .split(delimiter)
102            .map(|s| Text(s.to_string()))
103            .collect()
104    }
105    pub fn join(texts: &[Text], separator: &str) -> Text {
106        let joined = texts
107            .iter()
108            .map(|t| t.0.as_str())
109            .collect::<Vec<&str>>()
110            .join(separator);
111        Text(joined)
112    }
113    pub fn repeat(&self, n: usize) -> Text {
114        Text(self.0.repeat(n))
115    }
116}
117
118// modifiers
119impl Text {
120    pub fn clear(&mut self) {
121        self.0.clear();
122    }
123    pub fn reverse(&mut self) {
124        let reversed = self.0.chars().rev().collect::<String>();
125        self.0 = reversed;
126    }
127    pub fn push_str(&mut self, s: &str) {
128        self.0.push_str(s);
129    }
130    pub fn push_char(&mut self, c: char) {
131        self.0.push(c);
132    }
133    pub fn pop_char(&mut self) -> Option<char> {
134        self.0.pop()
135    }
136    pub fn insert(&mut self, index: usize, s: &str) -> Result<(), String> {
137        if index > self.0.len() {
138            return Err("Index out of bounds".to_string());
139        }
140        self.0.insert_str(index, s);
141        Ok(())
142    }
143    // TODO #320: Add proper error handling, also for insert and other analog to MapAccessError
144    pub fn remove(&mut self, index: usize) -> Result<char, String> {
145        if index >= self.0.len() {
146            return Err("Index out of bounds".to_string());
147        }
148        Ok(self.0.remove(index))
149    }
150    pub fn replace(&mut self, from: &str, to: &str) {
151        self.0 = self.0.replace(from, to);
152    }
153    pub fn replace_range(
154        &mut self,
155        range: core::ops::Range<usize>,
156        replace_with: &str,
157    ) -> Result<(), String> {
158        if range.start > range.end || range.end > self.0.len() {
159            return Err("Range out of bounds".to_string());
160        }
161        self.0.replace_range(range, replace_with);
162        Ok(())
163    }
164    pub fn set_char_at(
165        &mut self,
166        index: i64,
167        c: char,
168    ) -> Result<(), IndexOutOfBoundsError> {
169        let index = self.get_valid_index(index)?;
170        let mut chars: Vec<char> = self.0.chars().collect();
171        chars[index] = c;
172        self.0 = chars.iter().collect();
173        Ok(())
174    }
175}
176
177impl StructuralEq for Text {
178    fn structural_eq(&self, other: &Self) -> bool {
179        self.0 == other.0
180    }
181}
182
183/// The froms are used for this magic. This will automatically convert
184/// the Rust types to Text when using the += operator.
185impl From<&str> for Text {
186    fn from(s: &str) -> Self {
187        Text(s.to_string())
188    }
189}
190
191impl From<String> for Text {
192    fn from(s: String) -> Self {
193        Text(s)
194    }
195}
196
197/// Allow TypedDatexValue<Text> += String and TypedDatexValue<Text> += &str
198/// This can never panic since the Text::from from string will always succeed
199impl AddAssign<Text> for Text {
200    fn add_assign(&mut self, rhs: Text) {
201        self.0 += &rhs.0;
202    }
203}
204
205impl Add for Text {
206    type Output = Self;
207
208    fn add(self, rhs: Self) -> Self::Output {
209        Text(self.0 + &rhs.0)
210    }
211}
212
213impl Add for &Text {
214    type Output = Text;
215
216    fn add(self, rhs: Self) -> Self::Output {
217        Text(self.0.clone() + &rhs.0)
218    }
219}
220
221impl Add<Text> for &Text {
222    type Output = Text;
223
224    fn add(self, rhs: Text) -> Self::Output {
225        Text(self.0.clone() + &rhs.0)
226    }
227}
228
229impl Add<&Text> for Text {
230    type Output = Text;
231
232    fn add(self, rhs: &Text) -> Self::Output {
233        Text(self.0 + &rhs.0)
234    }
235}
236
237impl Add<&str> for Text {
238    type Output = Text;
239
240    fn add(self, rhs: &str) -> Self::Output {
241        Text(self.0 + rhs)
242    }
243}