Skip to main content

dynamic_cli/plugin/builtin/
version.rs

1//! Standalone `VersionPlugin`.
2//!
3//! Contributes the same `system_version` handler as [`SystemPlugin`],
4//! reusing [`SystemVersionHandler`]'s logic internally — no duplicated
5//! logic (#44 / DD-025).
6//!
7//! [`SystemPlugin`]: crate::plugin::system::SystemPlugin
8//! [`SystemVersionHandler`]: crate::plugin::system::SystemVersionHandler
9
10use crate::config::schema::CommandsConfig;
11use crate::executor::CommandHandler;
12use crate::plugin::system::SystemVersionHandler;
13use crate::plugin::Plugin;
14
15/// Standalone builtin providing only the `version` command.
16///
17/// Use this instead of [`SystemPlugin`][crate::builtin::system::SystemPlugin]
18/// when an application wants `version` without also registering `help` and
19/// `exit`.
20///
21/// # YAML config
22///
23/// ```yaml
24/// commands:
25///   - name: version
26///     implementation: system_version
27///     description: "Show version"
28///     required: false
29///     arguments: []
30///     options: []
31/// ```
32///
33/// # Example
34///
35/// ```
36/// use dynamic_cli::plugin::{Plugin, VersionPlugin};
37///
38/// let builtin = VersionPlugin::new();
39/// assert_eq!(builtin.name(), "version");
40///
41/// let handlers = builtin.handlers();
42/// assert_eq!(handlers.len(), 1);
43/// assert_eq!(handlers[0].0, "system_version");
44/// ```
45pub struct VersionPlugin {
46    /// Application config, needed to read `metadata.version`.
47    config: Option<CommandsConfig>,
48}
49
50impl VersionPlugin {
51    /// Create a new `VersionPlugin` with no config attached.
52    ///
53    /// # Example
54    ///
55    /// ```
56    /// use dynamic_cli::plugin::{Plugin, VersionPlugin};
57    ///
58    /// let builtin = VersionPlugin::new();
59    /// assert_eq!(builtin.name(), "version");
60    /// ```
61    pub fn new() -> Self {
62        Self { config: None }
63    }
64
65    /// Attach a config so the handler can read `metadata.version`.
66    ///
67    /// Call this yourself before
68    /// [`register_plugin`][crate::builder::CliBuilder::register_plugin] —
69    /// `CliBuilder::build()` does not attach config to plugins on its own.
70    ///
71    /// # Example
72    ///
73    /// ```
74    /// use dynamic_cli::plugin::{Plugin, VersionPlugin};
75    /// use dynamic_cli::config::schema::{CommandsConfig, Metadata};
76    ///
77    /// let config = CommandsConfig {
78    ///     metadata: Metadata {
79    ///         version: "1.0.0".to_string(),
80    ///         prompt: "myapp".to_string(),
81    ///         prompt_suffix: " > ".to_string(),
82    ///     },
83    ///     commands: vec![],
84    ///     global_options: vec![],
85    /// };
86    ///
87    /// let builtin = VersionPlugin::new().with_config(config);
88    /// assert_eq!(builtin.name(), "version");
89    /// ```
90    pub fn with_config(mut self, config: CommandsConfig) -> Self {
91        self.config = Some(config);
92        self
93    }
94}
95
96impl Default for VersionPlugin {
97    fn default() -> Self {
98        Self::new()
99    }
100}
101
102impl Plugin for VersionPlugin {
103    fn name(&self) -> &str {
104        "version"
105    }
106
107    fn version(&self) -> &str {
108        env!("CARGO_PKG_VERSION")
109    }
110
111    fn description(&self) -> &str {
112        "Standalone version command (split out of SystemPlugin, #44)"
113    }
114
115    fn handlers(&self) -> Vec<(String, Box<dyn CommandHandler>)> {
116        vec![(
117            "system_version".to_string(),
118            Box::new(SystemVersionHandler::new(self.config.clone())),
119        )]
120    }
121}
122
123// ============================================================================
124// Tests
125// ============================================================================
126
127#[cfg(test)]
128mod tests {
129    use super::*;
130    use crate::config::schema::{CommandsConfig, Metadata};
131    use crate::context::ExecutionContext;
132    use crate::parser::ParsedArgs;
133    use std::any::Any;
134    use std::collections::HashMap;
135
136    #[derive(Default)]
137    struct TestContext;
138
139    impl ExecutionContext for TestContext {
140        fn as_any(&self) -> &dyn Any {
141            self
142        }
143        fn as_any_mut(&mut self) -> &mut dyn Any {
144            self
145        }
146    }
147
148    fn test_config() -> CommandsConfig {
149        CommandsConfig {
150            metadata: Metadata {
151                version: "2.0.0".to_string(),
152                prompt: "testapp".to_string(),
153                prompt_suffix: " > ".to_string(),
154            },
155            commands: vec![],
156            global_options: vec![],
157        }
158    }
159
160    #[test]
161    fn test_version_plugin_metadata() {
162        let p = VersionPlugin::new();
163        assert_eq!(p.name(), "version");
164        assert!(!p.version().is_empty());
165        assert!(!p.description().is_empty());
166    }
167
168    #[test]
169    fn test_version_plugin_default() {
170        let p = VersionPlugin::default();
171        assert_eq!(p.name(), "version");
172    }
173
174    #[test]
175    fn test_version_plugin_handler_name() {
176        let handlers = VersionPlugin::new().handlers();
177        assert_eq!(handlers.len(), 1);
178        assert_eq!(handlers[0].0, "system_version");
179    }
180
181    #[test]
182    fn test_version_plugin_with_config() {
183        let plugin = VersionPlugin::new().with_config(test_config());
184        assert!(plugin.config.is_some());
185        assert_eq!(plugin.config.unwrap().metadata.version, "2.0.0");
186    }
187
188    #[test]
189    fn test_version_handler_executes_with_config() {
190        let plugin = VersionPlugin::new().with_config(test_config());
191        let handlers = plugin.handlers();
192        let (_, handler) = &handlers[0];
193        let mut ctx = TestContext;
194        assert!(handler
195            .execute(&mut ctx, &ParsedArgs::from_scalars(HashMap::new()))
196            .is_ok());
197    }
198
199    #[test]
200    fn test_version_handler_executes_without_config() {
201        let handlers = VersionPlugin::new().handlers();
202        let (_, handler) = &handlers[0];
203        let mut ctx = TestContext;
204        assert!(handler
205            .execute(&mut ctx, &ParsedArgs::from_scalars(HashMap::new()))
206            .is_ok());
207    }
208
209    #[test]
210    fn test_version_plugin_is_send_sync() {
211        fn assert_send_sync<T: Send + Sync>(_: T) {}
212        assert_send_sync(VersionPlugin::new());
213    }
214}