Initial upload: gitignore-gen Rust CLI tool with 100+ templates
Some checks failed
CI / test (push) Has been cancelled
CI / build (push) Has been cancelled
CI / lint (push) Has been cancelled

This commit is contained in:
2026-02-04 04:43:13 +00:00
parent 486407e28d
commit d919cadf3d

View File

@@ -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<PathBuf>,
#[arg(short, long, help = "Template to use")]
template: Option<String>,
#[arg(short, long, help = "Combine multiple templates (comma-separated)")]
combine: Option<String>,
#[arg(short, long, help = "Add custom patterns (comma-separated)")]
custom: Option<String>,
#[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<CategoryArg>,
#[arg(short, long, help = "Search pattern")]
search: Option<String>,
},
#[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<CategoryArg>,
},
#[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<CategoryArg> 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<PathBuf>,
template: Option<String>,
combine: Option<String>,
custom: Option<String>,
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<String> = 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<CategoryArg>, search: Option<String>) -> 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<CategoryArg>) -> 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(())
}