Skip to main content

dynamic_cli/plugin/builtin/
sysinfo.rs

1//! Standalone `SysInfoPlugin` (feature-gated).
2//!
3//! Prints basic runtime/OS information — operating system, architecture,
4//! and available parallelism — using only `std`. No new dependency: this
5//! is deliberately a minimal baseline (#45 / DD-025). A richer variant
6//! backed by the `sysinfo` crate is out of scope here; if pursued later,
7//! it stays under this same `sysinfo-plugin` feature flag rather than a
8//! second one, per the uniform feature-flag policy decided in the DD-025
9//! triage.
10//!
11//! Only available with the `sysinfo-plugin` feature.
12
13use crate::context::ExecutionContext;
14use crate::executor::CommandHandler;
15use crate::parser::ParsedArgs;
16use crate::plugin::Plugin;
17use crate::Result;
18
19/// Standalone plugin providing a single `sysinfo` command.
20///
21/// Reports the operating system, CPU architecture, and available
22/// parallelism (`std::thread::available_parallelism()`) — everything
23/// `std` can report without a third-party crate.
24///
25/// # YAML config
26///
27/// ```yaml
28/// commands:
29///   - name: sysinfo
30///     implementation: sysinfo_show
31///     description: "Show runtime/OS information"
32///     required: false
33///     arguments: []
34///     options: []
35/// ```
36///
37/// # Example
38///
39/// ```
40/// use dynamic_cli::plugin::{Plugin, SysInfoPlugin};
41///
42/// let plugin = SysInfoPlugin::new();
43/// assert_eq!(plugin.name(), "sysinfo");
44///
45/// let handlers = plugin.handlers();
46/// assert_eq!(handlers.len(), 1);
47/// assert_eq!(handlers[0].0, "sysinfo_show");
48/// ```
49pub struct SysInfoPlugin;
50
51impl SysInfoPlugin {
52    /// Create a new `SysInfoPlugin`.
53    ///
54    /// # Example
55    ///
56    /// ```
57    /// use dynamic_cli::plugin::{Plugin, SysInfoPlugin};
58    ///
59    /// let plugin = SysInfoPlugin::new();
60    /// assert_eq!(plugin.name(), "sysinfo");
61    /// ```
62    pub fn new() -> Self {
63        Self
64    }
65}
66
67impl Default for SysInfoPlugin {
68    fn default() -> Self {
69        Self::new()
70    }
71}
72
73impl Plugin for SysInfoPlugin {
74    fn name(&self) -> &str {
75        "sysinfo"
76    }
77
78    fn version(&self) -> &str {
79        env!("CARGO_PKG_VERSION")
80    }
81
82    fn description(&self) -> &str {
83        "Runtime/OS introspection (std-only baseline, feature-gated, #45)"
84    }
85
86    fn handlers(&self) -> Vec<(String, Box<dyn CommandHandler>)> {
87        vec![("sysinfo_show".to_string(), Box::new(SysInfoShowHandler))]
88    }
89}
90
91/// Handler for `sysinfo_show` — prints OS, architecture, and available
92/// parallelism.
93struct SysInfoShowHandler;
94
95impl CommandHandler for SysInfoShowHandler {
96    fn execute(&self, _ctx: &mut dyn ExecutionContext, _args: &ParsedArgs) -> Result<()> {
97        let parallelism = std::thread::available_parallelism()
98            .map(|n| n.get())
99            .unwrap_or(1);
100
101        println!("OS:                  {}", std::env::consts::OS);
102        println!("Architecture:        {}", std::env::consts::ARCH);
103        println!("Available parallelism: {}", parallelism);
104
105        Ok(())
106    }
107}
108
109// ============================================================================
110// Tests
111// ============================================================================
112
113#[cfg(test)]
114mod tests {
115    use super::*;
116    use std::any::Any;
117
118    #[derive(Default)]
119    struct TestContext;
120
121    impl ExecutionContext for TestContext {
122        fn as_any(&self) -> &dyn Any {
123            self
124        }
125        fn as_any_mut(&mut self) -> &mut dyn Any {
126            self
127        }
128    }
129
130    #[test]
131    fn test_sysinfo_plugin_metadata() {
132        let p = SysInfoPlugin::new();
133        assert_eq!(p.name(), "sysinfo");
134        assert!(!p.version().is_empty());
135        assert!(!p.description().is_empty());
136    }
137
138    #[test]
139    fn test_sysinfo_plugin_default() {
140        let p = SysInfoPlugin::default();
141        assert_eq!(p.name(), "sysinfo");
142    }
143
144    #[test]
145    fn test_sysinfo_plugin_handler_name() {
146        let handlers = SysInfoPlugin::new().handlers();
147        assert_eq!(handlers.len(), 1);
148        assert_eq!(handlers[0].0, "sysinfo_show");
149    }
150
151    #[test]
152    fn test_sysinfo_handler_executes() {
153        let handlers = SysInfoPlugin::new().handlers();
154        let (_, handler) = &handlers[0];
155        let mut ctx = TestContext;
156        assert!(handler
157            .execute(&mut ctx, &ParsedArgs::from_scalars(Default::default()))
158            .is_ok());
159    }
160
161    #[test]
162    fn test_sysinfo_plugin_is_send_sync() {
163        fn assert_send_sync<T: Send + Sync>(_: T) {}
164        assert_send_sync(SysInfoPlugin::new());
165    }
166}