ferric_ai/
handler.rs

1// Extensible command handler system for ferric CLI
2//
3// This module provides a clean, extensible architecture for handling CLI commands.
4// The handler pattern allows easy addition of new commands without modifying
5// existing code, following the Open-Closed Principle.
6//
7// To add a new command:
8// 1. Add a new variant to the Commands enum in cli/mod.rs
9// 2. Create a new handler struct implementing CommandHandler in commands/
10// 3. Add a new match arm in CommandRouter::route()
11//
12// Example for adding a new "analyze" command:
13// ```
14// // In cli/mod.rs - add to Commands enum:
15// Analyze { threshold: Option<f64> },
16//
17// // In commands/analyze.rs - create handler:
18// pub struct AnalyzeHandler { threshold: f64 }
19// impl CommandHandler for AnalyzeHandler { ... }
20//
21// // In handler.rs - add to route():
22// Commands::Analyze { threshold } => {
23//     let handler = AnalyzeHandler::new(threshold.unwrap_or(5.0));
24//     handler.execute(cli)
25// }
26// ```
27
28use crate::cli::{Cli, Commands};
29use crate::commands::{PrettyPrintHandler, LlmGuideHandler};
30use crate::commands::stats::StatsHandler;
31use crate::error::Result;
32use crate::parser::Tree;
33use crate::helpers::{resolve_path_to_files, parse_single_svg};
34
35/// Trait for command handlers - this makes the system extensible
36///
37/// Each command should implement this trait to provide its functionality.
38/// The trait receives the full CLI structure, allowing access to global options
39/// like the flamegraph file path and any command-specific parameters.
40pub trait CommandHandler {
41    /// Execute the command with the given CLI arguments
42    ///
43    /// # Arguments
44    /// * `cli` - The parsed CLI arguments containing the flamegraph path and command details
45    ///
46    /// # Returns
47    /// * `Result<()>` - Success or a FerricError describing what went wrong
48    fn execute(&self, cli: &Cli) -> Result<()>;
49
50    /// Parse flamegraph inputs into Tree structures for comparison
51    ///
52    /// # Arguments
53    /// * `cli` - The parsed CLI arguments containing flamegraph paths
54    ///
55    /// # Returns
56    /// * `Result<(Vec<Tree>, Vec<Tree>)>` - (primary_trees, comparison_trees)
57    ///   - primary_trees: Trees from -f input (1 file = 1 tree, directory = multiple trees)
58    ///   - comparison_trees: Trees from -c input (empty if no comparison)
59    #[allow(unused)]
60    fn parse_trees(&self, cli: &Cli) -> Result<(Vec<Tree>, Vec<Tree>)> {
61        let mut primary_trees = Vec::new();
62        let mut comparison_trees = Vec::new();
63
64        // Parse primary input (-f) using glob-aware resolution
65        let primary_files = resolve_path_to_files(&cli.flamegraph)?;
66        for file in primary_files {
67            let tree = parse_single_svg(&file)?;
68            primary_trees.push(tree);
69        }
70
71        // Parse comparison input (-c) if provided using glob-aware resolution
72        if let Some(compare_path) = &cli.compare {
73            let comparison_files = resolve_path_to_files(compare_path)?;
74            for file in comparison_files {
75                let tree = parse_single_svg(&file)?;
76                comparison_trees.push(tree);
77            }
78        }
79
80        Ok((primary_trees, comparison_trees))
81    }
82}
83
84
85/// Main router that routes commands to their handlers
86pub struct CommandRouter;
87
88impl CommandRouter {
89    /// Create a new router
90    pub fn new() -> Self {
91        Self
92    }
93
94    /// Route a command to the appropriate handler
95    /// This method is extensible - new commands can be added by adding new variants
96    /// to the Commands enum and adding corresponding match arms here
97    pub fn route(&self, cli: &Cli) -> Result<()> {
98        match &cli.command {
99            Commands::PrettyPrint => {
100                let handler = PrettyPrintHandler;
101                handler.execute(cli)
102            }
103            Commands::LlmGuide => {
104                let handler = LlmGuideHandler;
105                handler.execute(cli)
106            }
107            Commands::Stats(_) => {
108                let handler = StatsHandler;
109                handler.execute(cli)
110            }
111            // Future commands can be added here:
112            // Commands::Analyze { .. } => {
113            //     let handler = AnalyzeHandler::new(params);
114            //     handler.execute(cli)
115            // }
116            // Commands::Find { .. } => {
117            //     let handler = FindHandler::new(params);
118            //     handler.execute(cli)
119            // }
120        }
121    }
122}
123
124/// Default implementation for the router
125impl Default for CommandRouter {
126    fn default() -> Self {
127        Self::new()
128    }
129}
130
131#[cfg(test)]
132mod tests {
133    use super::*;
134
135    #[test]
136    fn test_router_creation() {
137        let _router = CommandRouter::new();
138        // Just verify it can be created
139    }
140
141    #[test]
142    fn test_default_router() {
143        let _router = CommandRouter;
144        // Just verify it can be created with default
145    }
146}