1use clap::{Parser, Subcommand};
2use std::path::PathBuf;
3
4#[derive(Parser, Debug)]
5#[command(
6 name = "uncomment",
7 version,
8 about = "Remove comments from code files using tree-sitter parsing",
9 long_about = "A fast, accurate CLI tool that removes comments from source code files using tree-sitter AST parsing. Automatically preserves important comments like linting directives, documentation, and metadata."
10)]
11pub struct Cli {
12 #[command(subcommand)]
13 pub command: Option<Commands>,
14
15 #[command(flatten)]
16 pub args: ProcessArgs,
17}
18
19#[derive(Subcommand, Debug)]
20pub enum Commands {
21 #[command(about = "Create a template configuration file")]
23 Init {
24 #[arg(short, long, default_value = ".uncommentrc.toml")]
26 output: PathBuf,
27
28 #[arg(short, long)]
30 force: bool,
31
32 #[arg(
34 long,
35 help = "Generate comprehensive config with all supported languages"
36 )]
37 comprehensive: bool,
38
39 #[arg(short, long, help = "Interactive mode to select languages and options")]
41 interactive: bool,
42 },
43}
44
45#[derive(Parser, Debug)]
46pub struct ProcessArgs {
47 #[arg(help = "Files, directories, or glob patterns to process")]
49 pub paths: Vec<String>,
50
51 #[arg(short = 'r', long, help = "Remove TODO comments (normally preserved)")]
53 pub remove_todo: bool,
54
55 #[arg(short = 'f', long, help = "Remove FIXME comments (normally preserved)")]
57 pub remove_fixme: bool,
58
59 #[arg(
61 short = 'd',
62 long,
63 help = "Remove documentation comments and docstrings"
64 )]
65 pub remove_doc: bool,
66
67 #[arg(
69 short = 'i',
70 long = "ignore",
71 help = "Additional patterns to preserve (can be used multiple times)"
72 )]
73 pub ignore_patterns: Vec<String>,
74
75 #[arg(
77 long = "no-default-ignores",
78 help = "Disable built-in preservation patterns (ESLint, Clippy, etc.)"
79 )]
80 pub no_default_ignores: bool,
81
82 #[arg(short = 'n', long, help = "Show changes without modifying files")]
84 pub dry_run: bool,
85
86 #[arg(short = 'v', long, help = "Show detailed processing information")]
88 pub verbose: bool,
89
90 #[arg(long = "no-gitignore", help = "Process files ignored by .gitignore")]
92 pub no_gitignore: bool,
93
94 #[arg(
96 long = "traverse-git-repos",
97 help = "Traverse into other git repositories (useful for monorepos)"
98 )]
99 pub traverse_git_repos: bool,
100
101 #[arg(
103 short = 'j',
104 long = "threads",
105 help = "Number of parallel threads (0 = auto-detect)",
106 default_value = "1"
107 )]
108 pub threads: usize,
109
110 #[arg(
112 short = 'c',
113 long = "config",
114 help = "Path to configuration file (overrides automatic discovery)"
115 )]
116 pub config: Option<PathBuf>,
117}
118
119impl ProcessArgs {
120 pub fn processing_options(&self) -> crate::processor::ProcessingOptions {
121 crate::processor::ProcessingOptions {
122 remove_todo: self.remove_todo,
123 remove_fixme: self.remove_fixme,
124 remove_doc: self.remove_doc,
125 custom_preserve_patterns: self.ignore_patterns.clone(),
126 use_default_ignores: !self.no_default_ignores,
127 dry_run: self.dry_run,
128 respect_gitignore: !self.no_gitignore,
129 traverse_git_repos: self.traverse_git_repos,
130 }
131 }
132}
133
134impl Cli {
135 pub fn handle_init_command(
137 output: &PathBuf,
138 force: bool,
139 comprehensive: bool,
140 interactive: bool,
141 ) -> anyhow::Result<()> {
142 if output.exists() && !force {
143 return Err(anyhow::anyhow!(
144 "Configuration file already exists: {}. Use --force to overwrite.",
145 output.display()
146 ));
147 }
148
149 let (template, detected_info) = if comprehensive {
150 (crate::config::Config::comprehensive_template_clean(), None)
151 } else if interactive {
152 (crate::config::Config::interactive_template_clean()?, None)
153 } else {
154 let current_dir =
156 std::env::current_dir().unwrap_or_else(|_| std::path::PathBuf::from("."));
157 let (template, info) = crate::config::Config::smart_template_with_info(¤t_dir)?;
158 (template, Some(info))
159 };
160
161 std::fs::write(output, template)?;
162
163 println!("✓ Created configuration file: {}", output.display());
164
165 if comprehensive {
166 println!("📦 Generated comprehensive config with 15+ language configurations");
167 } else if interactive {
168 println!("🎯 Generated customized config based on your selections");
169 } else if let Some(info) = detected_info {
170 if !info.detected_languages.is_empty() {
171 println!(
172 "🔍 Detected {} file types in your project:",
173 info.detected_languages.len()
174 );
175 for (lang, count) in &info.detected_languages {
176 println!(" {count} ({lang} files)");
177 }
178 println!(
179 "📝 Configured {} languages with appropriate settings",
180 info.configured_languages
181 );
182 } else {
183 println!("📝 No supported files detected, generated basic template");
184 }
185 if info.total_files > 0 {
186 println!("📊 Scanned {} files total", info.total_files);
187 }
188 } else {
189 println!("📝 Generated smart config based on detected files in your project");
190 }
191
192 Ok(())
193 }
194}