Initial upload: gitignore-generator-cli v1.0.0 with CI/CD workflow
This commit is contained in:
276
gitignore_generator/cli.py
Normal file
276
gitignore_generator/cli.py
Normal file
@@ -0,0 +1,276 @@
|
||||
"""CLI interface for gitignore-generator."""
|
||||
|
||||
import builtins
|
||||
import sys
|
||||
from typing import Optional
|
||||
|
||||
import click
|
||||
|
||||
from . import __version__
|
||||
from .api import get_list, get_patterns, GitignoreIOError
|
||||
from .cache import CacheManager
|
||||
from .detector import ProjectDetector
|
||||
from .generator import GitignoreGenerator
|
||||
|
||||
|
||||
def print_version(ctx: click.Context, param: click.Parameter, value: bool) -> None:
|
||||
"""Print version information."""
|
||||
if not value or ctx.resilient_parsing:
|
||||
return
|
||||
click.echo(f"gitignore-generator-cli v{__version__}")
|
||||
ctx.exit()
|
||||
|
||||
|
||||
@click.group()
|
||||
@click.option(
|
||||
'--version',
|
||||
is_flag=True,
|
||||
callback=print_version,
|
||||
expose_value=False,
|
||||
help='Show version information'
|
||||
)
|
||||
@click.option(
|
||||
'--cache-dir',
|
||||
type=click.Path(),
|
||||
help='Custom cache directory'
|
||||
)
|
||||
@click.pass_context
|
||||
def main(ctx: click.Context, cache_dir: Optional[str]) -> None:
|
||||
"""Generate .gitignore files for your projects."""
|
||||
ctx.ensure_object(dict)
|
||||
if cache_dir:
|
||||
ctx.obj['cache_dir'] = cache_dir
|
||||
|
||||
|
||||
@main.command()
|
||||
@click.argument('techs', nargs=-1, required=True)
|
||||
@click.option(
|
||||
'-o', '--output',
|
||||
type=click.Path(),
|
||||
help='Output file path (default: stdout)'
|
||||
)
|
||||
@click.option(
|
||||
'--preview',
|
||||
is_flag=True,
|
||||
help='Preview without writing to file'
|
||||
)
|
||||
@click.option(
|
||||
'--add-pattern',
|
||||
multiple=True,
|
||||
help='Add custom pattern'
|
||||
)
|
||||
@click.option(
|
||||
'--force-refresh',
|
||||
is_flag=True,
|
||||
help='Force refresh from API, bypass cache'
|
||||
)
|
||||
@click.option(
|
||||
'--merge',
|
||||
is_flag=True,
|
||||
help='Merge with existing .gitignore'
|
||||
)
|
||||
@click.pass_context
|
||||
def generate(
|
||||
ctx: click.Context,
|
||||
techs: tuple,
|
||||
output: Optional[str],
|
||||
preview: bool,
|
||||
add_pattern: tuple,
|
||||
force_refresh: bool,
|
||||
merge: bool
|
||||
) -> None:
|
||||
"""Generate .gitignore for specified tech stacks.
|
||||
|
||||
Examples:
|
||||
gitignore-generator generate node python django
|
||||
gitignore-generator generate node -o .gitignore --preview
|
||||
gitignore-generator generate node --add-pattern "*.log"
|
||||
"""
|
||||
tech_list = [t.lower() for t in techs]
|
||||
|
||||
if preview and output:
|
||||
click.echo("Error: --preview and -o cannot be used together", err=True)
|
||||
sys.exit(1)
|
||||
|
||||
generator = GitignoreGenerator()
|
||||
|
||||
if merge and output:
|
||||
try:
|
||||
content = generator.merge_with_existing(
|
||||
output, tech_list, builtins.list(add_pattern), force_refresh
|
||||
)
|
||||
except FileNotFoundError:
|
||||
click.echo(f"Error: File not found: {output}", err=True)
|
||||
sys.exit(1)
|
||||
else:
|
||||
try:
|
||||
content = generator.generate_file(
|
||||
tech_list, output, preview, builtins.list(add_pattern), force_refresh
|
||||
)
|
||||
except GitignoreIOError as e:
|
||||
click.echo(f"Error: {e}", err=True)
|
||||
sys.exit(1)
|
||||
|
||||
if preview:
|
||||
click.echo(content)
|
||||
|
||||
if output and not preview:
|
||||
click.echo(f"Created: {output}")
|
||||
|
||||
|
||||
@main.command()
|
||||
@click.option(
|
||||
'-o', '--output',
|
||||
type=click.Path(),
|
||||
help='Output file path'
|
||||
)
|
||||
@click.option(
|
||||
'--preview',
|
||||
is_flag=True,
|
||||
help='Preview without writing'
|
||||
)
|
||||
@click.option(
|
||||
'--force-refresh',
|
||||
is_flag=True,
|
||||
help='Force refresh from API'
|
||||
)
|
||||
@click.pass_context
|
||||
def detect(
|
||||
ctx: click.Context,
|
||||
output: Optional[str],
|
||||
preview: bool,
|
||||
force_refresh: bool
|
||||
) -> None:
|
||||
"""Auto-detect project type and suggest gitignore.
|
||||
|
||||
Scans current directory for project markers and suggests
|
||||
appropriate gitignore technologies.
|
||||
"""
|
||||
detector = ProjectDetector()
|
||||
suggestions = detector.suggest_gitignore()
|
||||
details = detector.get_detection_details()
|
||||
|
||||
if not suggestions:
|
||||
click.echo("No project type detected. Please specify tech stacks manually.")
|
||||
return
|
||||
|
||||
click.echo("Detected technologies:")
|
||||
for detail in details:
|
||||
tech = detail['technology']
|
||||
matched = ', '.join(detail['matched_files'][:3])
|
||||
click.echo(f" - {tech}: {matched}")
|
||||
|
||||
click.echo(f"\nSuggested .gitignore for: {', '.join(suggestions)}")
|
||||
|
||||
if preview or output:
|
||||
generator = GitignoreGenerator()
|
||||
try:
|
||||
content = generator.generate_file(
|
||||
suggestions, output, preview, None, force_refresh
|
||||
)
|
||||
except GitignoreIOError as e:
|
||||
click.echo(f"Error: {e}", err=True)
|
||||
sys.exit(1)
|
||||
|
||||
if preview:
|
||||
click.echo("\n--- Generated .gitignore ---")
|
||||
click.echo(content)
|
||||
|
||||
if output and not preview:
|
||||
click.echo(f"\nCreated: {output}")
|
||||
|
||||
|
||||
@main.command()
|
||||
@click.argument('search', required=False)
|
||||
@click.option(
|
||||
'--force-refresh',
|
||||
is_flag=True,
|
||||
help='Force refresh from API'
|
||||
)
|
||||
@click.pass_context
|
||||
def list(
|
||||
ctx: click.Context,
|
||||
search: Optional[str],
|
||||
force_refresh: bool
|
||||
) -> None:
|
||||
"""List all supported tech stacks.
|
||||
|
||||
Optionally filter by search term.
|
||||
"""
|
||||
try:
|
||||
techs = get_list(force_refresh)
|
||||
except GitignoreIOError as e:
|
||||
click.echo(f"Error: {e}", err=True)
|
||||
sys.exit(1)
|
||||
|
||||
if search:
|
||||
search_lower = search.lower()
|
||||
techs = [t for t in techs if search_lower in t.lower()]
|
||||
click.echo(f"Matching '{search}':")
|
||||
else:
|
||||
click.echo(f"Supported technologies ({len(techs)}):")
|
||||
|
||||
for tech in sorted(techs):
|
||||
click.echo(f" - {tech}")
|
||||
|
||||
|
||||
@main.command()
|
||||
@click.argument('tech')
|
||||
@click.option(
|
||||
'--force-refresh',
|
||||
is_flag=True,
|
||||
help='Force refresh from API'
|
||||
)
|
||||
@click.pass_context
|
||||
def show(ctx: click.Context, tech: str, force_refresh: bool) -> None:
|
||||
"""Show gitignore patterns for a specific tech.
|
||||
|
||||
Useful for previewing patterns before adding to your .gitignore.
|
||||
"""
|
||||
try:
|
||||
patterns = get_patterns(tech.lower(), force_refresh)
|
||||
click.echo(patterns)
|
||||
except GitignoreIOError as e:
|
||||
click.echo(f"Error: {e}", err=True)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
@main.command()
|
||||
@click.pass_context
|
||||
def cache(ctx: click.Context) -> None:
|
||||
"""Manage cache.
|
||||
|
||||
Show cache statistics or clear cache.
|
||||
"""
|
||||
cache = CacheManager()
|
||||
stats = cache.get_stats()
|
||||
|
||||
click.echo("Cache Statistics:")
|
||||
click.echo(f" Total items: {stats['total_items']}")
|
||||
click.echo(f" Valid items: {stats['valid_items']}")
|
||||
click.echo(f" Expired items: {stats['expired_items']}")
|
||||
click.echo(f" Cache directory: {stats['cache_dir']}")
|
||||
|
||||
|
||||
@main.command()
|
||||
@click.pass_context
|
||||
def clear_cache(ctx: click.Context) -> None:
|
||||
"""Clear all cached patterns."""
|
||||
cache = CacheManager()
|
||||
count = cache.clear()
|
||||
click.echo(f"Cleared {count} cached items.")
|
||||
|
||||
|
||||
@main.command()
|
||||
@click.option(
|
||||
'--days',
|
||||
type=int,
|
||||
default=7,
|
||||
help='Remove entries older than DAYS (default: 7)'
|
||||
)
|
||||
@click.pass_context
|
||||
def cleanup(ctx: click.Context, days: int) -> None:
|
||||
"""Remove expired cache entries."""
|
||||
cache = CacheManager(expiry_days=days)
|
||||
count = cache.cleanup_expired()
|
||||
click.echo(f"Cleaned up {count} expired entries.")
|
||||
Reference in New Issue
Block a user