Initial upload: gitignore-gen Rust CLI tool with 100+ templates
This commit is contained in:
286
app/gitignore-gen/src/cli/mod.rs
Normal file
286
app/gitignore-gen/src/cli/mod.rs
Normal 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(())
|
||||
}
|
||||
Reference in New Issue
Block a user