From 10250b98fbc9c358bdbfb007901fdea832ab618a Mon Sep 17 00:00:00 2001 From: 7000pctAUTO Date: Mon, 2 Feb 2026 15:54:21 +0000 Subject: [PATCH] Add CLI commands and interactive mode --- gitignore_generator/cli/interactive.py | 204 +++++++++++++++++++++++++ 1 file changed, 204 insertions(+) create mode 100644 gitignore_generator/cli/interactive.py diff --git a/gitignore_generator/cli/interactive.py b/gitignore_generator/cli/interactive.py new file mode 100644 index 0000000..db7f708 --- /dev/null +++ b/gitignore_generator/cli/interactive.py @@ -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 ', 'delete ', '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 , delete , 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}")