Skip to main content

tui_dispatch_debug/
cli.rs

1use clap::Args;
2use std::path::PathBuf;
3
4use crate::debug::ActionLoggerConfig;
5use crate::pattern_utils::split_patterns_csv;
6
7/// Shared CLI flags for debug tooling.
8#[derive(Args, Debug, Clone, Default)]
9#[command(next_help_heading = "Debug")]
10pub struct DebugCliArgs {
11    /// Enable debug mode (F12 to toggle overlay)
12    #[arg(long = "debug")]
13    pub enabled: bool,
14
15    /// Render a single frame and exit (after applying debug state/actions)
16    #[arg(long = "debug-render-once")]
17    pub render_once: bool,
18
19    /// Load initial state from a JSON snapshot
20    #[arg(long = "debug-state-in")]
21    pub state_in: Option<PathBuf>,
22
23    /// Load and replay actions from a JSON snapshot
24    #[arg(long = "debug-actions-in")]
25    pub actions_in: Option<PathBuf>,
26
27    /// Save dispatched actions to a JSON snapshot
28    #[arg(long = "debug-actions-out")]
29    pub actions_out: Option<PathBuf>,
30
31    /// Include action patterns when recording debug actions (comma-separated)
32    #[arg(long = "debug-actions-include")]
33    pub actions_include: Option<String>,
34
35    /// Exclude action patterns when recording debug actions (comma-separated)
36    #[arg(long = "debug-actions-exclude")]
37    pub actions_exclude: Option<String>,
38
39    /// Include action categories when recording debug actions (comma-separated)
40    #[arg(long = "debug-actions-include-categories")]
41    pub actions_include_categories: Option<String>,
42
43    /// Exclude action categories when recording debug actions (comma-separated)
44    #[arg(long = "debug-actions-exclude-categories")]
45    pub actions_exclude_categories: Option<String>,
46
47    /// Include exact action names when recording debug actions (comma-separated)
48    #[arg(long = "debug-actions-include-names")]
49    pub actions_include_names: Option<String>,
50
51    /// Exclude exact action names when recording debug actions (comma-separated)
52    #[arg(long = "debug-actions-exclude-names")]
53    pub actions_exclude_names: Option<String>,
54
55    /// Save JSON schema for state type to file
56    #[arg(long = "debug-state-schema-out")]
57    pub state_schema_out: Option<PathBuf>,
58
59    /// Save JSON schema for action type to file
60    #[arg(long = "debug-actions-schema-out")]
61    pub actions_schema_out: Option<PathBuf>,
62
63    /// Timeout in seconds for awaiting async actions during replay
64    #[arg(long = "debug-replay-timeout", default_value_t = 30)]
65    pub replay_timeout: u64,
66}
67
68impl DebugCliArgs {
69    pub fn action_filter(&self) -> ActionLoggerConfig {
70        let mut include_patterns = split_patterns(self.actions_include.as_deref());
71        include_patterns.extend(category_filters(self.actions_include_categories.as_deref()));
72        include_patterns.extend(name_filters(self.actions_include_names.as_deref()));
73
74        let mut exclude_patterns = split_patterns(self.actions_exclude.as_deref());
75        exclude_patterns.extend(category_filters(self.actions_exclude_categories.as_deref()));
76        exclude_patterns.extend(name_filters(self.actions_exclude_names.as_deref()));
77
78        if include_patterns.is_empty() && exclude_patterns.is_empty() {
79            ActionLoggerConfig::default()
80        } else {
81            ActionLoggerConfig::with_patterns(include_patterns, exclude_patterns)
82        }
83    }
84
85    pub fn auto_fetch(&self) -> bool {
86        self.state_in.is_none() && self.actions_in.is_none()
87    }
88}
89
90fn split_patterns(value: Option<&str>) -> Vec<String> {
91    value.map(split_patterns_csv).unwrap_or_default()
92}
93
94fn category_filters(value: Option<&str>) -> Vec<String> {
95    split_patterns(value)
96        .into_iter()
97        .map(|category| format!("cat:{}", category.to_ascii_lowercase()))
98        .collect()
99}
100
101fn name_filters(value: Option<&str>) -> Vec<String> {
102    split_patterns(value)
103        .into_iter()
104        .map(|name| format!("name:{name}"))
105        .collect()
106}
107
108#[cfg(test)]
109mod tests {
110    use super::*;
111
112    #[test]
113    fn test_action_filter_defaults() {
114        let args = DebugCliArgs::default();
115        let filter = args.action_filter();
116        assert!(!filter.should_log("Tick"));
117        assert!(filter.should_log("Connect"));
118    }
119
120    #[test]
121    fn test_action_filter_include_disables_default_excludes() {
122        let args = DebugCliArgs {
123            actions_include: Some("Tick".to_string()),
124            ..DebugCliArgs::default()
125        };
126        let filter = args.action_filter();
127        assert!(filter.should_log("Tick"));
128    }
129
130    #[test]
131    fn test_action_filter_category_and_name_flags() {
132        let args = DebugCliArgs {
133            actions_include_categories: Some("search".to_string()),
134            actions_exclude_names: Some("SearchSubmit".to_string()),
135            ..DebugCliArgs::default()
136        };
137        let filter = args.action_filter();
138        assert!(filter.should_log("SearchStart"));
139        assert!(!filter.should_log("SearchSubmit"));
140        assert!(!filter.should_log("Connect"));
141    }
142}