forge_tree/
cli.rs

1//! Command-line interface implementation for forge-tree
2//! 
3//! This module handles all CLI interactions including parsing arguments,
4//! subcommands, and orchestrating the forge and validate operations.
5
6use crate::{Generator, Parser, Result};
7use clap::{Arg, ArgMatches, Command};
8use colored::*;
9use std::collections::HashMap;
10
11pub struct Cli;
12
13impl Cli {
14    /// Creates the CLI command structure with all subcommands and arguments
15    pub fn new() -> Command {
16        Command::new("forge-tree")
17            .version(env!("CARGO_PKG_VERSION"))
18            .about("🏗️🌳 Forge project structures from text representations")
19            .author("Your Name <your.email@example.com>")
20            .subcommand_required(true)
21            .arg_required_else_help(true)
22            .subcommand(
23                Command::new("forge")
24                    .about("Forge a project structure from a text file")
25                    .arg(
26                        Arg::new("input")
27                            .help("Input file containing the project structure")
28                            .required(true)
29                            .index(1)
30                    )
31                    .arg(
32                        Arg::new("output")
33                            .short('o')
34                            .long("output")
35                            .help("Output directory (default: current directory)")
36                            .default_value(".")
37                    )
38                    .arg(
39                        Arg::new("force")
40                            .short('f')
41                            .long("force")
42                            .help("Force overwrite existing files")
43                            .action(clap::ArgAction::SetTrue)
44                    )
45                    .arg(
46                        Arg::new("verbose")
47                            .short('v')
48                            .long("verbose")
49                            .help("Verbose output showing each file/directory creation")
50                            .action(clap::ArgAction::SetTrue)
51                    )
52                    .arg(
53                        Arg::new("variable")
54                            .long("var")
55                            .help("Set template variables (format: key=value)")
56                            .action(clap::ArgAction::Append)
57                    )
58            )
59            .subcommand(
60                Command::new("validate")
61                    .about("Validate a structure file without forging it")
62                    .arg(
63                        Arg::new("input")
64                            .help("Input file to validate")
65                            .required(true)
66                            .index(1)
67                    )
68            )
69    }
70
71    /// Main entry point for CLI execution
72    /// Routes to appropriate subcommand handlers based on parsed arguments
73    pub fn run(matches: ArgMatches) -> Result<()> {
74        match matches.subcommand() {
75            Some(("forge", sub_matches)) => Self::handle_forge(sub_matches),
76            Some(("validate", sub_matches)) => Self::handle_validate(sub_matches),
77            _ => unreachable!(), // clap ensures this won't happen due to subcommand_required(true)
78        }
79    }
80
81    /// Handler for the `forge` subcommand
82    /// Parses the structure file and generates the project directory
83    fn handle_forge(matches: &ArgMatches) -> Result<()> {
84        // Extract command line arguments
85        let input_file = matches.get_one::<String>("input").unwrap();
86        let output_dir = matches.get_one::<String>("output").unwrap();
87        let force = matches.get_flag("force");
88        let verbose = matches.get_flag("verbose");
89        let variables = Self::parse_variables(matches);
90
91        // Show parsing progress if verbose mode is enabled
92        if verbose {
93            println!("{} Parsing structure from: {}", "📖".cyan(), input_file);
94        }
95
96        // Parse the structure file
97        let parser = Parser::new();
98        let mut structure = parser.parse_file(input_file)?;
99
100        // Merge user-provided template variables with parsed structure
101        structure.variables.extend(variables);
102
103        // Display structure summary in verbose mode
104        if verbose {
105            println!("{} Root: {}", "🌳".green(), structure.root);
106            println!("{} Items: {}", "📁".blue(), Self::count_total_items(&structure.items));
107        }
108
109        // Create and configure the generator with CLI flags
110        let generator = Generator::new()
111            .with_verbose(verbose)
112            .with_force_override(force); // Pass the --force flag to enable overwriting
113
114        // Execute the project generation
115        generator.generate(&structure, output_dir)?;
116
117        Ok(())
118    }
119
120    /// Handler for the `validate` subcommand
121    /// Checks structure file syntax without creating any files
122    fn handle_validate(matches: &ArgMatches) -> Result<()> {
123        let input_file = matches.get_one::<String>("input").unwrap();
124
125        println!("{} Validating: {}", "🔍".cyan(), input_file);
126
127        // Parse the structure file (validation happens during parsing)
128        let parser = Parser::new();
129        let structure = parser.parse_file(input_file)?;
130
131        // Show validation results
132        println!("{} Structure is valid!", "✅".green());
133        println!("  {} Root: {}", "🌳".green(), structure.root);
134        println!("  {} Total items: {}", "📊".blue(), Self::count_total_items(&structure.items));
135
136        Ok(())
137    }
138
139    /// Parse --var key=value pairs from command line into a HashMap
140    /// Handles multiple --var flags and provides error messages for invalid formats
141    fn parse_variables(matches: &ArgMatches) -> HashMap<String, String> {
142        let mut variables = HashMap::new();
143
144        if let Some(vars) = matches.get_many::<String>("variable") {
145            for var in vars {
146                if let Some((key, value)) = var.split_once('=') {
147                    variables.insert(key.to_string(), value.to_string());
148                } else {
149                    // Show warning for malformed variable assignments
150                    eprintln!("{} Invalid variable format: {} (expected key=value)", 
151                             "⚠️".yellow(), var);
152                }
153            }
154        }
155
156        variables
157    }
158
159    /// Recursively count total number of items in the structure tree
160    /// Used for progress bar initialization and statistics display
161    fn count_total_items(items: &[crate::parser::StructureItem]) -> usize {
162        let mut count = items.len();
163        for item in items {
164            count += Self::count_total_items(&item.children);
165        }
166        count
167    }
168}