This commit is contained in:
401
src/cli.py
Normal file
401
src/cli.py
Normal 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)
|
||||||
Reference in New Issue
Block a user