godot_core/builtin/string/
string_macros.rs

1/*
2 * Copyright (c) godot-rust; Bromeon and contributors.
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
6 */
7
8#[macro_export]
9macro_rules! impl_shared_string_api {
10    (
11        builtin: $Builtin:ty,
12        find_builder: $FindBuilder:ident,
13        split_builder: $SplitBuilder:ident,
14    ) => {
15        // --------------------------------------------------------------------------------------------------------------------------------------
16        // Extending the builtin itself
17
18        /// Manually-declared, shared methods between `GString` and `StringName`.
19        impl $Builtin {
20            /// Returns the Unicode code point ("character") at position `index`.
21            ///
22            /// # Panics
23            /// In debug builds, if `index` is out of bounds. In Release builds, `0` is returned instead.
24            // Unicode conversion panic is not documented because we rely on Godot strings having valid Unicode.
25            // TODO implement Index/IndexMut (for GString; StringName may have varying reprs).
26            pub fn unicode_at(&self, index: usize) -> char {
27                debug_assert!(index < self.len(), "unicode_at: index {} out of bounds (len {})", index, self.len());
28
29                let char_i64 = self.as_inner().unicode_at(index as i64);
30
31                u32::try_from(char_i64).ok()
32                    .and_then(|char_u32| char::from_u32(char_u32))
33                    .unwrap_or_else(|| {
34                        panic!("cannot map Unicode code point (value {char_i64}) to char (at position {index})")
35                    })
36            }
37
38            /// Find first occurrence of `what` and return index, or `None` if not found.
39            ///
40            /// Check [`find_ex()`](Self::find_ex) for all custom options.
41            pub fn find(&self, what: impl AsArg<GString>) -> Option<usize> {
42                self.find_ex(what).done()
43            }
44
45            /// Returns a builder for finding substrings, with various configuration options.
46            ///
47            /// The builder struct offers methods to configure 3 dimensions, which map to different Godot functions in the back:
48            ///
49            /// | Method        | Default behavior                          | Behavior after method call         |
50            /// |---------------|-------------------------------------------|------------------------------------|
51            /// | `r()`         | forward search (`find*`)                  | backward search (`rfind*`)         |
52            /// | `n()`         | case-sensitive search (`*find`)           | case-insensitive search (`*findn`) |
53            /// | `from(index)` | search from beginning (or end if reverse) | search from specified index        |
54            ///
55            /// Returns `Some(index)` of the first occurrence, or `None` if not found.
56            ///
57            /// # Example
58            /// To find the substring `"O"` in `"Hello World"`, not considering case and starting from position 5, you can write:
59            /// ```no_run
60            /// # use godot::prelude::*;
61            /// # fn do_sth_with_index(_i: usize) {}
62            /// let s = GString::from("Hello World");
63            /// if let Some(found) = s.find_ex("O").n().from(5).done() {
64            ///    do_sth_with_index(found)
65            /// }
66            /// ```
67            /// This is equivalent to the following GDScript code:
68            /// ```gdscript
69            /// var s: GString = "Hello World"
70            /// var found = s.findn("O", 5)
71            /// if found != -1:
72            ///     do_sth_with_index(found)
73            /// ```
74            #[doc(alias = "findn", alias = "rfind", alias = "rfindn")]
75            pub fn find_ex<'s, 'w>(
76                &'s self,
77                what: impl AsArg<GString> + 'w,
78            ) -> $FindBuilder<'s, 'w> {
79                $FindBuilder::new(self, what.into_arg())
80            }
81
82            /// Count how many times `what` appears within `range`. Use `..` for full string search.
83            pub fn count(&self, what: impl AsArg<GString>, range: impl std::ops::RangeBounds<usize>) -> usize {
84                let (from, to) = super::to_godot_fromto(range);
85                self.as_inner().count(what, from, to) as usize
86            }
87
88            /// Count how many times `what` appears within `range`, case-insensitively. Use `..` for full string search.
89            pub fn countn(&self, what: impl AsArg<GString>, range: impl std::ops::RangeBounds<usize>) -> usize {
90                let (from, to) = super::to_godot_fromto(range);
91                self.as_inner().countn(what, from, to) as usize
92            }
93
94            /// Splits the string according to `delimiter`.
95            ///
96            /// See [`split_ex()`][Self::split_ex] if you need further configuration.
97            pub fn split(&self, delimiter: impl AsArg<GString>) -> $crate::builtin::PackedStringArray {
98                self.split_ex(delimiter).done()
99            }
100
101            /// Returns a builder that splits this string into substrings using `delimiter`.
102            ///
103            /// If `delimiter` is an empty string, each substring will be a single character.
104            ///
105            /// The builder struct offers methods to configure multiple dimensions. Note that `rsplit` in Godot is not useful without the
106            /// `maxsplit` argument, so the two are combined in Rust as `maxsplit_r`.
107            ///
108            /// | Method             | Default behavior       | Behavior after method call                        |
109            /// |--------------------|------------------------|---------------------------------------------------|
110            /// | `disallow_empty()` | allows empty parts     | empty parts are removed from result               |
111            /// | `maxsplit(n)`      | entire string is split | splits `n` times -> `n+1` parts                   |
112            /// | `maxsplit_r(n)`    | entire string is split | splits `n` times -> `n+1` parts (start from back) |
113            #[doc(alias = "rsplit")]
114            pub fn split_ex<'s, 'w>(
115                &'s self,
116                delimiter: impl AsArg<GString> + 'w,
117            ) -> $SplitBuilder<'s, 'w> {
118                $SplitBuilder::new(self, delimiter.into_arg())
119            }
120
121            /// Returns a substring of this, as another `GString`.
122            // TODO is there no efficient way to implement this for StringName by interning?
123            pub fn substr(&self, range: impl std::ops::RangeBounds<usize>) -> GString {
124                let (from, len) = super::to_godot_fromlen_neg1(range);
125
126                self.as_inner().substr(from, len)
127            }
128
129            /// Splits the string using a string delimiter and returns the substring at index `slice`.
130            ///
131            /// Returns the original string if delimiter does not occur in the string. Returns `None` if `slice` is out of bounds.
132            ///
133            /// This is faster than [`split()`][Self::split], if you only need one substring.
134            pub fn get_slice(
135                &self,
136                delimiter: impl AsArg<GString>,
137                slice: usize,
138            ) -> Option<GString> {
139                let sliced = self.as_inner().get_slice(delimiter, slice as i64);
140
141                // Note: self="" always returns None.
142                super::populated_or_none(sliced)
143            }
144
145            /// Splits the string using a Unicode char `delimiter` and returns the substring at index `slice`.
146            ///
147            /// Returns the original string if delimiter does not occur in the string. Returns `None` if `slice` is out of bounds.
148            ///
149            /// This is faster than [`split()`][Self::split], if you only need one substring.
150            pub fn get_slicec(&self, delimiter: char, slice: usize) -> Option<GString> {
151                let sliced = self.as_inner().get_slicec(delimiter as i64, slice as i64);
152
153                // Note: self="" always returns None.
154                super::populated_or_none(sliced)
155            }
156
157            /// Returns the total number of slices, when the string is split with the given delimiter.
158            ///
159            /// See also [`split()`][Self::split] and [`get_slice()`][Self::get_slice].
160            pub fn get_slice_count(&self, delimiter: impl AsArg<GString>) -> usize {
161                self.as_inner().get_slice_count(delimiter) as usize
162            }
163
164            /// Returns a copy of the string without the specified index range.
165            pub fn erase(&self, range: impl std::ops::RangeBounds<usize>) -> GString {
166                let (from, len) = super::to_godot_fromlen_i32max(range);
167                self.as_inner().erase(from, len)
168            }
169
170            /// Returns a copy of the string with an additional string inserted at the given position.
171            ///
172            /// If the position is out of bounds, the string will be inserted at the end.
173            ///
174            /// Consider using [`format()`](Self::format) for more flexibility.
175            pub fn insert(&self, position: usize, what: impl AsArg<GString>) -> GString {
176                self.as_inner().insert(position as i64, what)
177            }
178
179            /// Format a string using substitutions from an array or dictionary.
180            ///
181            /// See Godot's [`String.format()`](https://docs.godotengine.org/en/stable/classes/class_string.html#class-string-method-format).
182            pub fn format(&self, array_or_dict: &Variant) -> GString {
183                self.as_inner().format(array_or_dict, "{_}")
184            }
185
186            /// Format a string using substitutions from an array or dictionary + custom placeholder.
187            ///
188            /// See Godot's [`String.format()`](https://docs.godotengine.org/en/stable/classes/class_string.html#class-string-method-format).
189            pub fn format_with_placeholder(
190                &self,
191                array_or_dict: &Variant,
192                placeholder: impl AsArg<GString>,
193            ) -> GString {
194                self.as_inner().format(array_or_dict, placeholder)
195            }
196
197            // left() + right() are not redefined, as their i64 can be negative.
198
199            /// Formats the string to be at least `min_length` long, by adding characters to the left of the string, if necessary.
200            ///
201            /// Godot itself allows padding with multiple characters, but that behavior is not very useful, because `min_length` isn't
202            /// respected in that case. The parameter in Godot is even called `character`. In Rust, we directly expose `char` instead.
203            ///
204            /// See also [`rpad()`](Self::rpad).
205            pub fn lpad(&self, min_length: usize, character: char) -> GString {
206                let one_char_string = GString::from([character].as_slice());
207                self.as_inner().lpad(min_length as i64, &one_char_string)
208            }
209
210            /// Formats the string to be at least `min_length` long, by adding characters to the right of the string, if necessary.
211            ///
212            /// Godot itself allows padding with multiple characters, but that behavior is not very useful, because `min_length` isn't
213            /// respected in that case. The parameter in Godot is even called `character`. In Rust, we directly expose `char` instead.
214            ///
215            /// See also [`lpad()`](Self::lpad).
216            pub fn rpad(&self, min_length: usize, character: char) -> GString {
217                let one_char_string = GString::from([character].as_slice());
218                self.as_inner().rpad(min_length as i64, &one_char_string)
219            }
220
221            /// Formats the string representing a number to have an exact number of `digits` _after_ the decimal point.
222            pub fn pad_decimals(&self, digits: usize) -> GString {
223                self.as_inner().pad_decimals(digits as i64)
224            }
225
226            /// Formats the string representing a number to have an exact number of `digits` _before_ the decimal point.
227            pub fn pad_zeros(&self, digits: usize) -> GString {
228                self.as_inner().pad_zeros(digits as i64)
229            }
230
231            /// Case-sensitive, lexicographic comparison to another string.
232            ///
233            /// Returns the `Ordering` relation of `self` towards `to`. Ordering is determined by the Unicode code points of each string, which
234            /// roughly matches the alphabetical order.
235            ///
236            /// See also [`nocasecmp_to()`](Self::nocasecmp_to), [`naturalcasecmp_to()`](Self::naturalcasecmp_to), [`filecasecmp_to()`](Self::filecasecmp_to).
237            pub fn casecmp_to(&self, to: impl AsArg<GString>) -> std::cmp::Ordering {
238                sys::i64_to_ordering(self.as_inner().casecmp_to(to))
239            }
240
241            /// Case-**insensitive**, lexicographic comparison to another string.
242            ///
243            /// Returns the `Ordering` relation of `self` towards `to`. Ordering is determined by the Unicode code points of each string, which
244            /// roughly matches the alphabetical order.
245            ///
246            /// See also [`casecmp_to()`](Self::casecmp_to), [`naturalcasecmp_to()`](Self::naturalcasecmp_to), [`filecasecmp_to()`](Self::filecasecmp_to).
247            pub fn nocasecmp_to(&self, to: impl AsArg<GString>) -> std::cmp::Ordering {
248                sys::i64_to_ordering(self.as_inner().nocasecmp_to(to))
249            }
250
251            /// Case-sensitive, **natural-order** comparison to another string.
252            ///
253            /// Returns the `Ordering` relation of `self` towards `to`. Ordering is determined by the Unicode code points of each string, which
254            /// roughly matches the alphabetical order.
255            ///
256            /// When used for sorting, natural order comparison orders sequences of numbers by the combined value of each digit as is often
257            /// expected, instead of the single digit's value. A sorted sequence of numbered strings will be `["1", "2", "3", ...]`, not
258            /// `["1", "10", "2", "3", ...]`.
259            ///
260            /// With different string lengths, returns `Ordering::Greater` if this string is longer than the `to` string, or `Ordering::Less`
261            /// if shorter.
262            ///
263            /// See also [`casecmp_to()`](Self::casecmp_to), [`naturalnocasecmp_to()`](Self::naturalnocasecmp_to), [`filecasecmp_to()`](Self::filecasecmp_to).
264            pub fn naturalcasecmp_to(&self, to: impl AsArg<GString>) -> std::cmp::Ordering {
265                sys::i64_to_ordering(self.as_inner().naturalcasecmp_to(to))
266            }
267
268            /// Case-insensitive, **natural-order** comparison to another string.
269            ///
270            /// Returns the `Ordering` relation of `self` towards `to`. Ordering is determined by the Unicode code points of each string, which
271            /// roughly matches the alphabetical order.
272            ///
273            /// When used for sorting, natural order comparison orders sequences of numbers by the combined value of each digit as is often
274            /// expected, instead of the single digit's value. A sorted sequence of numbered strings will be `["1", "2", "3", ...]`, not
275            /// `["1", "10", "2", "3", ...]`.
276            ///
277            /// With different string lengths, returns `Ordering::Greater` if this string is longer than the `to` string, or `Ordering::Less`
278            /// if shorter.
279            ///
280            /// See also [`casecmp_to()`](Self::casecmp_to), [`naturalcasecmp_to()`](Self::naturalcasecmp_to), [`filecasecmp_to()`](Self::filecasecmp_to).
281            pub fn naturalnocasecmp_to(&self, to: impl AsArg<GString>) -> std::cmp::Ordering {
282                sys::i64_to_ordering(self.as_inner().naturalnocasecmp_to(to))
283            }
284
285            /// Case-sensitive, filename-oriented comparison to another string.
286            ///
287            /// Like [`naturalcasecmp_to()`][Self::naturalcasecmp_to], but prioritizes strings that begin with periods (`.`) and underscores
288            /// (`_`) before any other character. Useful when sorting folders or file names.
289            ///
290            /// See also [`casecmp_to()`](Self::casecmp_to), [`naturalcasecmp_to()`](Self::naturalcasecmp_to), [`filenocasecmp_to()`](Self::filenocasecmp_to).
291            #[cfg(since_api = "4.3")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.3")))]
292            pub fn filecasecmp_to(&self, to: impl AsArg<GString>) -> std::cmp::Ordering {
293                sys::i64_to_ordering(self.as_inner().filecasecmp_to(to))
294            }
295
296            /// Case-insensitive, filename-oriented comparison to another string.
297            ///
298            /// Like [`naturalnocasecmp_to()`][Self::naturalnocasecmp_to], but prioritizes strings that begin with periods (`.`) and underscores
299            /// (`_`) before any other character. Useful when sorting folders or file names.
300            ///
301            /// See also [`casecmp_to()`](Self::casecmp_to), [`naturalcasecmp_to()`](Self::naturalcasecmp_to), [`filecasecmp_to()`](Self::filecasecmp_to).
302            #[cfg(since_api = "4.3")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.3")))]
303            pub fn filenocasecmp_to(&self, to: impl AsArg<GString>) -> std::cmp::Ordering {
304                sys::i64_to_ordering(self.as_inner().filenocasecmp_to(to))
305            }
306
307            /// Simple expression match (also called "glob" or "globbing"), where `*` matches zero or more arbitrary characters and `?`
308            /// matches any single character except a period (`.`).
309            ///
310            /// An empty string or empty expression always evaluates to `false`.
311            ///
312            /// Renamed from `match` because of collision with Rust keyword + possible confusion with `String::matches()` that can match regex.
313            #[doc(alias = "match")]
314            pub fn match_glob(&self, pattern: impl AsArg<GString>) -> bool {
315                self.as_inner().match_(pattern)
316            }
317
318            /// Simple **case-insensitive** expression match (also called "glob" or "globbing"), where `*` matches zero or more arbitrary
319            /// characters and `?` matches any single character except a period (`.`).
320            ///
321            /// An empty string or empty expression always evaluates to `false`.
322            ///
323            /// Renamed from `matchn` because of collision with Rust keyword + possible confusion with `String::matches()` that can match regex.
324            #[doc(alias = "matchn")]
325            pub fn matchn_glob(&self, pattern: impl AsArg<GString>) -> bool {
326                self.as_inner().matchn(pattern)
327            }
328        }
329
330        // --------------------------------------------------------------------------------------------------------------------------------------
331        // find() support
332
333        #[doc = concat!("Builder for [`", stringify!($Builtin), "::find_ex()`][", stringify!($Builtin), "::find_ex].")]
334        #[must_use]
335        pub struct $FindBuilder<'s, 'w> {
336            owner: &'s $Builtin,
337            what: meta::CowArg<'w, GString>,
338            reverse: bool,
339            case_insensitive: bool,
340            from_index: Option<usize>,
341        }
342
343        impl<'s, 'w> $FindBuilder<'s, 'w> {
344            pub(crate) fn new(owner: &'s $Builtin, what: meta::CowArg<'w, GString>) -> Self {
345                Self {
346                    owner,
347                    what,
348                    reverse: false,
349                    case_insensitive: false,
350                    from_index: None,
351                }
352            }
353
354            /// Reverse search direction (start at back).
355            pub fn r(self) -> Self {
356                Self {
357                    reverse: true,
358                    ..self
359                }
360            }
361
362            /// Case-**insensitive** search.
363            pub fn n(self) -> Self {
364                Self {
365                    case_insensitive: true,
366                    ..self
367                }
368            }
369
370            /// Start index -- begin search here rather than at start/end of string.
371            pub fn from(self, index: usize) -> Self {
372                Self {
373                    from_index: Some(index),
374                    ..self
375                }
376            }
377
378            /// Does the actual work. Must be called to finalize find operation.
379            pub fn done(self) -> Option<usize> {
380                let from_index = self.from_index.map(|i| i as i64);
381                let inner = self.owner.as_inner();
382                let what = self.what;
383
384                let godot_found = if self.reverse {
385                    let from_index = from_index.unwrap_or(-1);
386
387                    if self.case_insensitive {
388                        inner.rfindn(what, from_index)
389                    } else {
390                        inner.rfind(what, from_index)
391                    }
392                } else {
393                    let from_index = from_index.unwrap_or(0);
394
395                    if self.case_insensitive {
396                        inner.findn(what, from_index)
397                    } else {
398                        inner.find(what, from_index)
399                    }
400                };
401
402                super::found_to_option(godot_found)
403            }
404        }
405
406        // --------------------------------------------------------------------------------------------------------------------------------------
407        // split() support
408
409        #[doc = concat!("Builder for [`", stringify!($Builtin), "::split_ex()`][", stringify!($Builtin), "::split_ex].")]
410        #[must_use]
411        pub struct $SplitBuilder<'s, 'w> {
412            owner: &'s $Builtin,
413            delimiter: meta::CowArg<'w, GString>,
414            reverse: bool,
415            allow_empty: bool,
416            maxsplit: usize,
417        }
418
419        impl<'s, 'w> $SplitBuilder<'s, 'w> {
420            pub(crate) fn new(owner: &'s $Builtin, delimiter: meta::CowArg<'w, GString>) -> Self {
421                Self {
422                    owner,
423                    delimiter,
424                    reverse: false,
425                    allow_empty: true,
426                    maxsplit: 0,
427                }
428            }
429
430            /// After calling this method, empty strings between adjacent delimiters are excluded from the array.
431            pub fn disallow_empty(self) -> Self {
432                Self {
433                    allow_empty: false,
434                    ..self
435                }
436            }
437
438            /// Limit number of splits (forward mode).
439            ///
440            /// If `maxsplit` is greater than 0, the number of splits may not exceed `maxsplit`. By default, the entire string is split.
441            ///
442            /// Note that `number_of_splits` refers to the number of times a split occurs, which is the resulting element count **minus one**.
443            pub fn maxsplit(self, number_of_splits: usize) -> Self {
444                Self {
445                    maxsplit: number_of_splits,
446                    ..self
447                }
448            }
449
450            /// Limit number of splits (reverse mode).
451            ///
452            /// If `maxsplit` is greater than 0, the number of splits may not exceed `maxsplit`. By default, the entire string is split.
453            ///
454            /// Note that `number_of_splits` refers to the number of times a split occurs, which is the resulting element count **minus one**.
455            pub fn maxsplit_r(self, number_of_splits: usize) -> Self {
456                Self {
457                    maxsplit: number_of_splits,
458                    reverse: true,
459                    ..self
460                }
461            }
462
463            /// Does the actual work. Must be called to finalize split operation.
464            pub fn done(self) -> $crate::builtin::PackedStringArray {
465                let inner = self.owner.as_inner();
466                let delimiter = self.delimiter;
467
468                if self.reverse {
469                    inner.rsplit(delimiter, self.allow_empty, self.maxsplit as i64)
470                } else {
471                    inner.split(delimiter, self.allow_empty, self.maxsplit as i64)
472                }
473            }
474        }
475    };
476}