pyo3_stub_gen/docgen/
util.rs1pub mod prefix_stripper {
5 pub fn strip_stdlib_prefixes(type_expr: &str) -> String {
10 let stdlib_prefixes = &[
11 "typing.",
12 "builtins.",
13 "collections.abc.",
14 "typing_extensions.",
15 "decimal.",
16 "datetime.",
17 "pathlib.",
18 ];
19
20 let mut result = String::new();
21 let mut i = 0;
22 let chars: Vec<char> = type_expr.chars().collect();
23
24 while i < chars.len() {
25 if i == 0
27 || !chars[i - 1].is_alphanumeric() && chars[i - 1] != '_' && chars[i - 1] != '.'
28 {
29 let remaining: String = chars[i..].iter().collect();
30 let mut matched = false;
31
32 for prefix in stdlib_prefixes {
34 if remaining.starts_with(prefix) {
35 let after_prefix_idx = prefix.len();
36 if after_prefix_idx < remaining.len() {
37 let next_char = remaining.chars().nth(after_prefix_idx).unwrap();
38 if next_char.is_alphabetic() || next_char == '_' {
39 i += prefix.len();
40 matched = true;
41 break;
42 }
43 }
44 }
45 }
46
47 if matched {
48 continue;
49 }
50 }
51
52 result.push(chars[i]);
53 i += 1;
54 }
55
56 result
57 }
58
59 pub fn strip_package_prefixes(type_expr: &str, _current_module: &str) -> String {
65 let mut result = String::new();
66 let mut i = 0;
67 let chars: Vec<char> = type_expr.chars().collect();
68
69 while i < chars.len() {
70 if i == 0
72 || !chars[i - 1].is_alphanumeric() && chars[i - 1] != '_' && chars[i - 1] != '.'
73 {
74 let remaining: String = chars[i..].iter().collect();
75
76 let ident_match = remaining
78 .split(|c: char| !c.is_alphanumeric() && c != '_' && c != '.')
79 .next();
80
81 if let Some(ident) = ident_match {
82 if ident.contains('.') {
83 let parts: Vec<&str> = ident.split('.').collect();
85 if parts.len() >= 2 {
86 let first_part = parts[0];
87 let last_part = parts[parts.len() - 1];
88
89 if first_part
91 .chars()
92 .next()
93 .map(|c| c.is_lowercase())
94 .unwrap_or(false)
95 && last_part
96 .chars()
97 .next()
98 .map(|c| c.is_uppercase())
99 .unwrap_or(false)
100 {
101 let prefix_len = ident.len() - last_part.len();
103 i += prefix_len;
104 continue;
105 }
106 }
107 }
108 }
109 }
110
111 result.push(chars[i]);
112 i += 1;
113 }
114
115 result
116 }
117
118 pub fn strip_internal_prefixes(expr: &str) -> String {
122 let mut result = String::new();
123 let mut i = 0;
124 let chars: Vec<char> = expr.chars().collect();
125
126 while i < chars.len() {
127 if i == 0 || !chars[i - 1].is_alphanumeric() && chars[i - 1] != '_' {
129 let remaining: String = chars[i..].iter().collect();
131
132 if remaining.starts_with('_') {
134 let mut j = i + 1;
136 while j < chars.len() && (chars[j].is_alphanumeric() || chars[j] == '_') {
137 j += 1;
138 }
139
140 if j < chars.len() && chars[j] == '.' {
142 i = j + 1;
144 continue;
145 }
146 }
147 }
148
149 result.push(chars[i]);
150 i += 1;
151 }
152
153 result
154 }
155
156 pub fn strip_type_prefixes(type_expr: &str, current_module: &str) -> String {
160 let without_stdlib = strip_stdlib_prefixes(type_expr);
161 strip_package_prefixes(&without_stdlib, current_module)
162 }
163
164 pub fn strip_default_value_prefixes(text: &str) -> String {
169 text.replace("_core.", "")
170 .replace("typing.", "")
171 .replace("builtins.", "")
172 }
173}
174
175pub fn is_hidden_module(module_name: &str) -> bool {
177 module_name.split('.').any(|part| part.starts_with('_'))
178}
179
180#[cfg(test)]
181mod tests {
182 use super::prefix_stripper::*;
183 use super::*;
184
185 #[test]
186 fn test_strip_stdlib_prefixes() {
187 assert_eq!(strip_stdlib_prefixes("typing.Optional"), "Optional");
188 assert_eq!(strip_stdlib_prefixes("builtins.str"), "str");
189 assert_eq!(
190 strip_stdlib_prefixes("collections.abc.Callable"),
191 "Callable"
192 );
193 assert_eq!(
194 strip_stdlib_prefixes("typing.Optional[typing.List[int]]"),
195 "Optional[List[int]]"
196 );
197 }
198
199 #[test]
200 fn test_strip_package_prefixes() {
201 assert_eq!(strip_package_prefixes("main_mod.ClassA", ""), "ClassA");
202 assert_eq!(
203 strip_package_prefixes("pure.DataContainer", ""),
204 "DataContainer"
205 );
206 assert_eq!(
207 strip_package_prefixes("Optional[main_mod.ClassA]", ""),
208 "Optional[ClassA]"
209 );
210 }
211
212 #[test]
213 fn test_strip_internal_prefixes() {
214 assert_eq!(
215 strip_internal_prefixes("_core.InternalType"),
216 "InternalType"
217 );
218 assert_eq!(strip_internal_prefixes("_internal._nested.Type"), "Type");
219 assert_eq!(
220 strip_internal_prefixes("Optional[_core.Type]"),
221 "Optional[Type]"
222 );
223 }
224
225 #[test]
226 fn test_strip_type_prefixes() {
227 assert_eq!(
228 strip_type_prefixes("typing.Optional[main_mod.ClassA]", "main_mod"),
229 "Optional[ClassA]"
230 );
231 }
232
233 #[test]
234 fn test_strip_default_value_prefixes() {
235 assert_eq!(strip_default_value_prefixes("_core.Foo"), "Foo");
236 assert_eq!(strip_default_value_prefixes("typing.Optional"), "Optional");
237 assert_eq!(strip_default_value_prefixes("builtins.None"), "None");
238 }
239
240 #[test]
241 fn test_is_hidden_module() {
242 assert!(is_hidden_module("_core"));
243 assert!(is_hidden_module("package._internal"));
244 assert!(is_hidden_module("_hidden.submodule"));
245 assert!(!is_hidden_module("public"));
246 assert!(!is_hidden_module("package.submodule"));
247 }
248}