Coverage for little_loops/cli/gitignore.py: 19%

1"""ll-gitignore: Suggest and apply .gitignore patterns."""

2

3 from __future__ import annotations

4

5 import argparse

6 from pathlib import Path

7

8 from little_loops.cli_args import add_config_arg, add_dry_run_arg, add_quiet_arg

9 from little_loops.git_operations import (

10 GitignorePattern,

11 add_patterns_to_gitignore,

12 suggest_gitignore_patterns,

13)

14 from little_loops.logger import Logger

15

16

17 def main_gitignore() -> int:

18 """Entry point for ll-gitignore command.

19

20 Scan for untracked files, suggest .gitignore patterns, and optionally apply them.

21

22 Returns:

23 Exit code (0 = success, 1 = error)

24 """

25 parser = argparse.ArgumentParser(

26 prog="ll-gitignore",

27 description="Suggest and apply .gitignore patterns based on untracked files",

28 formatter_class=argparse.RawDescriptionHelpFormatter,

29 epilog="""

30 Examples:

31 %(prog)s # Show suggestions and apply approved patterns

32 %(prog)s --dry-run # Preview suggestions without modifying .gitignore

33 %(prog)s --quiet # Suppress non-essential output

34""",

35 )

36

37 add_dry_run_arg(parser)

38 add_quiet_arg(parser)

39 add_config_arg(parser)

40

41 args = parser.parse_args()

42

43 repo_root = args.config or Path.cwd()

44 logger = Logger(verbose=not args.quiet)

45 dry_run: bool = args.dry_run

46

47 if dry_run:

48 logger.info("[DRY RUN] Showing suggestions without modifying .gitignore")

49

50 suggestion = suggest_gitignore_patterns(repo_root=repo_root, logger=logger)

51

52 if not suggestion.has_suggestions:

53 logger.info("No .gitignore suggestions — your repo looks clean.")

54 return 0

55

56 logger.info(suggestion.summary)

57

58 # Display categorized suggestions

59 categories: dict[str, list[GitignorePattern]] = {}

60 for pattern in suggestion.patterns:

61 categories.setdefault(pattern.category, []).append(pattern)

62

63 for category, patterns in sorted(categories.items()):

64 logger.info(f"\n [{category}]")

65 for p in patterns:

66 file_count = len(p.files_matched)

67 files_label = f"{file_count} file{'s' if file_count != 1 else ''}"

68 logger.info(f" {p.pattern:<30} {p.description} ({files_label})")

69

70 if dry_run:

71 return 0

72

73 pattern_strings = [p.pattern for p in suggestion.patterns]

74 success = add_patterns_to_gitignore(pattern_strings, repo_root=repo_root, logger=logger)

75

76 return 0 if success else 1