Skip to content

Commit 39e8379

Browse files
committed
String formatting: support padding, alignment and precision
Allows to use `format!("{s:>6}")` and similar expressions, yielding expected results. Tries to not allocate extra strings if no custom formatting options are needed.
1 parent 48b634a commit 39e8379

File tree

6 files changed

+109
-5
lines changed

6 files changed

+109
-5
lines changed

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

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use godot_ffi as sys;
1313
use sys::types::OpaqueString;
1414
use sys::{ffi_methods, interface_fn, GodotFfi};
1515

16-
use crate::builtin::string::Encoding;
16+
use crate::builtin::string::{pad_if_needed, Encoding};
1717
use crate::builtin::{inner, NodePath, StringName, Variant};
1818
use crate::meta::error::StringError;
1919
use crate::meta::AsArg;
@@ -298,11 +298,13 @@ impl_shared_string_api! {
298298

299299
impl fmt::Display for GString {
300300
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
301-
for ch in self.chars() {
302-
f.write_char(*ch)?;
303-
}
301+
pad_if_needed(f, |f| {
302+
for ch in self.chars() {
303+
f.write_char(*ch)?;
304+
}
304305

305-
Ok(())
306+
Ok(())
307+
})
306308
}
307309
}
308310

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

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,3 +155,48 @@ fn found_to_option(index: i64) -> Option<usize> {
155155
Some(index_usize)
156156
}
157157
}
158+
159+
// ----------------------------------------------------------------------------------------------------------------------------------------------
160+
// Padding, alignment and precision support
161+
162+
// Used by sub-modules of this module.
163+
use standard_fmt::pad_if_needed;
164+
165+
mod 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<'a, 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+
}

itest/rust/src/builtin_tests/string/gstring_test.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,14 @@ crate::generate_string_bytes_and_cstr_tests!(
279279
]
280280
);
281281

282+
crate::generate_string_standard_fmt_tests!(
283+
builtin: GString,
284+
tests: [
285+
gstring_display,
286+
gstring_standard_pad,
287+
]
288+
);
289+
282290
// ----------------------------------------------------------------------------------------------------------------------------------------------
283291
// Helpers
284292

itest/rust/src/builtin_tests/string/node_path_test.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ fn node_path_conversion() {
3131

3232
assert_eq!(string, back);
3333
}
34+
3435
#[itest]
3536
fn node_path_equality() {
3637
let string = NodePath::from("some string");
@@ -126,3 +127,11 @@ fn node_path_get_subname() {
126127
assert_eq!(path.get_subname(2), "".into());
127128
})
128129
}
130+
131+
crate::generate_string_standard_fmt_tests!(
132+
builtin: NodePath,
133+
tests: [
134+
node_path_display,
135+
node_path_standard_pad,
136+
]
137+
);

itest/rust/src/builtin_tests/string/string_name_test.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,3 +175,11 @@ crate::generate_string_bytes_and_cstr_tests!(
175175
string_name_from_cstr_utf8,
176176
]
177177
);
178+
179+
crate::generate_string_standard_fmt_tests!(
180+
builtin: StringName,
181+
tests: [
182+
node_path_display,
183+
string_name_standard_pad,
184+
]
185+
);

itest/rust/src/builtin_tests/string/string_test_macros.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,3 +162,35 @@ macro_rules! generate_string_bytes_and_cstr_tests {
162162
}
163163
};
164164
}
165+
166+
// Tests padding with the standard formatter.
167+
#[macro_export]
168+
macro_rules! generate_string_standard_fmt_tests {
169+
(
170+
builtin: $T:ty,
171+
tests: [
172+
$display:ident,
173+
$standard_pad:ident,
174+
]
175+
) => {
176+
#[itest(focus)]
177+
fn $display() {
178+
let s = <$T>::from("abcd");
179+
180+
assert_eq!(format!("{s}"), "abcd");
181+
}
182+
183+
#[itest(focus)]
184+
fn $standard_pad() {
185+
let s = <$T>::from("abcd");
186+
187+
// Padding with spaces + alignment.
188+
assert_eq!(format!("{s:<6}"), "abcd ");
189+
assert_eq!(format!("{s:>6}"), " abcd");
190+
191+
// Precision.
192+
assert_eq!(format!("{s:.2}"), "ab");
193+
assert_eq!(format!("{s:.3}"), "abc");
194+
}
195+
};
196+
}

0 commit comments

Comments
 (0)