igntui 0.2.0__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. {igntui-0.2.0 → igntui-0.3.0}/PKG-INFO +1 -1
  2. {igntui-0.2.0 → igntui-0.3.0}/pyproject.toml +1 -1
  3. {igntui-0.2.0 → igntui-0.3.0}/src/igntui/__init__.py +1 -1
  4. {igntui-0.2.0 → igntui-0.3.0}/src/igntui/cli/commands/cache_cmd.py +22 -0
  5. {igntui-0.2.0 → igntui-0.3.0}/src/igntui/cli/commands/completion_cmd.py +7 -2
  6. {igntui-0.2.0 → igntui-0.3.0}/src/igntui/cli/parser.py +5 -0
  7. {igntui-0.2.0 → igntui-0.3.0}/src/igntui/core/cache.py +12 -20
  8. {igntui-0.2.0 → igntui-0.3.0}/LICENSE +0 -0
  9. {igntui-0.2.0 → igntui-0.3.0}/README.md +0 -0
  10. {igntui-0.2.0 → igntui-0.3.0}/src/igntui/__main__.py +0 -0
  11. {igntui-0.2.0 → igntui-0.3.0}/src/igntui/app.py +0 -0
  12. {igntui-0.2.0 → igntui-0.3.0}/src/igntui/cli/__init__.py +0 -0
  13. {igntui-0.2.0 → igntui-0.3.0}/src/igntui/cli/base.py +0 -0
  14. {igntui-0.2.0 → igntui-0.3.0}/src/igntui/cli/commands/__init__.py +0 -0
  15. {igntui-0.2.0 → igntui-0.3.0}/src/igntui/cli/commands/generate_cmd.py +0 -0
  16. {igntui-0.2.0 → igntui-0.3.0}/src/igntui/cli/commands/list_cmd.py +0 -0
  17. {igntui-0.2.0 → igntui-0.3.0}/src/igntui/cli/commands/test_cmd.py +0 -0
  18. {igntui-0.2.0 → igntui-0.3.0}/src/igntui/cli/commands/tui_cmd.py +0 -0
  19. {igntui-0.2.0 → igntui-0.3.0}/src/igntui/cli/setup.py +0 -0
  20. {igntui-0.2.0 → igntui-0.3.0}/src/igntui/core/__init__.py +0 -0
  21. {igntui-0.2.0 → igntui-0.3.0}/src/igntui/core/api/__init__.py +0 -0
  22. {igntui-0.2.0 → igntui-0.3.0}/src/igntui/core/api/client.py +0 -0
  23. {igntui-0.2.0 → igntui-0.3.0}/src/igntui/core/api/errors.py +0 -0
  24. {igntui-0.2.0 → igntui-0.3.0}/src/igntui/core/api/rate_limiter.py +0 -0
  25. {igntui-0.2.0 → igntui-0.3.0}/src/igntui/core/api/request_handler.py +0 -0
  26. {igntui-0.2.0 → igntui-0.3.0}/src/igntui/core/api/response.py +0 -0
  27. {igntui-0.2.0 → igntui-0.3.0}/src/igntui/core/api/types.py +0 -0
  28. {igntui-0.2.0 → igntui-0.3.0}/src/igntui/core/config.py +0 -0
  29. {igntui-0.2.0 → igntui-0.3.0}/src/igntui/core/managed_block.py +0 -0
  30. {igntui-0.2.0 → igntui-0.3.0}/src/igntui/core/project_config.py +0 -0
  31. {igntui-0.2.0 → igntui-0.3.0}/src/igntui/core/repo_config.py +0 -0
  32. {igntui-0.2.0 → igntui-0.3.0}/src/igntui/core/search.py +0 -0
  33. {igntui-0.2.0 → igntui-0.3.0}/src/igntui/core/usage.py +0 -0
  34. {igntui-0.2.0 → igntui-0.3.0}/src/igntui/main.py +0 -0
  35. {igntui-0.2.0 → igntui-0.3.0}/src/igntui/py.typed +0 -0
  36. {igntui-0.2.0 → igntui-0.3.0}/src/igntui/tui/__init__.py +0 -0
  37. {igntui-0.2.0 → igntui-0.3.0}/src/igntui/tui/actions.py +0 -0
  38. {igntui-0.2.0 → igntui-0.3.0}/src/igntui/tui/app.py +0 -0
  39. {igntui-0.2.0 → igntui-0.3.0}/src/igntui/tui/curses_setup.py +0 -0
  40. {igntui-0.2.0 → igntui-0.3.0}/src/igntui/tui/event_handler.py +0 -0
  41. {igntui-0.2.0 → igntui-0.3.0}/src/igntui/tui/lifecycle.py +0 -0
  42. {igntui-0.2.0 → igntui-0.3.0}/src/igntui/tui/renderer.py +0 -0
  43. {igntui-0.2.0 → igntui-0.3.0}/src/igntui/tui/state.py +0 -0
  44. {igntui-0.2.0 → igntui-0.3.0}/src/igntui/tui/updates.py +0 -0
  45. {igntui-0.2.0 → igntui-0.3.0}/src/igntui/ui/__init__.py +0 -0
  46. {igntui-0.2.0 → igntui-0.3.0}/src/igntui/ui/components/__init__.py +0 -0
  47. {igntui-0.2.0 → igntui-0.3.0}/src/igntui/ui/components/base_dialog.py +0 -0
  48. {igntui-0.2.0 → igntui-0.3.0}/src/igntui/ui/components/base_panel.py +0 -0
  49. {igntui-0.2.0 → igntui-0.3.0}/src/igntui/ui/components/confirm_dialog.py +0 -0
  50. {igntui-0.2.0 → igntui-0.3.0}/src/igntui/ui/components/content_panel.py +0 -0
  51. {igntui-0.2.0 → igntui-0.3.0}/src/igntui/ui/components/diff_preview_dialog.py +0 -0
  52. {igntui-0.2.0 → igntui-0.3.0}/src/igntui/ui/components/export_dialog.py +0 -0
  53. {igntui-0.2.0 → igntui-0.3.0}/src/igntui/ui/components/help_dialog.py +0 -0
  54. {igntui-0.2.0 → igntui-0.3.0}/src/igntui/ui/components/save_dialog.py +0 -0
  55. {igntui-0.2.0 → igntui-0.3.0}/src/igntui/ui/components/search_panel.py +0 -0
  56. {igntui-0.2.0 → igntui-0.3.0}/src/igntui/ui/components/selected_panel.py +0 -0
  57. {igntui-0.2.0 → igntui-0.3.0}/src/igntui/ui/components/splash.py +0 -0
  58. {igntui-0.2.0 → igntui-0.3.0}/src/igntui/ui/components/status_bar.py +0 -0
  59. {igntui-0.2.0 → igntui-0.3.0}/src/igntui/ui/components/templates_panel.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: igntui
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Interactive Terminal User Interface for GitIgnore template generation
5
5
  Keywords: gitignore,tui,terminal,git,templates,cli
6
6
  Author: Mohammad Abu Mattar
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "igntui"
3
- version = "0.2.0"
3
+ version = "0.3.0"
4
4
  description = "Interactive Terminal User Interface for GitIgnore template generation"
5
5
  readme = "README.md"
6
6
  license = "GPL-3.0-or-later"
@@ -5,7 +5,7 @@ import platform
5
5
  import sys
6
6
  from collections.abc import Callable
7
7
 
8
- __version__ = "0.2.0"
8
+ __version__ = "0.3.0"
9
9
  __author__ = "Mohammad Abu Mattar"
10
10
  __email__ = "mohammad.khaled@outlook.com"
11
11
  __description__ = """A professional TUI application for generating .gitignore files from templates
@@ -17,6 +17,11 @@ class CacheCommand(CLICommand):
17
17
 
18
18
  clear_parser = subparsers.add_parser("clear", help="Clear cache")
19
19
  clear_parser.add_argument("--force", action="store_true", help="Skip confirmation prompt")
20
+ clear_parser.add_argument(
21
+ "--expired",
22
+ action="store_true",
23
+ help="Remove only entries past their TTL, keeping everything still valid",
24
+ )
20
25
 
21
26
  subparsers.add_parser("stats", help="Show cache statistics")
22
27
  subparsers.add_parser("info", help="Show cache information")
@@ -33,6 +38,8 @@ class CacheCommand(CLICommand):
33
38
  elif args.cache_action == "stats":
34
39
  return self._show_stats(cache)
35
40
  elif args.cache_action == "clear":
41
+ if getattr(args, "expired", False):
42
+ return self._clear_expired(cache)
36
43
  return self._clear_cache(cache, args.force)
37
44
  else:
38
45
  print(f"Unknown cache action: {args.cache_action}")
@@ -85,6 +92,21 @@ class CacheCommand(CLICommand):
85
92
 
86
93
  return 0
87
94
 
95
+ def _clear_expired(self, cache: "CacheManager") -> int:
96
+ """Sweep only what is past its TTL.
97
+
98
+ Nothing prunes expired files on its own any more: reading every entry at
99
+ startup to find them cost every command ~19 ms over a realistic cache.
100
+ Re-requesting a template set overwrites its stale entry in place, so this
101
+ is for sets that were cached once and never asked for again.
102
+ """
103
+ removed = cache.cleanup_expired()
104
+ if removed:
105
+ print(f"Removed {removed} expired {'entry' if removed == 1 else 'entries'}")
106
+ else:
107
+ print("No expired entries")
108
+ return 0
109
+
88
110
  def _clear_cache(self, cache: "CacheManager", force: bool = False) -> int:
89
111
  if not force:
90
112
  response = input("Clear cache? This will remove all cached data. (y/N): ")
@@ -34,7 +34,7 @@ _igntui_complete() {
34
34
  tui) COMPREPLY=( $(compgen -W "--no-splash" -- "$cur") ); return ;;
35
35
  list) COMPREPLY=( $(compgen -W "--filter --count" -- "$cur") ); return ;;
36
36
  generate) COMPREPLY=( $(compgen -W "--output --append --force --dry-run --no-sidecar" -- "$cur") ); return ;;
37
- cache) COMPREPLY=( $(compgen -W "info stats clear --force" -- "$cur") ); return ;;
37
+ cache) COMPREPLY=( $(compgen -W "info stats clear --force --expired" -- "$cur") ); return ;;
38
38
  test) COMPREPLY=( $(compgen -W "--timeout" -- "$cur") ); return ;;
39
39
  completion) COMPREPLY=( $(compgen -W "bash zsh fish" -- "$cur") ); return ;;
40
40
  "") COMPREPLY=( $(compgen -W "$subcommands $global_flags" -- "$cur") ); return ;;
@@ -73,7 +73,10 @@ _igntui() {
73
73
  '--force[overwrite without prompt]' \\
74
74
  '--dry-run[print without writing]' \\
75
75
  '--no-sidecar[skip igntui.cfg.toml]' ;;
76
- cache) _values 'cache action' info stats clear ;;
76
+ cache) _arguments \\
77
+ '1:action:(info stats clear)' \\
78
+ '--force[skip confirmation]' \\
79
+ '--expired[only entries past their TTL]' ;;
77
80
  test) _arguments '--timeout[seconds]:seconds:' ;;
78
81
  completion) _values 'shell' bash zsh fish ;;
79
82
  esac
@@ -110,6 +113,8 @@ complete -c igntui -n "__fish_seen_subcommand_from generate" -l dry-run -d "Prin
110
113
  complete -c igntui -n "__fish_seen_subcommand_from generate" -l no-sidecar -d "Skip sidecar"
111
114
  complete -c igntui -n "__fish_seen_subcommand_from generate" -l force -d "Overwrite without prompt"
112
115
  complete -c igntui -n "__fish_seen_subcommand_from cache" -a "info stats clear"
116
+ complete -c igntui -n "__fish_seen_subcommand_from cache" -l force -d "Skip confirmation"
117
+ complete -c igntui -n "__fish_seen_subcommand_from cache" -l expired -d "Only expired entries"
113
118
  complete -c igntui -n "__fish_seen_subcommand_from completion" -a "bash zsh fish"
114
119
  """
115
120
 
@@ -117,6 +117,11 @@ def create_command_parser(
117
117
 
118
118
  cache_clear_parser = cache_subparsers.add_parser("clear", help="Clear all cache")
119
119
  cache_clear_parser.add_argument("--force", action="store_true", help="Skip confirmation prompt")
120
+ cache_clear_parser.add_argument(
121
+ "--expired",
122
+ action="store_true",
123
+ help="Remove only entries past their TTL, keeping everything still valid",
124
+ )
120
125
  cache_subparsers.add_parser("stats", help="Show cache statistics")
121
126
  cache_subparsers.add_parser("info", help="Show cache information")
122
127
 
@@ -49,8 +49,19 @@ class CacheManager:
49
49
 
50
50
  self.cache_dir.mkdir(parents=True, exist_ok=True)
51
51
  self._purge_legacy_content_keys()
52
- self._load_persistent_cache()
53
52
 
53
+ # Deliberately not reading the cache into memory here. `get()` already
54
+ # falls back to disk and promotes what it finds, so an eager load bought
55
+ # nothing except latency — and every command pays it, because
56
+ # GitIgnoreAPI constructs a manager on the way to doing anything. Over a
57
+ # realistic cache (300 content blobs, ~3.5 MiB) it measured ~19 ms of
58
+ # open + json.loads before the process could start its actual work.
59
+ #
60
+ # The eager pass also deleted expired files. Losing that is nearly free:
61
+ # a content key is a hash of its template set, so re-requesting the same
62
+ # set overwrites the stale entry in place, and `get()` deletes any
63
+ # expired entry it touches. Only sets never asked for again linger, and
64
+ # `igntui cache clear --expired` sweeps those.
54
65
  logger.debug("Initialized cache manager with directory: %s", self.cache_dir)
55
66
 
56
67
  def _purge_legacy_content_keys(self) -> None:
@@ -196,25 +207,6 @@ class CacheManager:
196
207
  **self._stats,
197
208
  }
198
209
 
199
- def _load_persistent_cache(self) -> None:
200
- try:
201
- loaded_count = 0
202
- for cache_file in self.cache_dir.glob("*.cache"):
203
- key = cache_file.stem
204
- entry = self._load_disk_cache(key)
205
-
206
- if entry and not entry.is_expired():
207
- self._memory_cache[key] = entry
208
- loaded_count += 1
209
- elif entry:
210
- cache_file.unlink()
211
-
212
- if loaded_count > 0:
213
- logger.info("Loaded %d cache entries from disk", loaded_count)
214
-
215
- except Exception as e:
216
- logger.warning("Failed to load persistent cache: %s", e)
217
-
218
210
  def _load_disk_cache(self, key: str) -> CacheEntry | None:
219
211
  cache_file = self.cache_dir / f"{key}.cache"
220
212
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes