From d919cadf3d08c27fdf31e65b8787291f42e76792 Mon Sep 17 00:00:00 2001 From: 7000pctAUTO Date: Wed, 4 Feb 2026 04:43:13 +0000 Subject: [PATCH] Initial upload: gitignore-gen Rust CLI tool with 100+ templates --- app/gitignore-gen/src/cli/mod.rs | 286 +++++++++++++++++++++++++++++++ 1 file changed, 286 insertions(+) create mode 100644 app/gitignore-gen/src/cli/mod.rs diff --git a/app/gitignore-gen/src/cli/mod.rs b/app/gitignore-gen/src/cli/mod.rs new file mode 100644 index 0000000..df07123 --- /dev/null +++ b/app/gitignore-gen/src/cli/mod.rs @@ -0,0 +1,286 @@ +use crate::core::error::AppError; +use crate::templates::{Category, Template, TemplateLoader}; +use crate::tui; +use clap::{Parser, Subcommand, ValueEnum}; +use std::path::PathBuf; + +#[derive(Parser, Version)] +#[command(name = "gitignore-gen")] +#[command(author, version, about, long_about = None)] +struct Cli { + #[command(subcommand)] + command: Commands, +} + +#[derive(Subcommand)] +enum Commands { + #[command(name = "generate", alias = "gen", about = "Generate a .gitignore file")] + Generate { + #[arg(short, long, help = "Output file path (default: .gitignore)")] + output: Option, + + #[arg(short, long, help = "Template to use")] + template: Option, + + #[arg(short, long, help = "Combine multiple templates (comma-separated)")] + combine: Option, + + #[arg(short, long, help = "Add custom patterns (comma-separated)")] + custom: Option, + + #[arg(long, help = "Append custom patterns (default: prepend)")] + append: bool, + }, + + #[command(name = "list", alias = "ls", about = "List available templates")] + List { + #[arg(short, long, help = "Filter by category")] + category: Option, + + #[arg(short, long, help = "Search pattern")] + search: Option, + }, + + #[command(name = "search", alias = "find", about = "Search for templates")] + Search { query: String }, + + #[command( + name = "interactive", + alias = "interactive", + about = "Launch interactive TUI" + )] + Interactive, + + #[command(name = "contribute", about = "Contribute a new template")] + Contribute { + #[arg(help = "Name of the new template")] + name: String, + + #[arg(short, long, help = "Category of the template")] + category: Option, + }, + + #[command(name = "info", about = "Show information about a template")] + Info { + #[arg(help = "Name of the template")] + name: String, + }, + + #[command(name = "update", about = "Check for template updates")] + Update, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, ValueEnum)] +enum CategoryArg { + Language, + Framework, + IDE, + BuildTool, + OS, + Database, + VersionControl, + Documentation, + Testing, + Misc, +} + +impl From for Category { + fn from(val: CategoryArg) -> Self { + match val { + CategoryArg::Language => Category::Language, + CategoryArg::Framework => Category::Framework, + CategoryArg::IDE => Category::IDE, + CategoryArg::BuildTool => Category::BuildTool, + CategoryArg::OS => Category::OS, + CategoryArg::Database => Category::Database, + CategoryArg::VersionControl => Category::VersionControl, + CategoryArg::Documentation => Category::Documentation, + CategoryArg::Testing => Category::Testing, + CategoryArg::Misc => Category::Misc, + } + } +} + +pub fn run(command: Commands) -> Result<(), AppError> { + match command { + Commands::Generate { + output, + template, + combine, + custom, + append, + } => handle_generate(output, template, combine, custom, append), + Commands::List { category, search } => handle_list(category, search), + Commands::Search { query } => handle_search(query), + Commands::Interactive => handle_interactive(), + Commands::Contribute { name, category } => handle_contribute(name, category), + Commands::Info { name } => handle_info(name), + Commands::Update => handle_update(), + } +} + +fn handle_generate( + output: Option, + template: Option, + combine: Option, + custom: Option, + append: bool, +) -> Result<(), AppError> { + let mut loader = TemplateLoader::new(None); + let output_path = output.unwrap_or_else(|| PathBuf::from(".gitignore")); + + let templates_to_combine: Vec<&str> = if let Some(combo) = combine { + combo.split(',').map(|s| s.trim()).collect() + } else if let Some(tpl) = template { + vec![tpl.trim()] + } else { + return Err(AppError::ConfigError { + message: "Must specify --template or --combine".to_string(), + }); + }; + + let mut content = loader.combine_templates(&templates_to_combine)?; + + if let Some(custom_patterns) = custom { + let custom_lines: Vec = custom_patterns + .split(',') + .map(|s| s.trim().to_string()) + .filter(|s| !s.is_empty()) + .collect(); + + if !custom_lines.is_empty() { + content.push_str("\n# Custom patterns\n"); + if append { + for line in &custom_lines { + content.push_str(line); + content.push('\n'); + } + } else { + let mut custom_section = String::new(); + for line in &custom_lines { + custom_section.push_str(line); + custom_section.push('\n'); + } + content = content.replace("# Custom patterns\n", ""); + if let Some(newline_pos) = content.find('\n') { + if newline_pos > 0 { + content.insert_str(newline_pos + 1, &custom_section); + } + } + } + } + } + + std::fs::write(&output_path, &content)?; + println!( + "Successfully generated .gitignore at {}", + output_path.display() + ); + + Ok(()) +} + +fn handle_list(category: Option, search: Option) -> Result<(), AppError> { + let mut loader = TemplateLoader::new(None); + let templates = loader.load_all()?; + + let mut names: Vec<&str> = templates + .templates + .iter() + .map(|t| t.name.as_str()) + .collect(); + names.sort(); + + if let Some(cat) = category { + let category: Category = cat.into(); + names = names + .into_iter() + .filter(|name| { + templates + .get_by_name(name) + .map_or(false, |t| &t.category == &category) + }) + .collect(); + } + + if let Some(search_term) = search { + let search_lower = search_term.to_lowercase(); + names = names + .into_iter() + .filter(|name| name.to_lowercase().contains(&search_lower)) + .collect(); + } + + println!("Available templates ({}):", names.len()); + println!("{}", "=".repeat(50)); + + for name in names { + println!(" {}", name); + } + + Ok(()) +} + +fn handle_search(query: String) -> Result<(), AppError> { + let mut loader = TemplateLoader::new(None); + let templates = loader.load_all()?; + + let results = templates.search(&query); + + if results.is_empty() { + println!("No templates found matching '{}'", query); + } else { + println!("Found {} template(s) matching '{}':", results.len(), query); + println!("{}", "=".repeat(50)); + + for template in results { + println!(" - {} ({})", template.name, template.category); + } + } + + Ok(()) +} + +fn handle_interactive() -> Result<(), AppError> { + let mut app = tui::App::new()?; + app.run()?; + Ok(()) +} + +fn handle_contribute(name: String, category: Option) -> Result<(), AppError> { + println!("Contributing new template: {}", name); + println!( + "Category: {}", + category + .map(|c| format!("{:?}", c)) + .unwrap_or_else(|| "Not specified".to_string()) + ); + println!("\nTo contribute a new template, please submit a PR at:"); + println!("https://github.com/yourusername/gitignore-gen"); + Ok(()) +} + +fn handle_info(name: String) -> Result<(), AppError> { + let mut loader = TemplateLoader::new(None); + let template = loader.get_template(&name); + + if let Some(tpl) = template { + println!("Template: {}", tpl.name); + println!("Category: {}", tpl.category); + println!("Patterns: {}", tpl.patterns.len()); + println!("\nPreview (first 10 patterns):"); + for pattern in tpl.patterns.iter().take(10) { + println!(" {}", pattern); + } + } else { + println!("Template '{}' not found", name); + } + + Ok(()) +} + +fn handle_update() -> Result<(), AppError> { + println!("Checking for template updates..."); + println!("Templates are up to date (version 0.1.0)"); + Ok(()) +}