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
180pub fn public_parent_module(module_name: &str) -> String {
193 let public_parts: Vec<&str> = module_name
194 .split('.')
195 .take_while(|part| !part.starts_with('_'))
196 .collect();
197 public_parts.join(".")
198}
199
200#[cfg(test)]
201mod tests {
202 use super::prefix_stripper::*;
203 use super::*;
204
205 #[test]
206 fn test_strip_stdlib_prefixes() {
207 assert_eq!(strip_stdlib_prefixes("typing.Optional"), "Optional");
208 assert_eq!(strip_stdlib_prefixes("builtins.str"), "str");
209 assert_eq!(
210 strip_stdlib_prefixes("collections.abc.Callable"),
211 "Callable"
212 );
213 assert_eq!(
214 strip_stdlib_prefixes("typing.Optional[typing.List[int]]"),
215 "Optional[List[int]]"
216 );
217 }
218
219 #[test]
220 fn test_strip_package_prefixes() {
221 assert_eq!(strip_package_prefixes("main_mod.ClassA", ""), "ClassA");
222 assert_eq!(
223 strip_package_prefixes("pure.DataContainer", ""),
224 "DataContainer"
225 );
226 assert_eq!(
227 strip_package_prefixes("Optional[main_mod.ClassA]", ""),
228 "Optional[ClassA]"
229 );
230 }
231
232 #[test]
233 fn test_strip_internal_prefixes() {
234 assert_eq!(
235 strip_internal_prefixes("_core.InternalType"),
236 "InternalType"
237 );
238 assert_eq!(strip_internal_prefixes("_internal._nested.Type"), "Type");
239 assert_eq!(
240 strip_internal_prefixes("Optional[_core.Type]"),
241 "Optional[Type]"
242 );
243 }
244
245 #[test]
246 fn test_strip_type_prefixes() {
247 assert_eq!(
248 strip_type_prefixes("typing.Optional[main_mod.ClassA]", "main_mod"),
249 "Optional[ClassA]"
250 );
251 }
252
253 #[test]
254 fn test_strip_default_value_prefixes() {
255 assert_eq!(strip_default_value_prefixes("_core.Foo"), "Foo");
256 assert_eq!(strip_default_value_prefixes("typing.Optional"), "Optional");
257 assert_eq!(strip_default_value_prefixes("builtins.None"), "None");
258 }
259
260 #[test]
261 fn test_is_hidden_module() {
262 assert!(is_hidden_module("_core"));
263 assert!(is_hidden_module("package._internal"));
264 assert!(is_hidden_module("_hidden.submodule"));
265 assert!(!is_hidden_module("public"));
266 assert!(!is_hidden_module("package.submodule"));
267 }
268
269 #[test]
270 fn test_public_parent_module() {
271 assert_eq!(
272 public_parent_module("generate_init_py._core"),
273 "generate_init_py"
274 );
275 assert_eq!(public_parent_module("pkg._internal.core"), "pkg");
276 assert_eq!(public_parent_module("pkg.mod._hidden"), "pkg.mod");
277 assert_eq!(public_parent_module("pkg.mod"), "pkg.mod");
278 assert_eq!(public_parent_module("_hidden"), "");
279 }
280}