pluma/RustIO.rs
1//! PluMA Rust Plugin Interface
2//!
3//! This module provides the interface for writing PluMA plugins in Rust.
4//! It integrates with the `pluma-plugin-trait` crate and provides FFI exports
5//! for the PluMA plugin system.
6//!
7//! # Example Plugin
8//!
9//! ```rust
10//! use pluma_plugin_trait::PlumaPlugin;
11//! use pluma_io::PluginManager;
12//!
13//! pub struct MyPlugin {
14//! data: String,
15//! }
16//!
17//! impl PlumaPlugin for MyPlugin {
18//! fn new() -> Self {
19//! MyPlugin { data: String::new() }
20//! }
21//!
22//! fn input(&mut self, filename: &str) {
23//! // Read input data
24//! self.data = std::fs::read_to_string(filename).unwrap_or_default();
25//! }
26//!
27//! fn run(&mut self) {
28//! // Process data
29//! self.data = self.data.to_uppercase();
30//! }
31//!
32//! fn output(&mut self, filename: &str) {
33//! // Write output data
34//! std::fs::write(filename, &self.data).ok();
35//! }
36//! }
37//!
38//! // Export the plugin for PluMA
39//! pluma_plugin_trait::export_plugin!(MyPlugin);
40//! ```
41
42use std::ffi::CStr;
43use std::os::raw::c_char;
44
45/// PluginManager provides access to PluMA runtime functions.
46/// This mirrors the C++ PluginManager interface.
47pub struct PluginManager;
48
49impl PluginManager {
50 /// Log a message to the PluMA log file
51 pub fn log(msg: &str) {
52 eprintln!("[PluMA/Rust] {}", msg);
53 }
54
55 /// Check if a plugin dependency is installed
56 pub fn dependency(plugin: &str) {
57 Self::log(&format!("Checking dependency: {}", plugin));
58 }
59
60 /// Get the current prefix path
61 pub fn prefix() -> String {
62 std::env::var("PLUMA_PREFIX").unwrap_or_else(|_| String::from(""))
63 }
64
65 /// Add prefix to a filename
66 pub fn add_prefix(filename: &str) -> String {
67 format!("{}/{}", Self::prefix(), filename)
68 }
69}
70
71/// Convert a C string pointer to a Rust String
72///
73/// # Safety
74/// The pointer must be a valid null-terminated C string
75pub unsafe fn c_str_to_string(ptr: *const c_char) -> String {
76 if ptr.is_null() {
77 return String::new();
78 }
79 CStr::from_ptr(ptr)
80 .to_str()
81 .unwrap_or("")
82 .to_string()
83}
84
85/// Macro to generate FFI exports for a PluMA plugin
86///
87/// This macro generates the necessary C-compatible functions that PluMA
88/// uses to load and execute Rust plugins.
89///
90/// # Usage
91/// ```rust
92/// use pluma_plugin_trait::PlumaPlugin;
93///
94/// struct MyPlugin { /* ... */ }
95///
96/// impl PlumaPlugin for MyPlugin {
97/// // ... implementation
98/// }
99///
100/// // Generate FFI exports
101/// pluma_export_plugin!(MyPlugin);
102/// ```
103#[macro_export]
104macro_rules! pluma_export_plugin {
105 ($plugin_type:ty) => {
106 /// Create a new plugin instance
107 #[no_mangle]
108 pub extern "C" fn plugin_create() -> *mut std::ffi::c_void {
109 let plugin = Box::new(<$plugin_type as pluma_plugin_trait::PlumaPlugin>::new());
110 Box::into_raw(plugin) as *mut std::ffi::c_void
111 }
112
113 /// Destroy a plugin instance
114 #[no_mangle]
115 pub extern "C" fn plugin_destroy(ptr: *mut std::ffi::c_void) {
116 if !ptr.is_null() {
117 unsafe {
118 let _ = Box::from_raw(ptr as *mut $plugin_type);
119 }
120 }
121 }
122
123 /// Execute the input phase
124 #[no_mangle]
125 pub extern "C" fn plugin_input(ptr: *mut std::ffi::c_void, filename: *const std::os::raw::c_char) {
126 if ptr.is_null() || filename.is_null() {
127 return;
128 }
129 unsafe {
130 let plugin = &mut *(ptr as *mut $plugin_type);
131 let filename_str = std::ffi::CStr::from_ptr(filename)
132 .to_str()
133 .unwrap_or("");
134 plugin.input(filename_str);
135 }
136 }
137
138 /// Execute the run phase
139 #[no_mangle]
140 pub extern "C" fn plugin_run(ptr: *mut std::ffi::c_void) {
141 if ptr.is_null() {
142 return;
143 }
144 unsafe {
145 let plugin = &mut *(ptr as *mut $plugin_type);
146 plugin.run();
147 }
148 }
149
150 /// Execute the output phase
151 #[no_mangle]
152 pub extern "C" fn plugin_output(ptr: *mut std::ffi::c_void, filename: *const std::os::raw::c_char) {
153 if ptr.is_null() || filename.is_null() {
154 return;
155 }
156 unsafe {
157 let plugin = &mut *(ptr as *mut $plugin_type);
158 let filename_str = std::ffi::CStr::from_ptr(filename)
159 .to_str()
160 .unwrap_or("");
161 plugin.output(filename_str);
162 }
163 }
164 };
165}
166
167#[cfg(test)]
168mod tests {
169 use super::*;
170
171 #[test]
172 fn test_c_str_conversion() {
173 let test_str = std::ffi::CString::new("test").unwrap();
174 unsafe {
175 let result = c_str_to_string(test_str.as_ptr());
176 assert_eq!(result, "test");
177 }
178 }
179
180 #[test]
181 fn test_null_c_str() {
182 unsafe {
183 let result = c_str_to_string(std::ptr::null());
184 assert_eq!(result, "");
185 }
186 }
187}