Skip to content

Commit f70341d

Browse files
committed
GString/StringName: custom find() + split(); shared macro code reuse
1 parent c011aef commit f70341d

File tree

8 files changed

+468
-191
lines changed

8 files changed

+468
-191
lines changed

Changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Cutting-edge API docs of the `master` branch are available [here](https://godot-
1414
- [v0.1.1](#v011), [v0.1.2](#v012), [v0.1.3](#v013)
1515

1616

17+
1718
## [v0.2.1](https://docs.rs/godot/0.2.1)
1819

1920
_8 December 2024_

godot-codegen/src/special_cases/special_cases.rs

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -235,12 +235,8 @@ pub fn is_builtin_method_exposed(builtin_ty: &TyName, godot_method_name: &str) -
235235
// TODO maybe consider renaming "match_" -> "matches". The "*n" could technically be "*_n", but is probably OK.
236236

237237
// GString
238-
| ("String", "find")
239-
| ("String", "findn")
240238
| ("String", "count")
241239
| ("String", "countn")
242-
| ("String", "rfind")
243-
| ("String", "rfindn")
244240
| ("String", "match")
245241
| ("String", "matchn")
246242
| ("String", "begins_with")
@@ -259,8 +255,6 @@ pub fn is_builtin_method_exposed(builtin_ty: &TyName, godot_method_name: &str) -
259255
| ("String", "to_camel_case")
260256
| ("String", "to_pascal_case")
261257
| ("String", "to_snake_case")
262-
| ("String", "split")
263-
| ("String", "rsplit")
264258
| ("String", "split_floats")
265259
| ("String", "join")
266260
| ("String", "to_upper")
@@ -331,21 +325,8 @@ pub fn is_builtin_method_exposed(builtin_ty: &TyName, godot_method_name: &str) -
331325
| ("String", "humanize_size")
332326

333327
// StringName
334-
| ("StringName", "casecmp_to")
335-
| ("StringName", "nocasecmp_to")
336-
| ("StringName", "naturalcasecmp_to")
337-
| ("StringName", "naturalnocasecmp_to")
338-
| ("StringName", "filecasecmp_to")
339-
| ("StringName", "filenocasecmp_to")
340-
| ("StringName", "get_slice")
341-
| ("StringName", "get_slicec")
342-
| ("StringName", "get_slice_count")
343-
| ("StringName", "find")
344-
| ("StringName", "findn")
345328
| ("StringName", "count")
346329
| ("StringName", "countn")
347-
| ("StringName", "rfind")
348-
| ("StringName", "rfindn")
349330
| ("StringName", "match")
350331
| ("StringName", "matchn")
351332
| ("StringName", "begins_with")
@@ -364,8 +345,6 @@ pub fn is_builtin_method_exposed(builtin_ty: &TyName, godot_method_name: &str) -
364345
| ("StringName", "to_camel_case")
365346
| ("StringName", "to_pascal_case")
366347
| ("StringName", "to_snake_case")
367-
| ("StringName", "split")
368-
| ("StringName", "rsplit")
369348
| ("StringName", "split_floats")
370349
| ("StringName", "join")
371350
| ("StringName", "to_upper")

godot-core/src/builtin/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ pub mod iter {
8282

8383
/// Specialized types related to Godot's various string implementations.
8484
pub mod strings {
85-
pub use super::string::TransientStringNameOrd;
85+
pub use super::string::{ExStringNameFind, ExStringNameSplit, ExGStringFind, ExGStringSplit, TransientStringNameOrd};
8686
}
8787

8888
// ----------------------------------------------------------------------------------------------------------------------------------------------

godot-core/src/builtin/string/gstring.rs

Lines changed: 10 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,16 @@
77

88
use std::convert::Infallible;
99
use std::ffi::c_char;
10+
use std::fmt;
1011
use std::fmt::Write;
11-
use std::str::FromStr;
12-
use std::{cmp, fmt, ops};
1312

1413
use godot_ffi as sys;
1514
use sys::types::OpaqueString;
1615
use sys::{ffi_methods, interface_fn, GodotFfi};
1716

1817
use crate::builtin::{inner, NodePath, StringName, Variant};
19-
use crate::meta;
2018
use crate::meta::AsArg;
19+
use crate::{impl_shared_string_api, meta};
2120

2221
/// Godot's reference counted string type.
2322
///
@@ -112,134 +111,6 @@ impl GString {
112111
}
113112
}
114113

115-
/// Returns a substring of this, as another `GString`.
116-
pub fn substr(&self, range: impl ops::RangeBounds<usize>) -> Self {
117-
let (from, len) = super::to_fromlen_pair(range);
118-
119-
self.as_inner().substr(from, len)
120-
}
121-
122-
/// Format a string using substitutions from an array or dictionary.
123-
///
124-
/// See Godot's [`String.format()`](https://docs.godotengine.org/en/stable/classes/class_string.html#class-string-method-format).
125-
pub fn format(&self, array_or_dict: &Variant) -> Self {
126-
self.as_inner().format(array_or_dict, "{_}")
127-
}
128-
129-
/// Format a string using substitutions from an array or dictionary + custom placeholder.
130-
///
131-
/// See Godot's [`String.format()`](https://docs.godotengine.org/en/stable/classes/class_string.html#class-string-method-format).
132-
pub fn format_with_placeholder(
133-
&self,
134-
array_or_dict: &Variant,
135-
placeholder: impl AsArg<GString>,
136-
) -> Self {
137-
self.as_inner().format(array_or_dict, placeholder)
138-
}
139-
140-
/// Case-sensitive, lexicographic comparison to another string.
141-
///
142-
/// Returns the `Ordering` relation of `self` towards `to`. Ordering is determined by the Unicode code points of each string, which roughly
143-
/// matches the alphabetical order.
144-
///
145-
/// See also [`nocasecmp_to()`](Self::nocasecmp_to), [`naturalcasecmp_to()`](Self::naturalcasecmp_to), [`filecasecmp_to()`](Self::filecasecmp_to).
146-
pub fn casecmp_to(&self, to: impl AsArg<GString>) -> cmp::Ordering {
147-
sys::i64_to_ordering(self.as_inner().casecmp_to(to))
148-
}
149-
150-
/// Case-**insensitive**, lexicographic comparison to another string.
151-
///
152-
/// Returns the `Ordering` relation of `self` towards `to`. Ordering is determined by the Unicode code points of each string, which roughly
153-
/// matches the alphabetical order.
154-
///
155-
/// See also [`casecmp_to()`](Self::casecmp_to), [`naturalcasecmp_to()`](Self::naturalcasecmp_to), [`filecasecmp_to()`](Self::filecasecmp_to).
156-
pub fn nocasecmp_to(&self, to: impl AsArg<GString>) -> cmp::Ordering {
157-
sys::i64_to_ordering(self.as_inner().nocasecmp_to(to))
158-
}
159-
160-
/// Case-sensitive, **natural-order** comparison to another string.
161-
///
162-
/// Returns the `Ordering` relation of `self` towards `to`. Ordering is determined by the Unicode code points of each string, which roughly
163-
/// matches the alphabetical order.
164-
///
165-
/// When used for sorting, natural order comparison orders sequences of numbers by the combined value of each digit as is often expected,
166-
/// instead of the single digit's value. A sorted sequence of numbered strings will be `["1", "2", "3", ...]`, not `["1", "10", "2", "3", ...]`.
167-
///
168-
/// With different string lengths, returns `Ordering::Greater` if this string is longer than the `to` string, or `Ordering::Less` if shorter.
169-
///
170-
/// See also [`casecmp_to()`](Self::casecmp_to), [`naturalnocasecmp_to()`](Self::naturalnocasecmp_to), [`filecasecmp_to()`](Self::filecasecmp_to).
171-
pub fn naturalcasecmp_to(&self, to: impl AsArg<GString>) -> cmp::Ordering {
172-
sys::i64_to_ordering(self.as_inner().naturalcasecmp_to(to))
173-
}
174-
175-
/// Case-insensitive, **natural-order** comparison to another string.
176-
///
177-
/// Returns the `Ordering` relation of `self` towards `to`. Ordering is determined by the Unicode code points of each string, which roughly
178-
/// matches the alphabetical order.
179-
///
180-
/// When used for sorting, natural order comparison orders sequences of numbers by the combined value of each digit as is often expected,
181-
/// instead of the single digit's value. A sorted sequence of numbered strings will be `["1", "2", "3", ...]`, not `["1", "10", "2", "3", ...]`.
182-
///
183-
/// With different string lengths, returns `Ordering::Greater` if this string is longer than the `to` string, or `Ordering::Less` if shorter.
184-
///
185-
/// See also [`casecmp_to()`](Self::casecmp_to), [`naturalcasecmp_to()`](Self::naturalcasecmp_to), [`filecasecmp_to()`](Self::filecasecmp_to).
186-
pub fn naturalnocasecmp_to(&self, to: impl AsArg<GString>) -> cmp::Ordering {
187-
sys::i64_to_ordering(self.as_inner().naturalnocasecmp_to(to))
188-
}
189-
190-
/// Case-sensitive, filename-oriented comparison to another string.
191-
///
192-
/// Like [`naturalcasecmp_to()`][Self::naturalcasecmp_to], but prioritizes strings that begin with periods (`.`) and underscores (`_`) before
193-
/// any other character. Useful when sorting folders or file names.
194-
///
195-
/// See also [`casecmp_to()`](Self::casecmp_to), [`naturalcasecmp_to()`](Self::naturalcasecmp_to), [`filenocasecmp_to()`](Self::filenocasecmp_to).
196-
#[cfg(since_api = "4.3")]
197-
pub fn filecasecmp_to(&self, to: impl AsArg<GString>) -> cmp::Ordering {
198-
sys::i64_to_ordering(self.as_inner().filecasecmp_to(to))
199-
}
200-
201-
/// Case-insensitive, filename-oriented comparison to another string.
202-
///
203-
/// Like [`naturalnocasecmp_to()`][Self::naturalnocasecmp_to], but prioritizes strings that begin with periods (`.`) and underscores (`_`) before
204-
/// any other character. Useful when sorting folders or file names.
205-
///
206-
/// See also [`casecmp_to()`](Self::casecmp_to), [`naturalcasecmp_to()`](Self::naturalcasecmp_to), [`filecasecmp_to()`](Self::filecasecmp_to).
207-
#[cfg(since_api = "4.3")]
208-
pub fn filenocasecmp_to(&self, to: impl AsArg<GString>) -> cmp::Ordering {
209-
sys::i64_to_ordering(self.as_inner().filenocasecmp_to(to))
210-
}
211-
212-
/// Splits the string using a string delimiter and returns the substring at index `slice`.
213-
///
214-
/// Returns the original string if delimiter does not occur in the string. Returns `None` if `slice` is out of bounds.
215-
///
216-
/// This is faster than [`split()`][Self::split], if you only need one substring.
217-
pub fn get_slice(&self, delimiter: impl AsArg<GString>, slice: usize) -> Option<GString> {
218-
let sliced = self.as_inner().get_slice(delimiter, slice as i64);
219-
220-
// Note: self="" always returns None.
221-
super::populated_or_none(sliced)
222-
}
223-
224-
/// Returns the total number of slices, when the string is split with the given delimiter.
225-
///
226-
/// See also [`split()`][Self::split] and [`get_slice()`][Self::get_slice].
227-
pub fn get_slice_count(&self, delimiter: impl AsArg<GString>) -> usize {
228-
self.as_inner().get_slice_count(delimiter) as usize
229-
}
230-
231-
/// Splits the string using a Unicode char `delimiter` and returns the substring at index `slice`.
232-
///
233-
/// Returns the original string if delimiter does not occur in the string. Returns `None` if `slice` is out of bounds.
234-
///
235-
/// This is faster than [`split()`][Self::split], if you only need one substring.
236-
pub fn get_slicec(&self, delimiter: char, slice: usize) -> Option<GString> {
237-
let sliced = self.as_inner().get_slicec(delimiter as i64, slice as i64);
238-
239-
// Note: self="" always returns None.
240-
super::populated_or_none(sliced)
241-
}
242-
243114
ffi_methods! {
244115
type sys::GDExtensionStringPtr = *mut Self;
245116

@@ -289,7 +160,7 @@ impl GString {
289160
}
290161

291162
meta::declare_arg_method! {
292-
/// Use as argument for an [`impl AsArg<StringName|NodePath>`][crate::AsArg] parameter.
163+
/// Use as argument for an [`impl AsArg<StringName|NodePath>`][crate::meta::AsArg] parameter.
293164
///
294165
/// This is a convenient way to convert arguments of similar string types.
295166
///
@@ -342,6 +213,12 @@ impl_builtin_traits! {
342213
}
343214
}
344215

216+
impl_shared_string_api! {
217+
builtin: GString,
218+
find_builder: ExGStringFind,
219+
split_builder: ExGStringSplit,
220+
}
221+
345222
impl fmt::Display for GString {
346223
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
347224
for ch in self.chars() {
@@ -438,7 +315,7 @@ impl From<GString> for String {
438315
}
439316
}
440317

441-
impl FromStr for GString {
318+
impl std::str::FromStr for GString {
442319
type Err = Infallible;
443320

444321
fn from_str(s: &str) -> Result<Self, Self::Err> {

godot-core/src/builtin/string/mod.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
mod gstring;
1111
mod macros;
1212
mod node_path;
13+
mod string_macros;
1314
mod string_name;
1415

1516
use crate::meta::error::ConvertError;
@@ -18,7 +19,7 @@ use std::ops;
1819

1920
pub use gstring::*;
2021
pub use node_path::NodePath;
21-
pub use string_name::{StringName, TransientStringNameOrd};
22+
pub use string_name::*;
2223

2324
impl GodotConvert for &str {
2425
type Via = GString;
@@ -82,3 +83,16 @@ fn populated_or_none(s: GString) -> Option<GString> {
8283
Some(s)
8384
}
8485
}
86+
87+
fn found_to_option(index: i64) -> Option<usize> {
88+
if index == -1 {
89+
None
90+
} else {
91+
// If this fails, then likely because we overlooked a negative value.
92+
let index_usize = index
93+
.try_into()
94+
.unwrap_or_else(|_| panic!("unexpected index {index} returned from Godot function"));
95+
96+
Some(index_usize)
97+
}
98+
}

0 commit comments

Comments
 (0)