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}