1use crate::model::{Argument, Class, Const, Function, Module, VariableLengthArgument};
2use std::collections::{BTreeSet, HashMap};
3use std::path::{Path, PathBuf};
4
5pub fn module_stub_files(module: &Module) -> HashMap<PathBuf, String> {
10 let mut output_files = HashMap::new();
11 add_module_stub_files(module, Path::new(""), &mut output_files);
12 output_files
13}
14
15fn add_module_stub_files(
16 module: &Module,
17 module_path: &Path,
18 output_files: &mut HashMap<PathBuf, String>,
19) {
20 output_files.insert(module_path.join("__init__.pyi"), module_stubs(module));
21 for submodule in &module.modules {
22 if submodule.modules.is_empty() {
23 output_files.insert(
24 module_path.join(format!("{}.pyi", submodule.name)),
25 module_stubs(submodule),
26 );
27 } else {
28 add_module_stub_files(submodule, &module_path.join(&submodule.name), output_files);
29 }
30 }
31}
32
33fn module_stubs(module: &Module) -> String {
35 let mut modules_to_import = BTreeSet::new();
36 let mut elements = Vec::new();
37 for konst in &module.consts {
38 elements.push(const_stubs(konst, &mut modules_to_import));
39 }
40 for class in &module.classes {
41 elements.push(class_stubs(class, &mut modules_to_import));
42 }
43 for function in &module.functions {
44 elements.push(function_stubs(function, &mut modules_to_import));
45 }
46 let mut final_elements = Vec::new();
47 for module_to_import in &modules_to_import {
48 final_elements.push(format!("import {module_to_import}"));
49 }
50 final_elements.extend(elements);
51
52 let mut output = String::new();
53
54 for element in final_elements {
56 let is_multiline = element.contains('\n');
57 if is_multiline && !output.is_empty() && !output.ends_with("\n\n") {
58 output.push('\n');
59 }
60 output.push_str(&element);
61 output.push('\n');
62 if is_multiline {
63 output.push('\n');
64 }
65 }
66
67 if output.ends_with("\n\n") {
69 output.pop();
70 }
71 output
72}
73
74fn class_stubs(class: &Class, modules_to_import: &mut BTreeSet<String>) -> String {
75 let mut buffer = format!("class {}:", class.name);
76 if class.methods.is_empty() {
77 buffer.push_str(" ...");
78 return buffer;
79 }
80 for method in &class.methods {
81 buffer.push_str("\n ");
83 buffer.push_str(&function_stubs(method, modules_to_import).replace('\n', "\n "));
84 }
85 buffer
86}
87
88fn function_stubs(function: &Function, modules_to_import: &mut BTreeSet<String>) -> String {
89 let mut parameters = Vec::new();
91 for argument in &function.arguments.positional_only_arguments {
92 parameters.push(argument_stub(argument, modules_to_import));
93 }
94 if !function.arguments.positional_only_arguments.is_empty() {
95 parameters.push("/".into());
96 }
97 for argument in &function.arguments.arguments {
98 parameters.push(argument_stub(argument, modules_to_import));
99 }
100 if let Some(argument) = &function.arguments.vararg {
101 parameters.push(format!("*{}", variable_length_argument_stub(argument)));
102 } else if !function.arguments.keyword_only_arguments.is_empty() {
103 parameters.push("*".into());
104 }
105 for argument in &function.arguments.keyword_only_arguments {
106 parameters.push(argument_stub(argument, modules_to_import));
107 }
108 if let Some(argument) = &function.arguments.kwarg {
109 parameters.push(format!("**{}", variable_length_argument_stub(argument)));
110 }
111 let mut buffer = String::new();
112 for decorator in &function.decorators {
113 buffer.push('@');
114 buffer.push_str(decorator);
115 buffer.push('\n');
116 }
117 buffer.push_str("def ");
118 buffer.push_str(&function.name);
119 buffer.push('(');
120 buffer.push_str(¶meters.join(", "));
121 buffer.push(')');
122 if let Some(returns) = &function.returns {
123 buffer.push_str(" -> ");
124 buffer.push_str(annotation_stub(returns, modules_to_import));
125 }
126 buffer.push_str(": ...");
127 buffer
128}
129
130fn const_stubs(konst: &Const, modules_to_import: &mut BTreeSet<String>) -> String {
131 modules_to_import.insert("typing".to_string());
132 let Const { name, value } = konst;
133 format!("{name}: typing.Final = {value}")
134}
135
136fn argument_stub(argument: &Argument, modules_to_import: &mut BTreeSet<String>) -> String {
137 let mut output = argument.name.clone();
138 if let Some(annotation) = &argument.annotation {
139 output.push_str(": ");
140 output.push_str(annotation_stub(annotation, modules_to_import));
141 }
142 if let Some(default_value) = &argument.default_value {
143 output.push_str(if argument.annotation.is_some() {
144 " = "
145 } else {
146 "="
147 });
148 output.push_str(default_value);
149 }
150 output
151}
152
153fn variable_length_argument_stub(argument: &VariableLengthArgument) -> String {
154 argument.name.clone()
155}
156
157fn annotation_stub<'a>(annotation: &'a str, modules_to_import: &mut BTreeSet<String>) -> &'a str {
158 if let Some((module, _)) = annotation.rsplit_once('.') {
159 modules_to_import.insert(module.into());
161 }
162 annotation
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168 use crate::model::Arguments;
169
170 #[test]
171 fn function_stubs_with_variable_length() {
172 let function = Function {
173 name: "func".into(),
174 decorators: Vec::new(),
175 arguments: Arguments {
176 positional_only_arguments: vec![Argument {
177 name: "posonly".into(),
178 default_value: None,
179 annotation: None,
180 }],
181 arguments: vec![Argument {
182 name: "arg".into(),
183 default_value: None,
184 annotation: None,
185 }],
186 vararg: Some(VariableLengthArgument {
187 name: "varargs".into(),
188 }),
189 keyword_only_arguments: vec![Argument {
190 name: "karg".into(),
191 default_value: None,
192 annotation: Some("str".into()),
193 }],
194 kwarg: Some(VariableLengthArgument {
195 name: "kwarg".into(),
196 }),
197 },
198 returns: Some("list[str]".into()),
199 };
200 assert_eq!(
201 "def func(posonly, /, arg, *varargs, karg: str, **kwarg) -> list[str]: ...",
202 function_stubs(&function, &mut BTreeSet::new())
203 )
204 }
205
206 #[test]
207 fn function_stubs_without_variable_length() {
208 let function = Function {
209 name: "afunc".into(),
210 decorators: Vec::new(),
211 arguments: Arguments {
212 positional_only_arguments: vec![Argument {
213 name: "posonly".into(),
214 default_value: Some("1".into()),
215 annotation: None,
216 }],
217 arguments: vec![Argument {
218 name: "arg".into(),
219 default_value: Some("True".into()),
220 annotation: None,
221 }],
222 vararg: None,
223 keyword_only_arguments: vec![Argument {
224 name: "karg".into(),
225 default_value: Some("\"foo\"".into()),
226 annotation: Some("str".into()),
227 }],
228 kwarg: None,
229 },
230 returns: None,
231 };
232 assert_eq!(
233 "def afunc(posonly=1, /, arg=True, *, karg: str = \"foo\"): ...",
234 function_stubs(&function, &mut BTreeSet::new())
235 )
236 }
237}