This commit is contained in:
204
gitignore_generator/cli/interactive.py
Normal file
204
gitignore_generator/cli/interactive.py
Normal file
@@ -0,0 +1,204 @@
|
||||
"""Interactive mode for gitignore-generator."""
|
||||
|
||||
import sys
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
import click
|
||||
|
||||
from gitignore_generator.template_loader import template_loader
|
||||
from gitignore_generator.validator import validator
|
||||
|
||||
|
||||
def show_preview(content: str) -> None:
|
||||
"""Show preview of gitignore content."""
|
||||
click.echo("\n--- Preview ---")
|
||||
lines = content.splitlines()
|
||||
for i, line in enumerate(lines[:30], 1):
|
||||
click.echo(f"{i:3} | {line}")
|
||||
if len(lines) > 30:
|
||||
click.echo(f"... and {len(lines) - 30} more lines")
|
||||
click.echo("-------------\n")
|
||||
|
||||
|
||||
def prompt_category() -> str:
|
||||
"""Prompt user to select template category."""
|
||||
categories = template_loader.get_templates_by_category()
|
||||
category_options = []
|
||||
|
||||
for cat in ["languages", "ides"]:
|
||||
if categories.get(cat):
|
||||
category_options.append(cat.capitalize())
|
||||
|
||||
category_options.append("Custom")
|
||||
|
||||
click.echo("\nSelect category:")
|
||||
for i, cat in enumerate(category_options, 1):
|
||||
click.echo(f" {i}. {cat}")
|
||||
|
||||
choice = click.prompt("Enter choice", type=int, default=1)
|
||||
|
||||
if 1 <= choice <= len(category_options):
|
||||
selected = category_options[choice - 1]
|
||||
if selected == "Custom":
|
||||
return "custom"
|
||||
return selected.lower()
|
||||
return "languages"
|
||||
|
||||
|
||||
def prompt_templates(category: str) -> List[str]:
|
||||
"""Prompt user to select templates."""
|
||||
templates = template_loader.get_available_templates(category)
|
||||
if not templates:
|
||||
return []
|
||||
|
||||
click.echo(f"\n{category.capitalize()} templates (select multiple, comma-separated):")
|
||||
for i, template in enumerate(templates[:20], 1):
|
||||
click.echo(f" {i}. {template}")
|
||||
if len(templates) > 20:
|
||||
click.echo(f" ... and {len(templates) - 20} more")
|
||||
|
||||
click.echo(" 0. None/Skip")
|
||||
|
||||
choices = click.prompt("Enter template numbers", type=str, default="")
|
||||
|
||||
selected = []
|
||||
if choices.strip():
|
||||
try:
|
||||
nums = [int(x.strip()) for x in choices.split(",")]
|
||||
for num in nums:
|
||||
if 1 <= num <= len(templates):
|
||||
selected.append(templates[num - 1])
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
return selected
|
||||
|
||||
|
||||
def prompt_custom_patterns() -> List[str]:
|
||||
"""Prompt user for custom patterns."""
|
||||
patterns = []
|
||||
click.echo("\nAdd custom patterns (one per line, empty to finish):")
|
||||
|
||||
while True:
|
||||
pattern = click.prompt("Pattern", default="", show_default=False).strip()
|
||||
if not pattern:
|
||||
break
|
||||
if pattern.startswith("#"):
|
||||
click.echo(" Skipping comment line")
|
||||
continue
|
||||
|
||||
issue = validator.validate_pattern(pattern)
|
||||
if issue:
|
||||
click.echo(f" Warning: {issue.message}")
|
||||
if not click.confirm("Add anyway?"):
|
||||
continue
|
||||
|
||||
patterns.append(pattern)
|
||||
|
||||
return patterns
|
||||
|
||||
|
||||
def prompt_output_file() -> str:
|
||||
"""Prompt user for output file name."""
|
||||
return click.prompt("Output file", default=".gitignore", show_default=True)
|
||||
|
||||
|
||||
def run_interactive_wizard() -> Optional[Tuple[str, str]]:
|
||||
"""Run the interactive wizard."""
|
||||
try:
|
||||
click.echo("=== Gitignore Generator - Interactive Mode ===")
|
||||
|
||||
category = prompt_category()
|
||||
selected_templates = []
|
||||
|
||||
if category != "custom":
|
||||
selected_templates = prompt_templates(category)
|
||||
|
||||
custom_patterns = prompt_custom_patterns()
|
||||
|
||||
if not selected_templates and not custom_patterns:
|
||||
click.echo("\nNo templates or patterns selected. Aborting.")
|
||||
return None
|
||||
|
||||
content_parts = []
|
||||
|
||||
for template_name in selected_templates:
|
||||
template_content = template_loader.get_template_content(template_name)
|
||||
if template_content:
|
||||
content_parts.append(f"# --- {template_name} ---\n")
|
||||
content_parts.append(template_content)
|
||||
content_parts.append("\n")
|
||||
|
||||
if custom_patterns:
|
||||
content_parts.append(f"# --- Custom Patterns ---\n")
|
||||
content_parts.extend(custom_patterns)
|
||||
content_parts.append("\n")
|
||||
|
||||
final_content = "".join(content_parts).rstrip() + "\n"
|
||||
|
||||
show_preview(final_content)
|
||||
|
||||
if click.confirm("Do you want to edit the patterns?"):
|
||||
final_content = edit_content_interactive(final_content)
|
||||
|
||||
output_file = prompt_output_file()
|
||||
|
||||
return final_content, output_file
|
||||
|
||||
except KeyboardInterrupt:
|
||||
click.echo("\n\nAborted by user.")
|
||||
return None
|
||||
except EOFError:
|
||||
click.echo("\n\nInput stream closed. Exiting.")
|
||||
return None
|
||||
|
||||
|
||||
def edit_content_interactive(content: str) -> str:
|
||||
"""Allow interactive editing of content."""
|
||||
lines = content.splitlines()
|
||||
click.echo("\nEdit mode (empty line number to finish editing):")
|
||||
click.echo(" Commands: 'add <pattern>', 'delete <line_num>', 'show', 'save'")
|
||||
|
||||
while True:
|
||||
cmd = click.prompt("Edit command", default="save", show_default=False).strip().lower()
|
||||
|
||||
if cmd == "save" or not cmd:
|
||||
break
|
||||
|
||||
if cmd.startswith("add "):
|
||||
pattern = cmd[4:].strip()
|
||||
if pattern:
|
||||
lines.append(pattern)
|
||||
click.echo(f"Added: {pattern}")
|
||||
|
||||
elif cmd.startswith("delete "):
|
||||
try:
|
||||
line_num = int(cmd[7:].strip())
|
||||
if 1 <= line_num <= len(lines):
|
||||
deleted = lines.pop(line_num - 1)
|
||||
click.echo(f"Deleted line {line_num}: {deleted}")
|
||||
else:
|
||||
click.echo("Invalid line number")
|
||||
except ValueError:
|
||||
click.echo("Invalid command")
|
||||
|
||||
elif cmd == "show":
|
||||
show_preview("\n".join(lines))
|
||||
|
||||
else:
|
||||
click.echo("Unknown command. Use: add <pattern>, delete <num>, show, save")
|
||||
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
|
||||
def run_simple_interactive() -> None:
|
||||
"""Run a simple interactive session."""
|
||||
result = run_interactive_wizard()
|
||||
if result:
|
||||
content, output_file = result
|
||||
try:
|
||||
with open(output_file, "w") as f:
|
||||
f.write(content)
|
||||
click.echo(f"\nSuccessfully wrote .gitignore to '{output_file}'")
|
||||
except IOError as e:
|
||||
click.echo(f"\nError writing file: {e}")
|
||||
Reference in New Issue
Block a user