Add source code files (detectors, generators, CLI)
This commit is contained in:
268
src/cli.py
Normal file
268
src/cli.py
Normal file
@@ -0,0 +1,268 @@
|
||||
"""CLI interface for auto-gitignore."""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Optional, Tuple
|
||||
|
||||
import click
|
||||
|
||||
from src.detectors.framework import FrameworkDetector
|
||||
from src.detectors.ide import IDEDetector
|
||||
from src.detectors.language import LanguageDetector
|
||||
from src.generators.merge import PatternMerger
|
||||
from src.generators.pattern import PatternGenerator
|
||||
|
||||
|
||||
LANGUAGE_CHOICES = [
|
||||
"python", "nodejs", "go", "java", "rust", "csharp", "cpp",
|
||||
"ruby", "php", "dart", "swift", "kotlin", "scala", "perl",
|
||||
"r", "elixir", "clojure", "lua", "haskell",
|
||||
]
|
||||
|
||||
FRAMEWORK_CHOICES = [
|
||||
"django", "flask", "fastapi", "react", "vue", "angular",
|
||||
"express", "nextjs", "nuxt", "svelte", "gatsby", "astro",
|
||||
"gin", "spring", "rails", "laravel", "dotnet", "quasar",
|
||||
"sveltekit", "remix", "vite", "nestjs",
|
||||
]
|
||||
|
||||
IDE_CHOICES = [
|
||||
"vscode", "jetbrains", "pycharm", "webstorm", "intellij",
|
||||
"eclipse", "netbeans", "sublime", "vim", "emacs", "atom", "spacemacs",
|
||||
]
|
||||
|
||||
OS_CHOICES = ["macos", "windows", "linux"]
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.option(
|
||||
"--path",
|
||||
"-p",
|
||||
type=click.Path(exists=True, file_okay=False, dir_okay=True),
|
||||
default=".",
|
||||
help="Path to project directory (default: current directory)",
|
||||
)
|
||||
@click.option(
|
||||
"--language",
|
||||
"-l",
|
||||
multiple=True,
|
||||
type=click.Choice(LANGUAGE_CHOICES),
|
||||
help="Force specific language(s)",
|
||||
)
|
||||
@click.option(
|
||||
"--framework",
|
||||
"-f",
|
||||
multiple=True,
|
||||
type=click.Choice(FRAMEWORK_CHOICES),
|
||||
help="Add framework-specific patterns",
|
||||
)
|
||||
@click.option(
|
||||
"--ide",
|
||||
multiple=True,
|
||||
type=click.Choice(IDE_CHOICES),
|
||||
help="Add IDE-specific patterns",
|
||||
)
|
||||
@click.option(
|
||||
"--os",
|
||||
multiple=True,
|
||||
type=click.Choice(OS_CHOICES),
|
||||
help="Add OS-specific patterns",
|
||||
)
|
||||
@click.option(
|
||||
"--custom",
|
||||
"-c",
|
||||
multiple=True,
|
||||
help="Add custom pattern(s)",
|
||||
)
|
||||
@click.option(
|
||||
"--output",
|
||||
"-o",
|
||||
type=click.Path(dir_okay=False),
|
||||
help="Output file path (default: .gitignore in project path)",
|
||||
)
|
||||
@click.option(
|
||||
"--merge/--no-merge",
|
||||
default=True,
|
||||
help="Merge patterns from multiple sources",
|
||||
)
|
||||
@click.option(
|
||||
"--dry-run",
|
||||
is_flag=True,
|
||||
default=False,
|
||||
help="Show generated content without writing file",
|
||||
)
|
||||
@click.option(
|
||||
"--force",
|
||||
is_flag=True,
|
||||
default=False,
|
||||
help="Overwrite existing .gitignore file",
|
||||
)
|
||||
@click.option(
|
||||
"--verbose",
|
||||
"-v",
|
||||
is_flag=True,
|
||||
default=False,
|
||||
help="Show detected stack and other details",
|
||||
)
|
||||
def main(
|
||||
path: str,
|
||||
language: Tuple[str, ...],
|
||||
framework: Tuple[str, ...],
|
||||
ide: Tuple[str, ...],
|
||||
os: Tuple[str, ...],
|
||||
custom: Tuple[str, ...],
|
||||
output: Optional[str],
|
||||
merge: bool,
|
||||
dry_run: bool,
|
||||
force: bool,
|
||||
verbose: bool,
|
||||
) -> None:
|
||||
"""Auto-generate .gitignore files based on project type and stack."""
|
||||
project_path = Path(path).resolve()
|
||||
|
||||
if output is None:
|
||||
output_path = project_path / ".gitignore"
|
||||
else:
|
||||
output_path = Path(output)
|
||||
|
||||
languages = list(language)
|
||||
frameworks = list(framework)
|
||||
ides = list(ide)
|
||||
os_patterns_list = list(os)
|
||||
custom_patterns = list(custom) if custom else []
|
||||
|
||||
if not languages or not frameworks:
|
||||
detected = detect_project_stack(project_path)
|
||||
if not languages:
|
||||
languages = detected.get("languages", [])
|
||||
if not frameworks:
|
||||
frameworks = detected.get("frameworks", [])
|
||||
if not ides:
|
||||
ides = detected.get("ides", [])
|
||||
if not os_patterns_list:
|
||||
os_patterns_list = detected.get("os", ["macos", "windows", "linux"])
|
||||
|
||||
if verbose:
|
||||
click.echo(f"Project path: {project_path}")
|
||||
if languages:
|
||||
click.echo(f"Detected languages: {', '.join(languages)}")
|
||||
if frameworks:
|
||||
click.echo(f"Detected frameworks: {', '.join(frameworks)}")
|
||||
if ides:
|
||||
click.echo(f"Detected IDEs: {', '.join(ides)}")
|
||||
click.echo("")
|
||||
|
||||
generator = PatternGenerator()
|
||||
merger = PatternMerger()
|
||||
|
||||
if merge and (languages or frameworks or ides):
|
||||
patterns_by_category: dict = {}
|
||||
if languages:
|
||||
patterns_by_category["language"] = []
|
||||
for lang in languages:
|
||||
patterns_by_category["language"].extend(
|
||||
generator.language_patterns.get(lang, [])
|
||||
)
|
||||
if frameworks:
|
||||
patterns_by_category["framework"] = []
|
||||
for fw in frameworks:
|
||||
patterns_by_category["framework"].extend(
|
||||
generator.framework_patterns.get(fw, [])
|
||||
)
|
||||
if ides:
|
||||
patterns_by_category["ide"] = []
|
||||
for ide_name in ides:
|
||||
patterns_by_category["ide"].extend(
|
||||
generator.ide_patterns.get(ide_name, [])
|
||||
)
|
||||
if os_patterns_list:
|
||||
patterns_by_category["os"] = []
|
||||
for os_name in os_patterns_list:
|
||||
patterns_by_category["os"].extend(
|
||||
generator.os_patterns.get(os_name, [])
|
||||
)
|
||||
|
||||
merged_patterns, conflicts = merger.merge(patterns_by_category, custom_patterns)
|
||||
|
||||
if conflicts and verbose:
|
||||
click.echo("Conflicts resolved:", err=True)
|
||||
for conflict in conflicts:
|
||||
click.echo(f" - {conflict}", err=True)
|
||||
|
||||
content = "\n".join(merged_patterns)
|
||||
else:
|
||||
content = generator.generate(
|
||||
languages=languages,
|
||||
frameworks=frameworks,
|
||||
ides=ides,
|
||||
os_list=list(os_patterns_list),
|
||||
custom_patterns=custom_patterns,
|
||||
)
|
||||
|
||||
if not content.strip():
|
||||
content = "# No patterns generated. Use --language, --framework, or --custom to add patterns."
|
||||
|
||||
if dry_run:
|
||||
click.echo(content)
|
||||
return
|
||||
|
||||
if output_path.exists() and not force:
|
||||
click.echo(f"Error: {output_path} already exists. Use --force to overwrite.", err=True)
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
output_path.write_text(content)
|
||||
click.echo(f"Generated .gitignore at {output_path}")
|
||||
|
||||
if verbose:
|
||||
line_count = len(content.splitlines())
|
||||
click.echo(f"Total lines: {line_count}")
|
||||
except PermissionError:
|
||||
click.echo(f"Error: Permission denied writing to {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)
|
||||
|
||||
|
||||
def detect_project_stack(path: Path) -> dict:
|
||||
"""Detect project languages, frameworks, and IDEs.
|
||||
|
||||
Args:
|
||||
path: Project root path.
|
||||
|
||||
Returns:
|
||||
Dict with detected languages, frameworks, and ides.
|
||||
"""
|
||||
language_detector = LanguageDetector()
|
||||
framework_detector = FrameworkDetector()
|
||||
ide_detector = IDEDetector()
|
||||
|
||||
languages = language_detector.detect(path)
|
||||
frameworks = framework_detector.detect(path)
|
||||
|
||||
frameworks_from_content = framework_detector.detect_from_content(path)
|
||||
for fw in frameworks_from_content:
|
||||
if fw not in frameworks:
|
||||
frameworks.append(fw)
|
||||
|
||||
package_frameworks = framework_detector.detect_package_json_frameworks(path)
|
||||
for fw in package_frameworks:
|
||||
if fw not in frameworks:
|
||||
frameworks.append(fw)
|
||||
|
||||
ides = ide_detector.detect(path)
|
||||
|
||||
if not ides:
|
||||
if ide_detector.detect_vscode(path):
|
||||
ides.append("vscode")
|
||||
if ide_detector.detect_jetbrains(path):
|
||||
if "pycharm" not in ides:
|
||||
ides.append("jetbrains")
|
||||
|
||||
return {
|
||||
"languages": languages,
|
||||
"frameworks": frameworks,
|
||||
"ides": ides,
|
||||
"os": ["macos", "windows", "linux"],
|
||||
}
|
||||
Reference in New Issue
Block a user