postcard_bindgen_core/code_gen/python/
mod.rs1mod des;
2mod general;
3mod generateable;
4mod ser;
5mod type_checks;
6
7use core::borrow::Borrow;
8
9use convert_case::{Case, Casing};
10use des::{gen_des_functions, gen_deserialize_func, gen_deserializer_code};
11use genco::{lang::python::Python, quote, quote_in, tokens::FormatInto};
12use general::gen_util;
13use generateable::{gen_basic_typings, gen_typings};
14use ser::{gen_ser_functions, gen_serialize_func, gen_serializer_code};
15use type_checks::gen_type_checks;
16
17use crate::{
18 code_gen::import_registry::ImportMode, path::PathBuf, registry::ContainerCollection, Exports,
19};
20
21use super::{
22 import_registry::{ImportItem, Package},
23 utils::{IfBranchedTemplate, TokensBranchedIterExt, TokensIterExt},
24};
25
26const PYTHON_OBJECT_VARIABLE: &str = "v";
27const PYTHON_LOGIC_AND: &str = "and";
28const PYTHON_LOGIC_OR: &str = "or";
29
30type Tokens = genco::lang::python::Tokens;
31
32type VariablePath = super::variable_path::VariablePath<Python>;
33type VariableAccess = super::variable_path::VariableAccess;
34type FieldAccessor<'a> = super::field_accessor::FieldAccessor<'a>;
35type AvailableCheck = super::available_check::AvailableCheck<Python>;
36type ImportRegistry = super::import_registry::ImportRegistry;
37type ExportFile = crate::ExportFile<Python>;
38type FunctionArg = super::function::FunctionArg<Python>;
39type Function = super::function::Function<Python>;
40
41#[derive(Debug)]
51pub struct GenerationSettings {
52 ser: bool,
53 des: bool,
54 runtime_type_checks: bool,
55 module_structure: bool,
56}
57
58impl GenerationSettings {
59 pub fn enable_all() -> Self {
61 Self {
62 ser: true,
63 des: true,
64 runtime_type_checks: true,
65 module_structure: true,
66 }
67 }
68
69 pub fn serialization(mut self, enabled: bool) -> Self {
71 self.ser = enabled;
72 self
73 }
74
75 pub fn deserialization(mut self, enabled: bool) -> Self {
77 self.des = enabled;
78 self
79 }
80
81 pub fn runtime_type_checks(mut self, enabled: bool) -> Self {
85 self.runtime_type_checks = enabled;
86 self
87 }
88
89 pub fn module_structure(mut self, enabled: bool) -> Self {
98 self.module_structure = enabled;
99 self
100 }
101}
102
103impl Default for GenerationSettings {
104 fn default() -> Self {
105 Self {
106 ser: false,
107 des: true,
108 runtime_type_checks: false,
109 module_structure: true,
110 }
111 }
112}
113
114pub fn generate(
115 mut containers: ContainerCollection,
116 gen_settings: impl Borrow<GenerationSettings>,
117 generate_package_name: String,
118) -> Exports<Python> {
119 let generate_package_name = generate_package_name.to_case(Case::Snake);
120 let gen_settings = gen_settings.borrow();
121
122 if !gen_settings.module_structure {
123 containers.flatten();
124 }
125
126 let mut files = Vec::new();
127
128 files.push(ExportFile {
129 content_type: "util".to_owned(),
130 content: gen_util(),
131 });
132
133 files.push(ExportFile {
134 content_type: "basic_types".to_owned(),
135 content: gen_basic_typings(),
136 });
137
138 files.extend(gen_typings(&containers, generate_package_name.clone()));
139
140 if gen_settings.runtime_type_checks {
141 let type_checks = gen_type_checks(containers.all_containers());
142
143 let type_checks = quote! {
144 from .util import *
145 from .types import *
146
147 $type_checks
148 };
149
150 files.push(ExportFile {
151 content_type: "runtime_checks".to_owned(),
152 content: type_checks,
153 });
154 }
155
156 if gen_settings.ser {
157 let serializer_code = gen_serializer_code();
158 let ser_code = quote! {
159 from .types import *
160 from .util import *
161 from .serializer import Serializer
162
163 $(gen_ser_functions(containers.all_containers()))
164
165 $(gen_serialize_func(containers.all_containers(), gen_settings.runtime_type_checks))
166 };
167
168 files.push(ExportFile {
169 content_type: "serializer".to_owned(),
170 content: serializer_code,
171 });
172
173 files.push(ExportFile {
174 content_type: "ser".to_owned(),
175 content: ser_code,
176 });
177 }
178
179 if gen_settings.des {
180 let deserializer_code = gen_deserializer_code();
181 let des_code = quote! {
182 from typing import TypeVar, Type, cast, Tuple
183
184 from .types import *
185 from .util import *
186 from .deserializer import Deserializer
187
188 $(gen_des_functions(containers.all_containers()))
189
190 $(gen_deserialize_func(containers.all_containers()))
191 };
192
193 files.push(ExportFile {
194 content_type: "deserializer".to_owned(),
195 content: deserializer_code,
196 });
197
198 files.push(ExportFile {
199 content_type: "des".to_owned(),
200 content: des_code,
201 });
202 }
203
204 let mut import_registry = ImportRegistry::new(generate_package_name);
205 import_registry.push(Package::Relative("types".into()), ImportItem::All);
206 import_registry.push(Package::Relative("basic_types".into()), ImportItem::All);
207
208 if gen_settings.des {
209 import_registry.push(
210 Package::Relative("des".into()),
211 ImportItem::Single("deserialize".into()),
212 );
213 }
214
215 if gen_settings.ser {
216 import_registry.push(
217 Package::Relative("ser".into()),
218 ImportItem::Single("serialize".into()),
219 );
220 }
221
222 files.push(ExportFile {
223 content_type: "__init__".to_owned(),
224 content: quote!($import_registry),
225 });
226
227 Exports { files }
228}
229
230impl<I, F> TokensIterExt<Python, F> for I
231where
232 I: Iterator<Item = F>,
233 F: FormatInto<Python>,
234{
235 const LOGICAL_AND: &'static str = PYTHON_LOGIC_AND;
236 const LOGICAL_OR: &'static str = PYTHON_LOGIC_OR;
237}
238
239pub(super) struct BranchedTemplate;
240
241impl IfBranchedTemplate<Python> for BranchedTemplate {
242 const IF_BRANCH: &'static str = "if";
243 const IF_ELSE_BRANCH: &'static str = "elif";
244 const ELSE_BRANCH: &'static str = "else";
245
246 fn push_condition(tokens: &mut Tokens, condition: impl FormatInto<Python>) {
247 tokens.append(condition)
248 }
249
250 fn push_condition_block(tokens: &mut Tokens, body: impl FormatInto<Python>) {
251 tokens.append(":");
252 tokens.indent();
253 tokens.append(body);
254 tokens.unindent();
255 }
256}
257
258impl<I> TokensBranchedIterExt<Python> for I
259where
260 I: Iterator<Item = (Option<Tokens>, Tokens)>,
261{
262 type Template = BranchedTemplate;
263}
264
265impl FormatInto<Python> for FieldAccessor<'_> {
266 fn format_into(self, tokens: &mut Tokens) {
267 quote_in! { *tokens =>
268 $(match self {
269 Self::Array | Self::None => (),
270 Self::Object(n) => $n = $[' '],
271 })
272 }
273 }
274}
275
276impl FormatInto<Python> for VariablePath {
277 fn format_into(self, tokens: &mut genco::Tokens<Python>) {
278 quote_in! { *tokens =>
279 $(self.start_variable)
280 }
281 self.parts
282 .into_iter()
283 .for_each(|part| part.format_into(tokens))
284 }
285}
286
287impl Default for VariablePath {
288 fn default() -> Self {
289 Self::new(PYTHON_OBJECT_VARIABLE.to_owned())
290 }
291}
292
293impl FormatInto<Python> for VariableAccess {
294 fn format_into(self, tokens: &mut genco::Tokens<Python>) {
295 quote_in! { *tokens =>
296 $(match self {
297 Self::Indexed(index) => [$index],
298 Self::Field(name) => .$name,
299 })
300 }
301 }
302}
303
304impl FormatInto<Python> for AvailableCheck {
305 fn format_into(self, tokens: &mut Tokens) {
306 quote_in! { *tokens =>
307 $(match self {
308 AvailableCheck::Object(..) => (),
309 AvailableCheck::None => ()
310 })
311 }
312 }
313}
314
315impl FormatInto<Python> for ImportRegistry {
316 fn format_into(self, tokens: &mut Tokens) {
317 let (base_path, items) = self.into_items_sorted();
318 for (package, imports) in items {
319 let joiner = ".";
320 let package = match package {
321 Package::Relative(path) => format!(".{}", path.into_path(joiner)),
322 Package::Extern(path) => path.into_path(joiner).to_string(),
323 Package::Intern(mut path) => {
324 if !path.is_empty() {
325 path.push_front(base_path.as_str());
326 path.into_path(joiner).to_string()
327 } else {
328 PathBuf::new()
329 .join(base_path.as_str())
330 .into_path(joiner)
331 .to_string()
332 }
333 }
334 Package::Root => base_path.to_owned(),
335 };
336
337 quote_in!(*tokens=> from $(package) import);
338 tokens.space();
339
340 match imports {
341 ImportMode::All => quote_in!(*tokens=> *),
342 ImportMode::Single(items) => {
343 let items = items.iter().map(|i| {
344 if let Some(alias) = &i.alias {
345 quote!($(&i.name) as $alias)
346 } else {
347 quote!($(&i.name))
348 }
349 });
350 quote_in!(*tokens=> $(for part in items join (, ) => $part))
351 }
352 }
353
354 tokens.push();
355 }
356 }
357}
358
359impl FormatInto<Python> for FunctionArg {
360 fn format_into(self, tokens: &mut Tokens) {
361 if let Some(r#type) = self.r#type {
362 quote_in! { *tokens =>
363 $(self.name): $r#type
364 }
365 } else {
366 quote_in! { *tokens =>
367 $(self.name)
368 }
369 }
370 }
371}
372
373impl FormatInto<Python> for Function {
374 fn format_into(self, tokens: &mut Tokens) {
375 let doc_string = self.doc_string.map(|doc_string| {
376 let mut tokens = Tokens::new();
377
378 tokens.append("\"\"\"");
379 tokens.append(
380 doc_string
381 .lines()
382 .enumerate()
383 .map(|(i, f)| {
384 if i > 0 {
385 format!(" {}", f)
386 } else {
387 f.to_string()
388 }
389 })
390 .collect::<Vec<_>>()
391 .join("\n"),
392 );
393 tokens.append("\"\"\"");
394 tokens
395 });
396
397 let return_type = self.return_type.map(|r| quote!($(" ")-> $r));
398 quote_in! { *tokens =>
399 def $(self.name)($(for arg in self.args join (, ) => $arg))$return_type:
400 $(doc_string)
401 $(self.body)
402 }
403 }
404}
405
406#[cfg(test)]
407mod test {
408 use genco::tokens::FormatInto;
409
410 use super::Tokens;
411
412 #[test]
413 fn test_import_registry_format() {
414 use super::{ImportItem, ImportRegistry, Package};
415
416 let mut import_registry = ImportRegistry::new("package".to_owned());
417 import_registry.push(
418 Package::Relative("basic_types".into()),
419 ImportItem::Aliased {
420 item_name: "A".into(),
421 alias: "A__A".into(),
422 },
423 );
424 import_registry.push(
425 Package::Intern("des".into()),
426 ImportItem::Single("deserialize".into()),
427 );
428 import_registry.push(
429 Package::Extern("ser".into()),
430 ImportItem::Single("serialize".into()),
431 );
432 import_registry.push(Package::Relative("types".into()), ImportItem::All);
433
434 let mut tokens = Tokens::new();
435 import_registry.format_into(&mut tokens);
436
437 assert_eq!(
438 tokens.to_file_string().unwrap(),
439 format!(
440 r#"from ser import serialize
441from package.des import deserialize
442from .basic_types import A as A__A
443from .types import *
444"#
445 )
446 );
447 }
448}