Skip to main content

kinetics_parser/
parser.rs

1use crate::{
2    ParsedFunction, Role, params::{Cron, Endpoint, Params, Worker}
3};
4use color_eyre::eyre;
5use std::path::PathBuf;
6use syn::{parse::Parse, visit::Visit, Attribute, ItemFn};
7use walkdir::WalkDir;
8
9#[derive(Debug, Default)]
10pub struct Parser {
11    /// All found functions in the source code
12    pub functions: Vec<ParsedFunction>,
13
14    /// Relative path to currently processing file
15    pub relative_path: String,
16}
17
18impl Parser {
19    /// Init new Parser
20    ///
21    /// And optionally parse the requested dir
22    pub fn new(path: Option<&PathBuf>) -> eyre::Result<Self> {
23        let mut parser: Parser = Default::default();
24
25        if let Some(path) = path {
26            parser.walk_dir(path)?;
27        }
28
29        Ok(parser)
30    }
31
32    pub fn walk_dir(&mut self, path: &PathBuf) -> eyre::Result<()> {
33        for entry in WalkDir::new(path)
34            .into_iter()
35            .filter_map(|e| e.ok())
36            .filter(|e| {
37                e.path().strip_prefix(path).is_ok_and(|p| p.starts_with("src/")) // only src folder
38                && e.path().extension().is_some_and(|ext| ext == "rs") // only rust files
39            })
40        {
41            let content = std::fs::read_to_string(entry.path())?;
42            let syntax = syn::parse_file(&content)?;
43
44            // Set current file relative path for further imports resolution
45            // WARN It prevents to implement parallel parsing of files and requires rework in the future
46            self.set_relative_path(entry.path().strip_prefix(path)?.to_str());
47
48            self.visit_file(&syntax);
49        }
50
51        Ok(())
52    }
53
54    pub fn set_relative_path(&mut self, file_path: Option<&str>) {
55        self.relative_path = file_path.map_or_else(|| "".to_string(), |s| s.to_string());
56    }
57
58    fn parse_endpoint(&mut self, attr: &Attribute) -> syn::Result<Endpoint> {
59        attr.parse_args_with(Endpoint::parse)
60    }
61
62    fn parse_worker(&mut self, attr: &Attribute) -> syn::Result<Worker> {
63        attr.parse_args_with(Worker::parse)
64    }
65
66    fn parse_cron(&mut self, attr: &Attribute) -> syn::Result<Cron> {
67        attr.parse_args_with(Cron::parse)
68    }
69
70    /// Checks if the input is a valid kinetics_macro definition and returns its role
71    /// Known definitions:
72    /// #[kinetics_macro::<role> or <role>]
73    fn parse_attr_role(&self, input: &Attribute) -> String {
74        let path = input.path();
75
76        if path.segments.len() == 1 {
77            let ident = &path.segments[0].ident;
78            return ident.to_string();
79        }
80
81        if path.segments.len() == 2 && &path.segments[0].ident == "kinetics_macro" {
82            let ident = &path.segments[1].ident;
83            return ident.to_string();
84        }
85
86        "".to_string()
87    }
88}
89
90impl Visit<'_> for Parser {
91    /// Visits function definitions
92    fn visit_item_fn(&mut self, item: &ItemFn) {
93        for attr in &item.attrs {
94            // Skip non-endpoint or non-worker attributes
95            let (role, params) = match self.parse_attr_role(attr).as_str() {
96                "endpoint" => {
97                    let params = self.parse_endpoint(attr).unwrap();
98                    (Role::Endpoint, Params::Endpoint(params))
99                }
100                "worker" => {
101                    let params = self.parse_worker(attr).unwrap();
102                    (Role::Worker, Params::Worker(params))
103                }
104                "cron" => {
105                    let params = self.parse_cron(attr).unwrap();
106                    (Role::Cron, Params::Cron(params))
107                }
108                _ => continue,
109            };
110
111            self.functions.push(ParsedFunction {
112                role,
113                params,
114                rust_function_name: item.sig.ident.to_string(),
115                relative_path: self.relative_path.clone(),
116            });
117        }
118
119        // We don't need to parse the function body (in case nested functions), so just exit here
120    }
121}