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