1use crate::{Generator, Parser, Result};
7use clap::{Arg, ArgMatches, Command};
8use colored::*;
9use std::collections::HashMap;
10
11pub struct Cli;
12
13impl Cli {
14 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 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!(), }
79 }
80
81 fn handle_forge(matches: &ArgMatches) -> Result<()> {
84 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 if verbose {
93 println!("{} Parsing structure from: {}", "📖".cyan(), input_file);
94 }
95
96 let parser = Parser::new();
98 let mut structure = parser.parse_file(input_file)?;
99
100 structure.variables.extend(variables);
102
103 if verbose {
105 println!("{} Root: {}", "🌳".green(), structure.root);
106 println!("{} Items: {}", "📁".blue(), Self::count_total_items(&structure.items));
107 }
108
109 let generator = Generator::new()
111 .with_verbose(verbose)
112 .with_force_override(force); generator.generate(&structure, output_dir)?;
116
117 Ok(())
118 }
119
120 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 let parser = Parser::new();
129 let structure = parser.parse_file(input_file)?;
130
131 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 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 eprintln!("{} Invalid variable format: {} (expected key=value)",
151 "⚠️".yellow(), var);
152 }
153 }
154 }
155
156 variables
157 }
158
159 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}