bones_utils/
names.rs

1/// Shortens a type name to remove all module paths.
2///
3/// The short name of a type is its full name as returned by
4/// [`std::any::type_name`], but with the prefix of all paths removed. For
5/// example, the short name of `alloc::vec::Vec<core::option::Option<u32>>`
6/// would be `Vec<Option<u32>>`.
7pub fn get_short_name(full_name: &str) -> String {
8    // Generics result in nested paths within <..> blocks.
9    // Consider "bevy_render::camera::camera::extract_cameras<bevy_render::camera::bundle::Camera3d>".
10    // To tackle this, we parse the string from left to right, collapsing as we go.
11    let mut index: usize = 0;
12    let end_of_string = full_name.len();
13    let mut parsed_name = String::new();
14
15    while index < end_of_string {
16        let rest_of_string = full_name.get(index..end_of_string).unwrap_or_default();
17
18        // Collapse everything up to the next special character,
19        // then skip over it
20        if let Some(special_character_index) = rest_of_string.find(|c: char| {
21            (c == ' ')
22                || (c == '<')
23                || (c == '>')
24                || (c == '(')
25                || (c == ')')
26                || (c == '[')
27                || (c == ']')
28                || (c == ',')
29                || (c == ';')
30        }) {
31            let segment_to_collapse = rest_of_string
32                .get(0..special_character_index)
33                .unwrap_or_default();
34            parsed_name += collapse_type_name(segment_to_collapse);
35            // Insert the special character
36            let special_character =
37                &rest_of_string[special_character_index..=special_character_index];
38            parsed_name.push_str(special_character);
39
40            match special_character {
41                ">" | ")" | "]"
42                    if rest_of_string[special_character_index + 1..].starts_with("::") =>
43                {
44                    parsed_name.push_str("::");
45                    // Move the index past the "::"
46                    index += special_character_index + 3;
47                }
48                // Move the index just past the special character
49                _ => index += special_character_index + 1,
50            }
51        } else {
52            // If there are no special characters left, we're done!
53            parsed_name += collapse_type_name(rest_of_string);
54            index = end_of_string;
55        }
56    }
57    parsed_name
58}
59
60#[inline(always)]
61fn collapse_type_name(string: &str) -> &str {
62    string.split("::").last().unwrap()
63}
64
65#[cfg(test)]
66mod name_formatting_tests {
67    use super::get_short_name;
68
69    #[test]
70    fn trivial() {
71        assert_eq!(get_short_name("test_system"), "test_system");
72    }
73
74    #[test]
75    fn path_separated() {
76        assert_eq!(
77            get_short_name("bevy_prelude::make_fun_game"),
78            "make_fun_game".to_string()
79        );
80    }
81
82    #[test]
83    fn tuple_type() {
84        assert_eq!(
85            get_short_name("(String, String)"),
86            "(String, String)".to_string()
87        );
88    }
89
90    #[test]
91    fn array_type() {
92        assert_eq!(get_short_name("[i32; 3]"), "[i32; 3]".to_string());
93    }
94
95    #[test]
96    fn trivial_generics() {
97        assert_eq!(get_short_name("a<B>"), "a<B>".to_string());
98    }
99
100    #[test]
101    fn multiple_type_parameters() {
102        assert_eq!(get_short_name("a<B, C>"), "a<B, C>".to_string());
103    }
104
105    #[test]
106    fn generics() {
107        assert_eq!(
108            get_short_name("bevy_render::camera::camera::extract_cameras<bevy_render::camera::bundle::Camera3d>"),
109            "extract_cameras<Camera3d>".to_string()
110        );
111    }
112
113    #[test]
114    fn nested_generics() {
115        assert_eq!(
116            get_short_name("bevy::mad_science::do_mad_science<mad_science::Test<mad_science::Tube>, bavy::TypeSystemAbuse>"),
117            "do_mad_science<Test<Tube>, TypeSystemAbuse>".to_string()
118        );
119    }
120
121    #[test]
122    fn sub_path_after_closing_bracket() {
123        assert_eq!(
124            get_short_name("bevy_asset::assets::Assets<bevy_scene::dynamic_scene::DynamicScene>::asset_event_system"),
125            "Assets<DynamicScene>::asset_event_system".to_string()
126        );
127        assert_eq!(
128            get_short_name("(String, String)::default"),
129            "(String, String)::default".to_string()
130        );
131        assert_eq!(
132            get_short_name("[i32; 16]::default"),
133            "[i32; 16]::default".to_string()
134        );
135    }
136}