use serde::{Deserialize, Serialize};
#[derive(Debug, Clone)]
pub struct EntryPointInfo {
pub name: String,
pub module: String,
pub symbol: String,
pub section: String,
}
impl EntryPointInfo {
pub fn is_manifest_based(&self) -> bool {
let symbol_lower = self.symbol.to_lowercase();
symbol_lower == "manifest"
|| symbol_lower == "register_plugin"
|| symbol_lower.ends_with("_manifest")
|| symbol_lower.ends_with("_plugin")
}
pub fn is_class(&self) -> bool {
self.symbol
.chars()
.next()
.map(|c| c.is_uppercase())
.unwrap_or(false)
}
pub fn infer_kind(&self) -> PluginKind {
let section_suffix = self.section.strip_prefix("r2x.").unwrap_or(&self.section);
match section_suffix {
"transforms" | "modifiers" => PluginKind::Modifier,
"parsers" => PluginKind::Parser,
"exporters" => PluginKind::Exporter,
"upgraders" => PluginKind::Upgrader,
"utilities" => PluginKind::Utility,
"translations" => PluginKind::Translation,
_ if self.section == "r2x_plugin" => self.infer_kind_from_symbol(),
_ => PluginKind::Utility,
}
}
fn infer_kind_from_symbol(&self) -> PluginKind {
let lower = self.symbol.to_lowercase();
if lower.contains("parser") {
PluginKind::Parser
} else if lower.contains("export") {
PluginKind::Exporter
} else if lower.contains("upgrade") {
PluginKind::Upgrader
} else if lower.contains("modif") || lower.contains("transform") {
PluginKind::Modifier
} else if lower.contains("translat") {
PluginKind::Translation
} else {
PluginKind::Parser
}
}
pub fn full_entry(&self) -> String {
format!("{}:{}", self.module, self.symbol)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DiscoveredPlugin {
pub name: String,
pub kind: PluginKind,
pub entry: String,
pub invocation: InvocationSpec,
pub io: IOContract,
pub resources: Option<ResourceSpec>,
pub upgrade: Option<UpgradeSpec>,
pub description: Option<String>,
pub tags: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum PluginKind {
Parser,
Exporter,
Modifier,
Upgrader,
Utility,
Translation,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InvocationSpec {
pub implementation: ImplementationType,
pub method: Option<String>,
#[serde(default)]
pub constructor: Vec<ArgumentSpec>,
#[serde(default)]
pub call: Vec<ArgumentSpec>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum ImplementationType {
Class,
Function,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ArgumentSpec {
pub name: String,
pub annotation: Option<String>,
pub default: Option<String>,
pub required: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IOContract {
#[serde(default)]
pub consumes: Vec<IOSlot>,
#[serde(default)]
pub produces: Vec<IOSlot>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum IOSlot {
System,
ConfigFile,
StoreFolder,
File,
Folder,
Data,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResourceSpec {
pub store: Option<StoreSpec>,
pub config: Option<ConfigSpec>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StoreSpec {
pub mode: StoreMode,
pub path: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum StoreMode {
Folder,
Manifest,
Inline,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConfigSpec {
pub module: String,
pub name: String,
#[serde(default)]
pub fields: Vec<ConfigField>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConfigField {
pub name: String,
pub types: Vec<String>,
pub default: Option<String>,
pub required: bool,
pub description: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UpgradeSpec {
pub version_strategy_json: Option<String>,
pub version_reader_json: Option<String>,
pub upgrade_steps_json: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DecoratorRegistration {
pub decorator_class: String,
pub decorator_method: String,
pub function_name: String,
pub function_module: String,
pub source_file: Option<String>,
pub line_number: Option<usize>,
#[serde(default)]
pub decorator_args: toml::Table,
pub function_signature: Option<FunctionSignature>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FunctionSignature {
pub return_type: String,
#[serde(default)]
pub parameters: Vec<FunctionParameter>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FunctionParameter {
pub name: String,
pub param_type: String,
pub default: Option<String>,
#[serde(default)]
pub is_keyword_only: bool,
pub is_var_arg: Option<VarArgType>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum VarArgType {
Args,
Kwargs,
}
impl DiscoveredPlugin {
pub fn to_manifest_plugin(&self) -> r2x_manifest::Plugin {
use crate::schema_extractor::parse_union_types_from_annotation;
use r2x_manifest::{Plugin, PluginType};
use smallvec::SmallVec;
use std::sync::Arc;
let plugin_type = match self.invocation.implementation {
ImplementationType::Class => PluginType::Class,
ImplementationType::Function => PluginType::Function,
};
let (module, class_name, function_name) = if let Some(idx) = self.entry.rfind('.') {
let module = Arc::from(&self.entry[..idx]);
let symbol = &self.entry[idx + 1..];
match plugin_type {
PluginType::Class => (module, Some(Arc::from(symbol)), None),
PluginType::Function => (module, None, Some(Arc::from(symbol))),
}
} else {
(Arc::from(""), None, None)
};
const RUNTIME_PARAMS: &[&str] = &[
"self", "system", "config", "store", "stdin", "ctx", "context",
];
let config_spec = self.resources.as_ref().and_then(|r| r.config.as_ref());
let (config_class, config_module) = config_spec
.map(|c| {
(
Some(Arc::from(c.name.as_str())),
Some(Arc::from(c.module.as_str())),
)
})
.unwrap_or((None, None));
let config_fields = config_spec.map(|c| c.fields.as_slice()).unwrap_or(&[]);
let mut parameters: SmallVec<[r2x_manifest::Parameter; 4]> = config_fields
.iter()
.map(|field| r2x_manifest::Parameter {
name: Arc::from(field.name.as_str()),
types: field.types.iter().map(|t| Arc::from(t.as_str())).collect(),
module: None,
required: field.required,
default: field.default.as_ref().map(|d| Arc::from(d.as_str())),
description: field.description.as_ref().map(|d| Arc::from(d.as_str())),
})
.collect();
let existing_names: std::collections::HashSet<String> =
parameters.iter().map(|p| p.name.to_string()).collect();
for arg in self.invocation.call.iter() {
let name = arg.name.as_str();
if RUNTIME_PARAMS.contains(&name) || existing_names.contains(name) {
continue;
}
let types: SmallVec<[Arc<str>; 2]> = arg
.annotation
.as_deref()
.map(|ann| {
parse_union_types_from_annotation(ann)
.into_iter()
.map(|t| Arc::from(t.as_str()))
.collect()
})
.unwrap_or_else(|| SmallVec::from_elem(Arc::from("Any"), 1));
parameters.push(r2x_manifest::Parameter {
name: Arc::from(arg.name.as_str()),
types,
module: None,
required: arg.required,
default: arg.default.as_ref().map(|d| Arc::from(d.as_str())),
description: None,
});
}
Plugin {
name: Arc::from(self.name.as_str()),
plugin_type,
module,
class_name,
function_name,
config_class,
config_module,
hooks: SmallVec::new(),
parameters,
config_schema: Default::default(),
content_hash: 0,
}
}
}