godot_core/builtin/string/
mod.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//! Godot-types that are Strings.
9
10mod gstring;
11mod macros;
12mod node_path;
13mod string_macros;
14mod string_name;
15
16use crate::meta::error::ConvertError;
17use crate::meta::{FromGodot, GodotConvert, ToGodot};
18use std::ops;
19
20pub use gstring::*;
21pub use node_path::NodePath;
22pub use string_name::*;
23
24impl GodotConvert for &str {
25    type Via = GString;
26}
27
28impl ToGodot for &str {
29    type ToVia<'v>
30        = GString
31    where
32        Self: 'v;
33
34    fn to_godot(&self) -> Self::ToVia<'_> {
35        GString::from(*self)
36    }
37}
38
39impl GodotConvert for String {
40    type Via = GString;
41}
42
43impl ToGodot for String {
44    type ToVia<'v> = Self::Via;
45
46    fn to_godot(&self) -> Self::ToVia<'_> {
47        GString::from(self)
48    }
49}
50
51impl FromGodot for String {
52    fn try_from_godot(via: Self::Via) -> Result<Self, ConvertError> {
53        Ok(via.to_string())
54    }
55}
56
57// ----------------------------------------------------------------------------------------------------------------------------------------------
58// Encoding
59
60/// Specifies string encoding.
61///
62/// Used in functions such as [`GString::try_from_bytes()`][GString::try_from_bytes] to handle multiple input string encodings.
63#[non_exhaustive]
64#[derive(Copy, Clone, Eq, PartialEq, Debug)]
65pub enum Encoding {
66    Ascii,
67    Latin1,
68    Utf8,
69}
70
71// ----------------------------------------------------------------------------------------------------------------------------------------------
72
73/// Returns a tuple of `(from, len)` from a Rust range.
74///
75/// Unbounded upper bounds are represented by `len = -1`.
76fn to_godot_fromlen_neg1<R>(range: R) -> (i64, i64)
77where
78    R: ops::RangeBounds<usize>,
79{
80    let from = match range.start_bound() {
81        ops::Bound::Included(&n) => n as i64,
82        ops::Bound::Excluded(&n) => (n as i64) + 1,
83        ops::Bound::Unbounded => 0,
84    };
85
86    let len = match range.end_bound() {
87        ops::Bound::Included(&n) => {
88            let to = (n + 1) as i64;
89            debug_assert!(
90                from <= to,
91                "range: start ({from}) > inclusive end ({n}) + 1"
92            );
93            to - from
94        }
95        ops::Bound::Excluded(&n) => {
96            let to = n as i64;
97            debug_assert!(from <= to, "range: start ({from}) > exclusive end ({to})");
98            to - from
99        }
100        ops::Bound::Unbounded => -1,
101    };
102
103    (from, len)
104}
105
106/// Returns a tuple of `(from, len)` from a Rust range.
107///
108/// Unbounded upper bounds are represented by `i32::MAX` (yes, not `i64::MAX` -- since Godot treats some indexes as 32-bit despite being
109/// declared `i64` in GDExtension API).
110fn to_godot_fromlen_i32max<R>(range: R) -> (i64, i64)
111where
112    R: ops::RangeBounds<usize>,
113{
114    let (from, len) = to_godot_fromlen_neg1(range);
115    if len == -1 {
116        // Use i32 here because Godot may wrap around larger values (see Rustdoc).
117        (from, i32::MAX as i64)
118    } else {
119        (from, len)
120    }
121}
122
123/// Returns a tuple of `(from, to)` from a Rust range.
124///
125/// Unbounded upper bounds are represented by `to = 0`.
126fn to_godot_fromto<R>(range: R) -> (i64, i64)
127where
128    R: ops::RangeBounds<usize>,
129{
130    let (from, len) = to_godot_fromlen_neg1(range);
131    if len == -1 {
132        (from, 0)
133    } else {
134        (from, from + len)
135    }
136}
137
138fn populated_or_none(s: GString) -> Option<GString> {
139    if s.is_empty() {
140        None
141    } else {
142        Some(s)
143    }
144}
145
146fn found_to_option(index: i64) -> Option<usize> {
147    if index == -1 {
148        None
149    } else {
150        // If this fails, then likely because we overlooked a negative value.
151        let index_usize = index
152            .try_into()
153            .unwrap_or_else(|_| panic!("unexpected index {index} returned from Godot function"));
154
155        Some(index_usize)
156    }
157}
158
159// ----------------------------------------------------------------------------------------------------------------------------------------------
160// Padding, alignment and precision support
161
162// Used by sub-modules of this module.
163use standard_fmt::pad_if_needed;
164
165mod standard_fmt {
166    use std::fmt;
167    use std::fmt::Write;
168
169    pub fn pad_if_needed<F>(f: &mut fmt::Formatter<'_>, display_impl: F) -> fmt::Result
170    where
171        F: Fn(&mut fmt::Formatter<'_>) -> fmt::Result,
172    {
173        let needs_format = f.width().is_some() || f.precision().is_some() || f.align().is_some();
174
175        // Early exit if no custom formatting is needed.
176        if !needs_format {
177            return display_impl(f);
178        }
179
180        let ic = FmtInterceptor { display_impl };
181
182        let mut local_str = String::new();
183        write!(&mut local_str, "{ic}")?;
184        f.pad(&local_str)
185    }
186
187    struct FmtInterceptor<F>
188    where
189        F: Fn(&mut fmt::Formatter<'_>) -> fmt::Result,
190    {
191        display_impl: F,
192    }
193
194    impl<F> fmt::Display for FmtInterceptor<F>
195    where
196        F: Fn(&mut fmt::Formatter<'_>) -> fmt::Result,
197    {
198        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
199            (self.display_impl)(f)
200        }
201    }
202}