r2x-ast 0.0.7

AST discovery utilities for the r2x CLI
Documentation
use super::*;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum KwArgRole {
    Name,
    EntryReference,
    Method,
    Description,
    Config,
    Store,
    IoType,
    Other,
}

impl KwArgRole {
    pub fn from_identifier(name: &str) -> Self {
        match name {
            "name" => KwArgRole::Name,
            "obj" | "callable" | "target" | "function" | "factory" | "entry" => {
                KwArgRole::EntryReference
            }
            "call_method" | "method" | "call_function" => KwArgRole::Method,
            "description" => KwArgRole::Description,
            "config" => KwArgRole::Config,
            "store" => KwArgRole::Store,
            "io_type" => KwArgRole::IoType,
            _ => KwArgRole::Other,
        }
    }
}

pub(super) struct KwArg {
    pub name: String,
    pub value: String,
    #[allow(dead_code)]
    pub arg_type: String,
    pub role: KwArgRole,
}

impl PluginExtractor {
    pub(super) fn extract_keyword_arguments_from_text(
        &self,
        call_text: &str,
    ) -> Result<Vec<KwArg>> {
        let mut args = Vec::new();

        if let Some(start) = call_text.find('(') {
            if let Some(end) = call_text.rfind(')') {
                let args_str = &call_text[start + 1..end];

                for arg in args_str.split(',') {
                    let arg = arg.trim();
                    if arg.is_empty() {
                        continue;
                    }

                    if let Some(eq_idx) = arg.find('=') {
                        let key = arg[..eq_idx].trim().to_string();
                        let value_str = arg[eq_idx + 1..].trim();
                        let arg_type = self.infer_argument_type(value_str);
                        let value = if arg_type == "string" {
                            value_str
                                .trim_matches(|c: char| c == '"' || c == '\'')
                                .to_string()
                        } else {
                            value_str.to_string()
                        };

                        args.push(KwArg {
                            name: key.clone(),
                            value: value.clone(),
                            arg_type: arg_type.clone(),
                            role: KwArgRole::from_identifier(&key),
                        });

                        debug!("Extracted arg: {} = {} (type: {})", key, value, arg_type);
                    }
                }
            }
        }

        Ok(args)
    }

    pub(super) fn infer_argument_type(&self, value_str: &str) -> String {
        let value_str = value_str.trim();

        if (value_str.starts_with('"') && value_str.ends_with('"'))
            || (value_str.starts_with('\'') && value_str.ends_with('\''))
        {
            return "string".to_string();
        }

        if value_str.parse::<i64>().is_ok() {
            return "number".to_string();
        }

        if value_str.parse::<f64>().is_ok() {
            return "float".to_string();
        }

        if value_str == "True" || value_str == "False" {
            return "boolean".to_string();
        }

        if value_str.contains('.')
            && value_str
                .chars()
                .all(|c| c.is_alphanumeric() || c == '_' || c == '.')
        {
            return "enum_value".to_string();
        }

        if value_str.chars().next().is_some_and(|c| c.is_uppercase()) {
            return "class_reference".to_string();
        }

        if value_str.starts_with('[') || value_str.starts_with('{') {
            return "complex".to_string();
        }

        "identifier".to_string()
    }

    pub(super) fn find_kwarg_value(&self, kwargs: &[KwArg], name: &str) -> Result<String> {
        kwargs
            .iter()
            .find(|arg| arg.name == name)
            .map(|arg| arg.value.clone())
            .ok_or_else(|| anyhow!("Argument '{}' not found", name))
    }
}