flag_rs/
active_help.rs

1//! ActiveHelp system for contextual hints during completion
2//!
3//! ActiveHelp provides contextual assistance to users as they type commands,
4//! similar to Cobra's ActiveHelp feature. Help messages can be static or
5//! conditional based on the current command context.
6
7use crate::context::Context;
8
9/// Type alias for `ActiveHelp` condition functions
10pub type ConditionFn = dyn Fn(&Context) -> bool + Send + Sync;
11
12/// Represents an `ActiveHelp` message with optional condition
13pub struct ActiveHelp {
14    /// The help message to display
15    pub message: String,
16    /// Optional condition that must be true for this help to be shown
17    pub condition: Option<std::sync::Arc<ConditionFn>>,
18}
19
20impl ActiveHelp {
21    /// Creates a new `ActiveHelp` message without condition
22    ///
23    /// # Examples
24    ///
25    /// ```rust
26    /// use flag_rs::active_help::ActiveHelp;
27    ///
28    /// let help = ActiveHelp::new("Use TAB to see available options");
29    /// ```
30    #[must_use]
31    pub fn new<S: Into<String>>(message: S) -> Self {
32        Self {
33            message: message.into(),
34            condition: None,
35        }
36    }
37
38    /// Creates a new `ActiveHelp` message with a condition
39    ///
40    /// The help will only be shown when the condition returns true.
41    ///
42    /// # Examples
43    ///
44    /// ```rust
45    /// use flag_rs::active_help::ActiveHelp;
46    ///
47    /// let help = ActiveHelp::with_condition(
48    ///     "Tip: Use -n <namespace> to filter results",
49    ///     |ctx| ctx.flag("namespace").is_none()
50    /// );
51    /// ```
52    #[must_use]
53    pub fn with_condition<S, F>(message: S, condition: F) -> Self
54    where
55        S: Into<String>,
56        F: Fn(&Context) -> bool + Send + Sync + 'static,
57    {
58        Self {
59            message: message.into(),
60            condition: Some(std::sync::Arc::new(condition)),
61        }
62    }
63
64    /// Checks if this help should be displayed given the current context
65    #[must_use]
66    pub fn should_display(&self, ctx: &Context) -> bool {
67        self.condition
68            .as_ref()
69            .map_or(true, |condition| condition(ctx))
70    }
71}
72
73impl Clone for ActiveHelp {
74    fn clone(&self) -> Self {
75        Self {
76            message: self.message.clone(),
77            condition: self.condition.clone(),
78        }
79    }
80}
81
82impl std::fmt::Debug for ActiveHelp {
83    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84        f.debug_struct("ActiveHelp")
85            .field("message", &self.message)
86            .field("condition", &self.condition.is_some())
87            .finish()
88    }
89}
90
91/// Configuration for `ActiveHelp` behavior
92#[derive(Clone, Debug)]
93pub struct ActiveHelpConfig {
94    /// Whether to show help on double-TAB
95    pub show_on_double_tab: bool,
96    /// Whether to show help when no completions are available
97    pub show_on_no_completions: bool,
98    /// Whether to disable `ActiveHelp` globally
99    pub disabled: bool,
100    /// Environment variable to check for disabling `ActiveHelp`
101    pub disable_env_var: Option<String>,
102}
103
104impl Default for ActiveHelpConfig {
105    fn default() -> Self {
106        Self {
107            show_on_double_tab: true,
108            show_on_no_completions: true,
109            disabled: false,
110            disable_env_var: Some("COBRA_ACTIVE_HELP".to_string()), // Compatible with Cobra
111        }
112    }
113}
114
115impl ActiveHelpConfig {
116    /// Checks if `ActiveHelp` is enabled based on configuration and environment
117    #[must_use]
118    pub fn is_enabled(&self) -> bool {
119        if self.disabled {
120            return false;
121        }
122
123        // Check environment variable if configured
124        if let Some(ref env_var) = self.disable_env_var {
125            if let Ok(value) = std::env::var(env_var) {
126                // If set to "0" or "false", ActiveHelp is disabled
127                return !matches!(value.to_lowercase().as_str(), "0" | "false");
128            }
129        }
130
131        true
132    }
133}
134
135#[cfg(test)]
136mod tests {
137    use super::*;
138
139    #[test]
140    fn test_active_help_new() {
141        let help = ActiveHelp::new("Test message");
142        assert_eq!(help.message, "Test message");
143        assert!(help.condition.is_none());
144    }
145
146    #[test]
147    fn test_active_help_with_condition() {
148        let help = ActiveHelp::with_condition("Conditional help", |_ctx| true);
149        assert_eq!(help.message, "Conditional help");
150        assert!(help.condition.is_some());
151    }
152
153    #[test]
154    fn test_should_display() {
155        let ctx = Context::new(vec![]);
156
157        // Help without condition should always display
158        let help = ActiveHelp::new("Always shown");
159        assert!(help.should_display(&ctx));
160
161        // Help with always-true condition
162        let help = ActiveHelp::with_condition("Also shown", |_| true);
163        assert!(help.should_display(&ctx));
164
165        // Help with always-false condition
166        let help = ActiveHelp::with_condition("Never shown", |_| false);
167        assert!(!help.should_display(&ctx));
168    }
169
170    #[test]
171    fn test_active_help_config_default() {
172        let config = ActiveHelpConfig::default();
173        assert!(config.show_on_double_tab);
174        assert!(config.show_on_no_completions);
175        assert!(!config.disabled);
176        assert_eq!(
177            config.disable_env_var,
178            Some("COBRA_ACTIVE_HELP".to_string())
179        );
180    }
181
182    #[test]
183    fn test_active_help_config_is_enabled() {
184        // Default config should be enabled
185        let config = ActiveHelpConfig::default();
186        assert!(config.is_enabled());
187
188        // Disabled config
189        let config = ActiveHelpConfig {
190            disabled: true,
191            ..Default::default()
192        };
193        assert!(!config.is_enabled());
194    }
195}