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