ligen_python_parser/module/
mod.rs

1use crate::prelude::*;
2use ligen::{ir::Module, parser::ParserConfig};
3use rustpython_parser::ast::ModModule;
4use crate::parser::PythonParser;
5
6#[derive(Default)]
7pub struct ModuleParser;
8
9impl Parser<&str> for ModuleParser {
10    type Output = WithSource<ModModule>;
11    fn parse(&self, input: &str, _config: &ParserConfig) -> Result<Self::Output> {
12        let module = parse(input, Mode::Module, "<embedded>")
13            .map_err(|error| Error::Message(format!("Failed to parse module: {}", error)))?
14            .module()
15            .ok_or(Error::Message("No module found".into()))?;
16        Ok(WithSource::new(input, module))
17    }
18}
19
20impl Parser<WithSource<ModModule>> for PythonParser {
21    type Output = Module;
22    fn parse(&self, input: WithSource<ModModule>, config: &ParserConfig) -> Result<Self::Output> {
23        let scope = self.parse(input.sub(input.ast.body.as_slice()), config)?;
24        let imports = scope.imports;
25        let objects = scope.objects;
26        let types = scope.types;
27        let functions = scope.functions;
28        let interfaces = scope.interfaces;
29        Ok(Module { objects, functions, types, interfaces, imports, .. Default::default() })
30    }
31}
32
33pub(crate) struct Directory<'a>(pub &'a std::path::Path);
34pub(crate) struct File<'a>(pub &'a std::path::Path);
35pub(crate) struct SubPath<'a>(pub &'a std::path::Path);
36
37impl Parser<File<'_>> for PythonParser {
38    type Output = Module;
39    fn parse(&self, File(input): File<'_>, config: &ParserConfig) -> Result<Self::Output> {
40        let content = std::fs::read_to_string(input)?;
41        let module = ModuleParser.parse(content.as_str(), config)?;
42        let mut module = self.parse(module, config)?;
43        module.identifier = self.identifier_parser.parse(input, config)?;
44        Ok(module)
45    }
46}
47
48impl Parser<Directory<'_>> for PythonParser {
49    type Output = Module;
50    fn parse(&self, Directory(input): Directory<'_>, config: &ParserConfig) -> Result<Self::Output> {
51        let identifier = self.identifier_parser.parse(input, config)?;
52        let mut module = Module { identifier, .. Default::default() };
53        let mut modules: Vec<Module> = Vec::new();
54        for entry in input.read_dir()? {
55            let entry = entry?;
56            let path = entry.path();
57            let extension = path
58                .extension()
59                .and_then(|extension| extension.to_str())
60                .map(String::from)
61                .unwrap_or_default();
62            if extension == "py" || extension == "pyi" || path.is_dir() {
63                if let Ok(module) = self.parse(SubPath(path.as_path()), config) {
64                    if let Some(existing) = modules
65                        .iter_mut()
66                        .find(|existing| existing.identifier == module.identifier)
67                    {
68                        existing.join(module)
69                    } else {
70                        modules.push(module);
71                    }
72                }
73            }
74        }
75        if let Some((index, _)) = modules
76            .iter()
77            .enumerate()
78            .find(|(_, sub_module)| sub_module.identifier.name == "__init__")
79        {
80            let identifier = module.identifier;
81            module = modules.remove(index);
82            module.identifier = identifier;
83        }
84        module.modules = modules;
85        Ok(module)
86    }
87}
88
89impl Parser<SubPath<'_>> for PythonParser {
90    type Output = Module;
91    fn parse(&self, SubPath(input): SubPath<'_>, config: &ParserConfig) -> Result<Self::Output> {
92        let input = if input.with_extension("py").exists() {
93            input.with_extension("py")
94        } else {
95            input.to_path_buf()
96        };
97
98        let input = input.as_path();
99
100        if input.is_dir() {
101            self.parse(Directory(input), config)
102        } else {
103            self.parse(File(input), config)
104                .map_err(|error| Error::Message(format!("Failed to read {}. Cause: {:?}", input.display(), error)))
105        }        
106    }
107}