Initial commit: gitignore-generator v0.1.0
Some checks failed
CI / test (push) Has been cancelled

This commit is contained in:
2026-02-01 03:31:32 +00:00
parent bc6bf76f1b
commit c45e93fbb4

401
src/cli.py Normal file
View File

@@ -0,0 +1,401 @@
"""CLI interface for gitignore-generator."""
import os
import sys
from pathlib import Path
from typing import List, Optional
import click
from . import __version__
from .config import Config
from .detector import ProjectDetector
from .generator import GitignoreGenerator
@click.group()
@click.version_option(version=__version__, prog_name="gitignore-generator")
@click.option(
"--config",
"-c",
type=click.Path(exists=True, dir_okay=False, readable=True),
help="Path to configuration file",
)
@click.pass_context
def main(ctx: click.Context, config: Optional[str]) -> None:
"""A CLI tool that generates .gitignore files for any project type."""
ctx.ensure_object(dict)
config_obj = None
if config:
config_obj = Config.load(config)
ctx.obj["config"] = config_obj
@main.command("generate")
@click.argument(
"types",
nargs=-1,
type=click.Choice(
[
"python",
"javascript",
"typescript",
"java",
"go",
"rust",
"dotnet",
"php",
"ruby",
"django",
"flask",
"react",
"vue",
"angular",
"rails",
"laravel",
"spring",
"vscode",
"jetbrains",
"visualstudiocode",
"linux",
"macos",
"windows",
"docker",
"gradle",
"maven",
"jupyter",
"terraform",
]
),
)
@click.option(
"--output",
"-o",
type=click.Path(dir_okay=False, writable=True),
default=".gitignore",
help="Output file path (default: .gitignore)",
)
@click.option(
"--append/--overwrite",
default=True,
help="Append to existing file or overwrite (default: append)",
)
@click.option(
"--language",
"-l",
multiple=True,
type=click.Choice(
[
"python",
"javascript",
"typescript",
"java",
"go",
"rust",
"dotnet",
"php",
"ruby",
]
),
help="Programming language(s) to include",
)
@click.option(
"--framework",
"-f",
multiple=True,
type=click.Choice(
[
"django",
"flask",
"react",
"vue",
"angular",
"rails",
"laravel",
"spring",
]
),
help="Framework(s) to include",
)
@click.option(
"--ide",
"-i",
multiple=True,
type=click.Choice(["vscode", "jetbrains", "visualstudiocode"]),
help="IDE(s) to include",
)
@click.option(
"--os",
multiple=True,
type=click.Choice(["linux", "macos", "windows"]),
help="Operating system(s) to include",
)
@click.option(
"--tools",
"-t",
multiple=True,
type=click.Choice(
["docker", "gradle", "maven", "jupyter", "terraform"]
),
help="Tool(s) to include",
)
@click.option(
"--stdout",
is_flag=True,
default=False,
help="Output to stdout instead of writing to file",
)
@click.pass_context
def generate(
ctx: click.Context,
types: tuple,
output: str,
append: bool,
language: tuple,
framework: tuple,
ide: tuple,
os_opts: tuple,
tools: tuple,
stdout: bool,
) -> None:
"""Generate a .gitignore file for the specified types."""
config = ctx.obj.get("config")
generator = GitignoreGenerator(config)
if types:
for t in types:
generator.add_template(t)
else:
selected_types = list(language) + list(framework) + list(ide)
selected_types.extend(list(os_opts))
selected_types.extend(list(tools))
if not selected_types:
detector = ProjectDetector(Path.cwd())
detected = detector.detect()
for t in detected:
generator.add_template(t)
if not detected:
click.echo(
"No project type detected. Please specify types manually.",
err=True,
)
click.echo(
"Use --language, --framework, --ide, --os, or --tools option.",
err=True,
)
sys.exit(1)
else:
for t in selected_types:
generator.add_template(t)
content = generator.generate()
if stdout:
click.echo(content)
else:
output_path = Path(output)
if output_path.exists() and append:
mode = "a"
else:
mode = "w"
try:
with open(output_path, mode) as f:
f.write(content)
click.echo(f"Successfully wrote to {output_path}")
except PermissionError:
click.echo(f"Permission denied: {output_path}", err=True)
sys.exit(1)
except OSError as e:
click.echo(f"Error writing to {output_path}: {e}", err=True)
sys.exit(1)
@main.command("detect")
@click.argument(
"path",
type=click.Path(exists=True, file_okay=False, readable=True),
default=".",
)
@click.pass_context
def detect(ctx: click.Context, path: str) -> None:
"""Detect project type(s) from directory structure."""
detector = ProjectDetector(Path(path))
detected = detector.detect()
if detected:
click.echo("Detected project types:")
for t in detected:
click.echo(f" - {t}")
else:
click.echo("No known project type detected.")
@main.command("list")
@click.option(
"--category",
"-c",
type=click.Choice(
["language", "framework", "ide", "os", "tools", "all"]
),
default="all",
help="Category to list (default: all)",
)
def list_templates(category: str) -> None:
"""List available template types."""
templates = {
"language": [
"python",
"javascript",
"typescript",
"java",
"go",
"rust",
"dotnet",
"php",
"ruby",
],
"framework": [
"django",
"flask",
"react",
"vue",
"angular",
"rails",
"laravel",
"spring",
],
"ide": ["vscode", "jetbrains", "visualstudiocode"],
"os": ["linux", "macos", "windows"],
"tools": ["docker", "gradle", "maven", "jupyter", "terraform"],
}
if category == "all":
for cat, items in templates.items():
click.echo(f"{cat.capitalize()}:")
for item in items:
click.echo(f" - {item}")
elif category in templates:
click.echo(f"{category.capitalize()}:")
for item in templates[category]:
click.echo(f" - {item}")
@main.command("wizard")
@click.pass_context
def wizard(ctx: click.Context) -> None:
"""Interactive wizard to generate .gitignore."""
click.echo("=== gitignore-generator Wizard ===")
click.echo("")
languages = [
"python",
"javascript",
"typescript",
"java",
"go",
"rust",
"dotnet",
"php",
"ruby",
]
frameworks = [
"django",
"flask",
"react",
"vue",
"angular",
"rails",
"laravel",
"spring",
]
ides = ["vscode", "jetbrains", "visualstudiocode"]
oss = ["linux", "macos", "windows"]
tools = ["docker", "gradle", "maven", "jupyter", "terraform"]
selected = []
click.echo("Select programming languages (space-separated, Enter to skip):")
for idx, lang in enumerate(languages, 1):
click.echo(f" {idx}. {lang}")
selected_langs = click.prompt(
"Languages", default="", type=str
).split()
for lang in selected_langs:
if lang in languages:
selected.append(lang)
click.echo("")
click.echo("Select frameworks (space-separated, Enter to skip):")
for idx, fw in enumerate(frameworks, 1):
click.echo(f" {idx}. {fw}")
selected_fws = click.prompt(
"Frameworks", default="", type=str
).split()
for fw in selected_fws:
if fw in frameworks:
selected.append(fw)
click.echo("")
click.echo("Select IDEs (space-separated, Enter to skip):")
for idx, ide in enumerate(ides, 1):
click.echo(f" {idx}. {ide}")
selected_ides = click.prompt("IDEs", default="", type=str).split()
for ide in selected_ides:
if ide in ides:
selected.append(ide)
click.echo("")
click.echo("Select operating systems (space-separated, Enter to skip):")
for idx, os_name in enumerate(oss, 1):
click.echo(f" {idx}. {os_name}")
selected_oss = click.prompt("OS", default="", type=str).split()
for os_name in selected_oss:
if os_name in oss:
selected.append(os_name)
click.echo("")
click.echo("Select tools (space-separated, Enter to skip):")
for idx, tool in enumerate(tools, 1):
click.echo(f" {idx}. {tool}")
selected_tools = click.prompt("Tools", default="", type=str).split()
for tool in selected_tools:
if tool in tools:
selected.append(tool)
if not selected:
click.echo("No templates selected.")
sys.exit(0)
config = ctx.obj.get("config")
generator = GitignoreGenerator(config)
for t in selected:
generator.add_template(t)
content = generator.generate()
click.echo("")
click.echo("Preview of generated .gitignore (first 50 lines):")
click.echo("-" * 50)
for idx, line in enumerate(content.split("\n")[:50]):
click.echo(line)
if len(content.split("\n")) > 50:
click.echo(f"... and {len(content.split('\\n')) - 50} more lines")
click.echo("-" * 50)
if click.confirm("Write to .gitignore?"):
output_path = Path(".gitignore")
mode = "a" if output_path.exists() else "w"
try:
with open(output_path, mode) as f:
f.write(content)
click.echo(f"Successfully wrote to {output_path}")
except PermissionError:
click.echo(f"Permission denied: {output_path}", err=True)
sys.exit(1)
except OSError as e:
click.echo(f"Error writing to {output_path}: {e}", err=True)
sys.exit(1)
elif click.confirm("Output to stdout instead?"):
click.echo(content)