datex_core/values/core_values/
text.rs

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