igntui 0.3.0__tar.gz → 0.4.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 (60) hide show
  1. {igntui-0.3.0 → igntui-0.4.0}/PKG-INFO +4 -3
  2. igntui-0.4.0/pyproject.toml +149 -0
  3. igntui-0.3.0/pyproject.toml → igntui-0.4.0/pyproject.toml.orig +7 -3
  4. {igntui-0.3.0 → igntui-0.4.0}/src/igntui/__init__.py +1 -1
  5. {igntui-0.3.0 → igntui-0.4.0}/src/igntui/core/cache.py +25 -1
  6. {igntui-0.3.0 → igntui-0.4.0}/LICENSE +0 -0
  7. {igntui-0.3.0 → igntui-0.4.0}/README.md +0 -0
  8. {igntui-0.3.0 → igntui-0.4.0}/src/igntui/__main__.py +0 -0
  9. {igntui-0.3.0 → igntui-0.4.0}/src/igntui/app.py +0 -0
  10. {igntui-0.3.0 → igntui-0.4.0}/src/igntui/cli/__init__.py +0 -0
  11. {igntui-0.3.0 → igntui-0.4.0}/src/igntui/cli/base.py +0 -0
  12. {igntui-0.3.0 → igntui-0.4.0}/src/igntui/cli/commands/__init__.py +0 -0
  13. {igntui-0.3.0 → igntui-0.4.0}/src/igntui/cli/commands/cache_cmd.py +0 -0
  14. {igntui-0.3.0 → igntui-0.4.0}/src/igntui/cli/commands/completion_cmd.py +0 -0
  15. {igntui-0.3.0 → igntui-0.4.0}/src/igntui/cli/commands/generate_cmd.py +0 -0
  16. {igntui-0.3.0 → igntui-0.4.0}/src/igntui/cli/commands/list_cmd.py +0 -0
  17. {igntui-0.3.0 → igntui-0.4.0}/src/igntui/cli/commands/test_cmd.py +0 -0
  18. {igntui-0.3.0 → igntui-0.4.0}/src/igntui/cli/commands/tui_cmd.py +0 -0
  19. {igntui-0.3.0 → igntui-0.4.0}/src/igntui/cli/parser.py +0 -0
  20. {igntui-0.3.0 → igntui-0.4.0}/src/igntui/cli/setup.py +0 -0
  21. {igntui-0.3.0 → igntui-0.4.0}/src/igntui/core/__init__.py +0 -0
  22. {igntui-0.3.0 → igntui-0.4.0}/src/igntui/core/api/__init__.py +0 -0
  23. {igntui-0.3.0 → igntui-0.4.0}/src/igntui/core/api/client.py +0 -0
  24. {igntui-0.3.0 → igntui-0.4.0}/src/igntui/core/api/errors.py +0 -0
  25. {igntui-0.3.0 → igntui-0.4.0}/src/igntui/core/api/rate_limiter.py +0 -0
  26. {igntui-0.3.0 → igntui-0.4.0}/src/igntui/core/api/request_handler.py +0 -0
  27. {igntui-0.3.0 → igntui-0.4.0}/src/igntui/core/api/response.py +0 -0
  28. {igntui-0.3.0 → igntui-0.4.0}/src/igntui/core/api/types.py +0 -0
  29. {igntui-0.3.0 → igntui-0.4.0}/src/igntui/core/config.py +0 -0
  30. {igntui-0.3.0 → igntui-0.4.0}/src/igntui/core/managed_block.py +0 -0
  31. {igntui-0.3.0 → igntui-0.4.0}/src/igntui/core/project_config.py +0 -0
  32. {igntui-0.3.0 → igntui-0.4.0}/src/igntui/core/repo_config.py +0 -0
  33. {igntui-0.3.0 → igntui-0.4.0}/src/igntui/core/search.py +0 -0
  34. {igntui-0.3.0 → igntui-0.4.0}/src/igntui/core/usage.py +0 -0
  35. {igntui-0.3.0 → igntui-0.4.0}/src/igntui/main.py +0 -0
  36. {igntui-0.3.0 → igntui-0.4.0}/src/igntui/py.typed +0 -0
  37. {igntui-0.3.0 → igntui-0.4.0}/src/igntui/tui/__init__.py +0 -0
  38. {igntui-0.3.0 → igntui-0.4.0}/src/igntui/tui/actions.py +0 -0
  39. {igntui-0.3.0 → igntui-0.4.0}/src/igntui/tui/app.py +0 -0
  40. {igntui-0.3.0 → igntui-0.4.0}/src/igntui/tui/curses_setup.py +0 -0
  41. {igntui-0.3.0 → igntui-0.4.0}/src/igntui/tui/event_handler.py +0 -0
  42. {igntui-0.3.0 → igntui-0.4.0}/src/igntui/tui/lifecycle.py +0 -0
  43. {igntui-0.3.0 → igntui-0.4.0}/src/igntui/tui/renderer.py +0 -0
  44. {igntui-0.3.0 → igntui-0.4.0}/src/igntui/tui/state.py +0 -0
  45. {igntui-0.3.0 → igntui-0.4.0}/src/igntui/tui/updates.py +0 -0
  46. {igntui-0.3.0 → igntui-0.4.0}/src/igntui/ui/__init__.py +0 -0
  47. {igntui-0.3.0 → igntui-0.4.0}/src/igntui/ui/components/__init__.py +0 -0
  48. {igntui-0.3.0 → igntui-0.4.0}/src/igntui/ui/components/base_dialog.py +0 -0
  49. {igntui-0.3.0 → igntui-0.4.0}/src/igntui/ui/components/base_panel.py +0 -0
  50. {igntui-0.3.0 → igntui-0.4.0}/src/igntui/ui/components/confirm_dialog.py +0 -0
  51. {igntui-0.3.0 → igntui-0.4.0}/src/igntui/ui/components/content_panel.py +0 -0
  52. {igntui-0.3.0 → igntui-0.4.0}/src/igntui/ui/components/diff_preview_dialog.py +0 -0
  53. {igntui-0.3.0 → igntui-0.4.0}/src/igntui/ui/components/export_dialog.py +0 -0
  54. {igntui-0.3.0 → igntui-0.4.0}/src/igntui/ui/components/help_dialog.py +0 -0
  55. {igntui-0.3.0 → igntui-0.4.0}/src/igntui/ui/components/save_dialog.py +0 -0
  56. {igntui-0.3.0 → igntui-0.4.0}/src/igntui/ui/components/search_panel.py +0 -0
  57. {igntui-0.3.0 → igntui-0.4.0}/src/igntui/ui/components/selected_panel.py +0 -0
  58. {igntui-0.3.0 → igntui-0.4.0}/src/igntui/ui/components/splash.py +0 -0
  59. {igntui-0.3.0 → igntui-0.4.0}/src/igntui/ui/components/status_bar.py +0 -0
  60. {igntui-0.3.0 → igntui-0.4.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.3.0
3
+ Version: 0.4.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
@@ -15,11 +15,12 @@ Classifier: Programming Language :: Python :: 3
15
15
  Classifier: Programming Language :: Python :: 3.11
16
16
  Classifier: Programming Language :: Python :: 3.12
17
17
  Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python :: 3.14
18
19
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
20
  Classifier: Topic :: Software Development :: Version Control :: Git
20
21
  Classifier: Topic :: Terminals
21
22
  Classifier: Topic :: Utilities
22
- Requires-Dist: windows-curses>=2.4.1 ; sys_platform == 'win32'
23
+ Requires-Dist: windows-curses>=2.4.2 ; sys_platform == 'win32'
23
24
  Requires-Dist: pyfiglet>=1.0.2
24
25
  Requires-Dist: tomli-w>=1.0.0
25
26
  Requires-Dist: pytest>=7.0.0 ; extra == 'dev'
@@ -30,7 +31,7 @@ Requires-Dist: pytest>=7.0.0 ; extra == 'test'
30
31
  Requires-Dist: pytest-cov>=4.0.0 ; extra == 'test'
31
32
  Requires-Dist: pytest-mock>=3.10.0 ; extra == 'test'
32
33
  Requires-Dist: freezegun>=1.4.0 ; extra == 'test'
33
- Requires-Dist: windows-curses>=2.4.1 ; sys_platform == 'win32' and extra == 'windows'
34
+ Requires-Dist: windows-curses>=2.4.2 ; sys_platform == 'win32' and extra == 'windows'
34
35
  Maintainer: Mohammad Abu Mattar
35
36
  Maintainer-email: Mohammad Abu Mattar <mohammad.khaled@outlook.com>
36
37
  Requires-Python: >=3.11
@@ -0,0 +1,149 @@
1
+ [project]
2
+ name = "igntui"
3
+ version = "0.4.0"
4
+ description = "Interactive Terminal User Interface for GitIgnore template generation"
5
+ readme = "README.md"
6
+ license = "GPL-3.0-or-later"
7
+ license-files = ["LICENSE"]
8
+ requires-python = ">=3.11"
9
+ dependencies = [
10
+ "windows-curses>=2.4.2; sys_platform == 'win32'",
11
+ "pyfiglet>=1.0.2",
12
+ "tomli-w>=1.0.0",
13
+ ]
14
+ keywords = [
15
+ "gitignore",
16
+ "tui",
17
+ "terminal",
18
+ "git",
19
+ "templates",
20
+ "cli",
21
+ ]
22
+ classifiers = [
23
+ "Development Status :: 4 - Beta",
24
+ "Environment :: Console :: Curses",
25
+ "Intended Audience :: Developers",
26
+ "Operating System :: OS Independent",
27
+ "Programming Language :: Python :: 3",
28
+ "Programming Language :: Python :: 3.11",
29
+ "Programming Language :: Python :: 3.12",
30
+ "Programming Language :: Python :: 3.13",
31
+ "Programming Language :: Python :: 3.14",
32
+ "Topic :: Software Development :: Libraries :: Python Modules",
33
+ "Topic :: Software Development :: Version Control :: Git",
34
+ "Topic :: Terminals",
35
+ "Topic :: Utilities",
36
+ ]
37
+
38
+ [[project.authors]]
39
+ name = "Mohammad Abu Mattar"
40
+ email = "mohammad.khaled@outlook.com"
41
+
42
+ [[project.maintainers]]
43
+ name = "Mohammad Abu Mattar"
44
+ email = "mohammad.khaled@outlook.com"
45
+
46
+ [project.urls]
47
+ Homepage = "https://github.com/MKAbuMattar/igntui"
48
+ Repository = "https://github.com/MKAbuMattar/igntui"
49
+ Documentation = "https://github.com/MKAbuMattar/igntui/blob/main/README.md"
50
+ Issues = "https://github.com/MKAbuMattar/igntui/issues"
51
+
52
+ [project.scripts]
53
+ igntui = "igntui.main:cli_main"
54
+ gitignore-tui = "igntui.main:tui_main"
55
+
56
+ [project.optional-dependencies]
57
+ dev = [
58
+ "pytest>=7.0.0",
59
+ "pytest-cov>=4.0.0",
60
+ "pytest-mock>=3.10.0",
61
+ "ruff>=0.1.0",
62
+ ]
63
+ test = [
64
+ "pytest>=7.0.0",
65
+ "pytest-cov>=4.0.0",
66
+ "pytest-mock>=3.10.0",
67
+ "freezegun>=1.4.0",
68
+ ]
69
+ windows = ["windows-curses>=2.4.2; sys_platform == 'win32'"]
70
+
71
+ [build-system]
72
+ requires = ["uv_build>=0.9.5,<0.13.0"]
73
+ build-backend = "uv_build"
74
+
75
+ [tool.ruff]
76
+ line-length = 100
77
+ target-version = "py311"
78
+
79
+ [tool.ruff.lint]
80
+ select = [
81
+ "E",
82
+ "W",
83
+ "F",
84
+ "I",
85
+ "B",
86
+ "C4",
87
+ "UP",
88
+ ]
89
+ ignore = [
90
+ "E501",
91
+ "B008",
92
+ "C901",
93
+ "E402",
94
+ "W291",
95
+ "W293",
96
+ "C408",
97
+ "C414",
98
+ ]
99
+
100
+ [tool.ruff.lint.per-file-ignores]
101
+ "__init__.py" = ["F401"]
102
+
103
+ [tool.mypy]
104
+ python_version = "3.11"
105
+ warn_return_any = true
106
+ warn_unused_configs = true
107
+ disallow_untyped_defs = false
108
+ ignore_missing_imports = true
109
+
110
+ [[tool.mypy.overrides]]
111
+ module = [
112
+ "curses",
113
+ "curses.*",
114
+ "windows_curses",
115
+ "windows_curses.*",
116
+ ]
117
+ ignore_missing_imports = true
118
+ ignore_errors = true
119
+
120
+ [tool.ty.environment]
121
+ python-version = "3.11"
122
+
123
+ [tool.ty.src]
124
+ include = ["src/igntui"]
125
+
126
+ [tool.pytest.ini_options]
127
+ testpaths = ["tests"]
128
+ python_files = ["test_*.py"]
129
+ addopts = "-v --strict-markers"
130
+ markers = ["integration: tests that hit the network or filesystem extensively"]
131
+
132
+ [tool.coverage.run]
133
+ source = ["src/igntui"]
134
+ omit = [
135
+ "*/tests/*",
136
+ "*/__main__.py",
137
+ "*/tui/curses_setup.py",
138
+ "*/tui/renderer.py",
139
+ "*/ui/*",
140
+ ]
141
+
142
+ [tool.coverage.report]
143
+ exclude_lines = [
144
+ "pragma: no cover",
145
+ "raise NotImplementedError",
146
+ "if __name__ == .__main__.:",
147
+ "if TYPE_CHECKING:",
148
+ ]
149
+ show_missing = true
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "igntui"
3
- version = "0.3.0"
3
+ version = "0.4.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"
@@ -16,7 +16,10 @@ maintainers = [
16
16
  ]
17
17
  requires-python = ">=3.11"
18
18
  dependencies = [
19
- "windows-curses>=2.4.1; sys_platform == 'win32'",
19
+ # 2.4.2 is the first release with cp314 wheels. The floor matters on
20
+ # Windows: an older pin can resolve to a version with no wheel for the
21
+ # running interpreter, which fails at install time rather than degrading.
22
+ "windows-curses>=2.4.2; sys_platform == 'win32'",
20
23
  "pyfiglet>=1.0.2",
21
24
  "tomli-w>=1.0.0",
22
25
  ]
@@ -30,6 +33,7 @@ classifiers = [
30
33
  "Programming Language :: Python :: 3.11",
31
34
  "Programming Language :: Python :: 3.12",
32
35
  "Programming Language :: Python :: 3.13",
36
+ "Programming Language :: Python :: 3.14",
33
37
  "Topic :: Software Development :: Libraries :: Python Modules",
34
38
  "Topic :: Software Development :: Version Control :: Git",
35
39
  "Topic :: Terminals",
@@ -63,7 +67,7 @@ test = [
63
67
  "freezegun>=1.4.0",
64
68
  ]
65
69
  windows = [
66
- "windows-curses>=2.4.1; sys_platform == 'win32'",
70
+ "windows-curses>=2.4.2; sys_platform == 'win32'",
67
71
  ]
68
72
 
69
73
  [build-system]
@@ -5,7 +5,7 @@ import platform
5
5
  import sys
6
6
  from collections.abc import Callable
7
7
 
8
- __version__ = "0.3.0"
8
+ __version__ = "0.4.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
@@ -4,7 +4,9 @@
4
4
  import hashlib
5
5
  import json
6
6
  import logging
7
+ import os
7
8
  import re
9
+ import tempfile
8
10
  import time
9
11
  from dataclasses import asdict, dataclass
10
12
  from pathlib import Path
@@ -228,16 +230,38 @@ class CacheManager:
228
230
  return None
229
231
 
230
232
  def _save_disk_cache(self, key: str, entry: CacheEntry) -> None:
233
+ """Write an entry to disk atomically.
234
+
235
+ Writing in place meant a crash, a full disk, or two processes saving the
236
+ same key could leave truncated JSON behind. That was survivable — a read
237
+ catches the JSONDecodeError and unlinks the file — but the entry was lost
238
+ and the failure was silent. Writing a temp file in the same directory and
239
+ renaming makes the swap atomic on POSIX and Windows: a reader sees either
240
+ the old entry or the new one, never half of either.
241
+ """
231
242
  cache_file = self.cache_dir / f"{key}.cache"
243
+ tmp_path: str | None = None
232
244
 
233
245
  try:
234
- with open(cache_file, "w", encoding="utf-8") as f:
246
+ # Same directory, so os.replace is a rename rather than a cross-device copy.
247
+ fd, tmp_path = tempfile.mkstemp(dir=self.cache_dir, suffix=".tmp")
248
+ with os.fdopen(fd, "w", encoding="utf-8") as f:
235
249
  json.dump(asdict(entry), f, separators=(",", ":"))
250
+ os.replace(tmp_path, cache_file)
251
+ tmp_path = None
236
252
 
237
253
  self._stats["disk_writes"] += 1
238
254
 
239
255
  except (OSError, TypeError) as e:
240
256
  logger.warning("Failed to save cache file %s: %s", cache_file, e)
257
+ finally:
258
+ # A serialisation failure leaves the temp file behind; the previous
259
+ # good entry is still in place, which is the point.
260
+ if tmp_path is not None:
261
+ try:
262
+ os.unlink(tmp_path)
263
+ except OSError:
264
+ pass
241
265
 
242
266
  def _delete_disk_cache(self, key: str) -> bool:
243
267
  cache_file = self.cache_dir / f"{key}.cache"
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
File without changes