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

36 statements  

« prev     ^ index     » next       coverage.py v7.12.0, created at 2026-03-18 16:18 -0500

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

2 

3from __future__ import annotations 

4 

5import argparse 

6from pathlib import Path 

7 

8from little_loops.cli_args import add_config_arg, add_dry_run_arg, add_quiet_arg 

9from little_loops.git_operations import ( 

10 GitignorePattern, 

11 add_patterns_to_gitignore, 

12 suggest_gitignore_patterns, 

13) 

14from little_loops.logger import Logger 

15 

16 

17def 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=""" 

30Examples: 

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