wuf 0.1.0a7__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 (199) hide show
  1. wuf-0.1.0a7/.github/scripts/sync_lock_version.py +123 -0
  2. wuf-0.1.0a7/LICENSE +21 -0
  3. wuf-0.1.0a7/MANIFEST.in +4 -0
  4. wuf-0.1.0a7/PKG-INFO +214 -0
  5. wuf-0.1.0a7/README.md +174 -0
  6. wuf-0.1.0a7/THIRD_PARTY_NOTICES.md +19 -0
  7. wuf-0.1.0a7/benchmarks/README.md +87 -0
  8. wuf-0.1.0a7/benchmarks/__init__.py +1 -0
  9. wuf-0.1.0a7/benchmarks/cases.py +96 -0
  10. wuf-0.1.0a7/benchmarks/fixture.py +389 -0
  11. wuf-0.1.0a7/benchmarks/metrics.py +243 -0
  12. wuf-0.1.0a7/benchmarks/run.py +517 -0
  13. wuf-0.1.0a7/pyproject.toml +115 -0
  14. wuf-0.1.0a7/setup.cfg +4 -0
  15. wuf-0.1.0a7/tests/architecture/test_dependencies.py +214 -0
  16. wuf-0.1.0a7/tests/end_to_end/test_wuf.py +691 -0
  17. wuf-0.1.0a7/tests/integration/application/test_derived_scope.py +429 -0
  18. wuf-0.1.0a7/tests/integration/application/test_execution.py +676 -0
  19. wuf-0.1.0a7/tests/integration/application/test_git_selection.py +263 -0
  20. wuf-0.1.0a7/tests/integration/benchmarks/test_fixture.py +132 -0
  21. wuf-0.1.0a7/tests/integration/filesystem/test_collector.py +1878 -0
  22. wuf-0.1.0a7/tests/integration/filesystem/test_ignore.py +692 -0
  23. wuf-0.1.0a7/tests/integration/git/test_changes.py +693 -0
  24. wuf-0.1.0a7/tests/integration/git/test_commits.py +1183 -0
  25. wuf-0.1.0a7/tests/integration/git/test_reflogs.py +692 -0
  26. wuf-0.1.0a7/tests/integration/git/test_tags.py +524 -0
  27. wuf-0.1.0a7/tests/integration/package/test_distribution.py +39 -0
  28. wuf-0.1.0a7/tests/integration/package/test_release_scripts.py +77 -0
  29. wuf-0.1.0a7/tests/support/__init__.py +1 -0
  30. wuf-0.1.0a7/tests/support/git_repo.py +130 -0
  31. wuf-0.1.0a7/tests/support/reports.py +154 -0
  32. wuf-0.1.0a7/tests/unit/application/test_report.py +9 -0
  33. wuf-0.1.0a7/tests/unit/benchmarks/test_benchmarks.py +70 -0
  34. wuf-0.1.0a7/tests/unit/cli/test_cli.py +354 -0
  35. wuf-0.1.0a7/tests/unit/cli/test_progress.py +32 -0
  36. wuf-0.1.0a7/tests/unit/collection/filesystem/test_inventory_metadata.py +167 -0
  37. wuf-0.1.0a7/tests/unit/collection/filesystem/test_linux.py +322 -0
  38. wuf-0.1.0a7/tests/unit/collection/filesystem/test_metadata.py +275 -0
  39. wuf-0.1.0a7/tests/unit/collection/filesystem/test_publication.py +67 -0
  40. wuf-0.1.0a7/tests/unit/collection/filesystem/test_timestamps.py +76 -0
  41. wuf-0.1.0a7/tests/unit/collection/git/test_change_spool.py +95 -0
  42. wuf-0.1.0a7/tests/unit/collection/git/test_objects.py +318 -0
  43. wuf-0.1.0a7/tests/unit/collection/test_diagnostics.py +152 -0
  44. wuf-0.1.0a7/tests/unit/collection/test_process.py +37 -0
  45. wuf-0.1.0a7/tests/unit/configuration/test_local_timezone.py +94 -0
  46. wuf-0.1.0a7/tests/unit/configuration/test_options.py +655 -0
  47. wuf-0.1.0a7/tests/unit/configuration/test_settings.py +970 -0
  48. wuf-0.1.0a7/tests/unit/configuration/test_styles.py +94 -0
  49. wuf-0.1.0a7/tests/unit/domain/test_coverage.py +330 -0
  50. wuf-0.1.0a7/tests/unit/domain/test_evidence.py +118 -0
  51. wuf-0.1.0a7/tests/unit/domain/test_observations.py +319 -0
  52. wuf-0.1.0a7/tests/unit/domain/test_schedule.py +202 -0
  53. wuf-0.1.0a7/tests/unit/domain/test_scope.py +130 -0
  54. wuf-0.1.0a7/tests/unit/domain/test_time.py +184 -0
  55. wuf-0.1.0a7/tests/unit/folding/test_aggregation.py +1092 -0
  56. wuf-0.1.0a7/tests/unit/folding/test_pipeline.py +31 -0
  57. wuf-0.1.0a7/tests/unit/folding/test_spill_failures.py +124 -0
  58. wuf-0.1.0a7/tests/unit/reporting/test_coverage_rendering.py +78 -0
  59. wuf-0.1.0a7/tests/unit/reporting/test_identity.py +70 -0
  60. wuf-0.1.0a7/tests/unit/reporting/test_summary.py +240 -0
  61. wuf-0.1.0a7/tests/unit/reporting/test_terminal.py +1071 -0
  62. wuf-0.1.0a7/wuf/__init__.py +10 -0
  63. wuf-0.1.0a7/wuf/__main__.py +5 -0
  64. wuf-0.1.0a7/wuf/application/__init__.py +1 -0
  65. wuf-0.1.0a7/wuf/application/collection.py +132 -0
  66. wuf-0.1.0a7/wuf/application/collection_plan.py +56 -0
  67. wuf-0.1.0a7/wuf/application/coverage/__init__.py +5 -0
  68. wuf-0.1.0a7/wuf/application/coverage/reconciliation.py +23 -0
  69. wuf-0.1.0a7/wuf/application/errors.py +10 -0
  70. wuf-0.1.0a7/wuf/application/execution.py +128 -0
  71. wuf-0.1.0a7/wuf/application/report.py +381 -0
  72. wuf-0.1.0a7/wuf/application/report_context.py +156 -0
  73. wuf-0.1.0a7/wuf/application/resolution.py +93 -0
  74. wuf-0.1.0a7/wuf/cli/__init__.py +21 -0
  75. wuf-0.1.0a7/wuf/cli/config_display.py +208 -0
  76. wuf-0.1.0a7/wuf/cli/main.py +131 -0
  77. wuf-0.1.0a7/wuf/cli/parser.py +333 -0
  78. wuf-0.1.0a7/wuf/cli/runner.py +154 -0
  79. wuf-0.1.0a7/wuf/collection/__init__.py +1 -0
  80. wuf-0.1.0a7/wuf/collection/diagnostics.py +259 -0
  81. wuf-0.1.0a7/wuf/collection/filesystem/__init__.py +24 -0
  82. wuf-0.1.0a7/wuf/collection/filesystem/accounting.py +170 -0
  83. wuf-0.1.0a7/wuf/collection/filesystem/collector.py +276 -0
  84. wuf-0.1.0a7/wuf/collection/filesystem/entries.py +216 -0
  85. wuf-0.1.0a7/wuf/collection/filesystem/git_inventory.py +277 -0
  86. wuf-0.1.0a7/wuf/collection/filesystem/ignore/__init__.py +50 -0
  87. wuf-0.1.0a7/wuf/collection/filesystem/ignore/exclusions.py +79 -0
  88. wuf-0.1.0a7/wuf/collection/filesystem/ignore/inventory.py +149 -0
  89. wuf-0.1.0a7/wuf/collection/filesystem/ignore/inventory_format.py +190 -0
  90. wuf-0.1.0a7/wuf/collection/filesystem/ignore/inventory_spool.py +280 -0
  91. wuf-0.1.0a7/wuf/collection/filesystem/ignore/models.py +146 -0
  92. wuf-0.1.0a7/wuf/collection/filesystem/ignore/paths.py +192 -0
  93. wuf-0.1.0a7/wuf/collection/filesystem/ignore/runner.py +256 -0
  94. wuf-0.1.0a7/wuf/collection/filesystem/ignore/service.py +381 -0
  95. wuf-0.1.0a7/wuf/collection/filesystem/inventory_metadata.py +262 -0
  96. wuf-0.1.0a7/wuf/collection/filesystem/linux.py +257 -0
  97. wuf-0.1.0a7/wuf/collection/filesystem/metadata.py +472 -0
  98. wuf-0.1.0a7/wuf/collection/filesystem/models.py +254 -0
  99. wuf-0.1.0a7/wuf/collection/filesystem/root.py +358 -0
  100. wuf-0.1.0a7/wuf/collection/filesystem/root_schedule.py +116 -0
  101. wuf-0.1.0a7/wuf/collection/filesystem/scan.py +192 -0
  102. wuf-0.1.0a7/wuf/collection/filesystem/timestamps.py +81 -0
  103. wuf-0.1.0a7/wuf/collection/filesystem/traversal.py +345 -0
  104. wuf-0.1.0a7/wuf/collection/git/__init__.py +46 -0
  105. wuf-0.1.0a7/wuf/collection/git/batches.py +22 -0
  106. wuf-0.1.0a7/wuf/collection/git/changes/__init__.py +23 -0
  107. wuf-0.1.0a7/wuf/collection/git/changes/collector.py +365 -0
  108. wuf-0.1.0a7/wuf/collection/git/changes/diff.py +244 -0
  109. wuf-0.1.0a7/wuf/collection/git/changes/models.py +184 -0
  110. wuf-0.1.0a7/wuf/collection/git/changes/spool.py +166 -0
  111. wuf-0.1.0a7/wuf/collection/git/command_error.py +44 -0
  112. wuf-0.1.0a7/wuf/collection/git/commits/__init__.py +21 -0
  113. wuf-0.1.0a7/wuf/collection/git/commits/collector.py +105 -0
  114. wuf-0.1.0a7/wuf/collection/git/commits/models.py +240 -0
  115. wuf-0.1.0a7/wuf/collection/git/commits/repository.py +386 -0
  116. wuf-0.1.0a7/wuf/collection/git/coverage.py +246 -0
  117. wuf-0.1.0a7/wuf/collection/git/evidence/__init__.py +30 -0
  118. wuf-0.1.0a7/wuf/collection/git/evidence/collector.py +249 -0
  119. wuf-0.1.0a7/wuf/collection/git/evidence/models.py +148 -0
  120. wuf-0.1.0a7/wuf/collection/git/evidence/summary.py +127 -0
  121. wuf-0.1.0a7/wuf/collection/git/objects/__init__.py +56 -0
  122. wuf-0.1.0a7/wuf/collection/git/objects/cat_file.py +241 -0
  123. wuf-0.1.0a7/wuf/collection/git/objects/commit_parser.py +110 -0
  124. wuf-0.1.0a7/wuf/collection/git/objects/compact.py +234 -0
  125. wuf-0.1.0a7/wuf/collection/git/objects/models.py +193 -0
  126. wuf-0.1.0a7/wuf/collection/git/objects/reader.py +219 -0
  127. wuf-0.1.0a7/wuf/collection/git/objects/revision_scan.py +57 -0
  128. wuf-0.1.0a7/wuf/collection/git/objects/signatures.py +131 -0
  129. wuf-0.1.0a7/wuf/collection/git/process.py +242 -0
  130. wuf-0.1.0a7/wuf/collection/git/reflogs/__init__.py +41 -0
  131. wuf-0.1.0a7/wuf/collection/git/reflogs/collector.py +326 -0
  132. wuf-0.1.0a7/wuf/collection/git/reflogs/models.py +174 -0
  133. wuf-0.1.0a7/wuf/collection/git/reflogs/parser.py +248 -0
  134. wuf-0.1.0a7/wuf/collection/git/reflogs/reader.py +106 -0
  135. wuf-0.1.0a7/wuf/collection/git/reflogs/spill.py +188 -0
  136. wuf-0.1.0a7/wuf/collection/git/repository.py +198 -0
  137. wuf-0.1.0a7/wuf/collection/git/revisions.py +181 -0
  138. wuf-0.1.0a7/wuf/collection/git/runner.py +277 -0
  139. wuf-0.1.0a7/wuf/collection/git/selection.py +78 -0
  140. wuf-0.1.0a7/wuf/collection/git/tags/__init__.py +23 -0
  141. wuf-0.1.0a7/wuf/collection/git/tags/collector.py +242 -0
  142. wuf-0.1.0a7/wuf/collection/git/tags/models.py +188 -0
  143. wuf-0.1.0a7/wuf/collection/git/tags/parser.py +129 -0
  144. wuf-0.1.0a7/wuf/collection/git/tags/reader.py +271 -0
  145. wuf-0.1.0a7/wuf/collection/process.py +49 -0
  146. wuf-0.1.0a7/wuf/configuration/__init__.py +100 -0
  147. wuf-0.1.0a7/wuf/configuration/effective.py +330 -0
  148. wuf-0.1.0a7/wuf/configuration/files.py +362 -0
  149. wuf-0.1.0a7/wuf/configuration/layers.py +78 -0
  150. wuf-0.1.0a7/wuf/configuration/local_timezone.py +74 -0
  151. wuf-0.1.0a7/wuf/configuration/options.py +236 -0
  152. wuf-0.1.0a7/wuf/configuration/parsing.py +304 -0
  153. wuf-0.1.0a7/wuf/configuration/profiles.py +50 -0
  154. wuf-0.1.0a7/wuf/configuration/resolution.py +286 -0
  155. wuf-0.1.0a7/wuf/configuration/schema.py +106 -0
  156. wuf-0.1.0a7/wuf/configuration/styles.py +243 -0
  157. wuf-0.1.0a7/wuf/domain/__init__.py +5 -0
  158. wuf-0.1.0a7/wuf/domain/coverage/__init__.py +41 -0
  159. wuf-0.1.0a7/wuf/domain/coverage/builder.py +229 -0
  160. wuf-0.1.0a7/wuf/domain/coverage/models.py +468 -0
  161. wuf-0.1.0a7/wuf/domain/evidence.py +318 -0
  162. wuf-0.1.0a7/wuf/domain/identity.py +65 -0
  163. wuf-0.1.0a7/wuf/domain/observations.py +388 -0
  164. wuf-0.1.0a7/wuf/domain/provenance.py +259 -0
  165. wuf-0.1.0a7/wuf/domain/schedule.py +270 -0
  166. wuf-0.1.0a7/wuf/domain/scope.py +103 -0
  167. wuf-0.1.0a7/wuf/domain/time.py +268 -0
  168. wuf-0.1.0a7/wuf/folding/__init__.py +30 -0
  169. wuf-0.1.0a7/wuf/folding/bands.py +42 -0
  170. wuf-0.1.0a7/wuf/folding/builder.py +488 -0
  171. wuf-0.1.0a7/wuf/folding/layout.py +239 -0
  172. wuf-0.1.0a7/wuf/folding/markers.py +177 -0
  173. wuf-0.1.0a7/wuf/folding/models.py +284 -0
  174. wuf-0.1.0a7/wuf/folding/pipeline.py +140 -0
  175. wuf-0.1.0a7/wuf/folding/spill.py +340 -0
  176. wuf-0.1.0a7/wuf/py.typed +1 -0
  177. wuf-0.1.0a7/wuf/reporting/__init__.py +1 -0
  178. wuf-0.1.0a7/wuf/reporting/sanitization.py +131 -0
  179. wuf-0.1.0a7/wuf/reporting/terminal/__init__.py +6 -0
  180. wuf-0.1.0a7/wuf/reporting/terminal/chart.py +396 -0
  181. wuf-0.1.0a7/wuf/reporting/terminal/chart_time.py +138 -0
  182. wuf-0.1.0a7/wuf/reporting/terminal/coverage_details.py +206 -0
  183. wuf-0.1.0a7/wuf/reporting/terminal/coverage_scope.py +45 -0
  184. wuf-0.1.0a7/wuf/reporting/terminal/coverage_status.py +82 -0
  185. wuf-0.1.0a7/wuf/reporting/terminal/event_list.py +150 -0
  186. wuf-0.1.0a7/wuf/reporting/terminal/identity.py +92 -0
  187. wuf-0.1.0a7/wuf/reporting/terminal/layout.py +144 -0
  188. wuf-0.1.0a7/wuf/reporting/terminal/legend.py +283 -0
  189. wuf-0.1.0a7/wuf/reporting/terminal/markers.py +74 -0
  190. wuf-0.1.0a7/wuf/reporting/terminal/options.py +56 -0
  191. wuf-0.1.0a7/wuf/reporting/terminal/renderer.py +78 -0
  192. wuf-0.1.0a7/wuf/reporting/terminal/scope_details.py +74 -0
  193. wuf-0.1.0a7/wuf/reporting/terminal/summary.py +150 -0
  194. wuf-0.1.0a7/wuf.egg-info/PKG-INFO +214 -0
  195. wuf-0.1.0a7/wuf.egg-info/SOURCES.txt +197 -0
  196. wuf-0.1.0a7/wuf.egg-info/dependency_links.txt +1 -0
  197. wuf-0.1.0a7/wuf.egg-info/entry_points.txt +3 -0
  198. wuf-0.1.0a7/wuf.egg-info/requires.txt +21 -0
  199. wuf-0.1.0a7/wuf.egg-info/top_level.txt +1 -0
@@ -0,0 +1,123 @@
1
+ """Synchronize Wuf's release version into committed release metadata."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import re
7
+ import sys
8
+ import tempfile
9
+ import tomllib
10
+ from pathlib import Path
11
+
12
+ from packaging.version import InvalidVersion, Version
13
+
14
+ _VERSION_LINE = re.compile(r'version = "[^"]+"(?P<newline>\r?\n)?\Z')
15
+ _COMPATIBILITY_DEPENDENCIES_LINE = re.compile(
16
+ r'^dependencies = \["wuf[^"]*"\](?P<newline>\r?\n)?$',
17
+ re.MULTILINE,
18
+ )
19
+
20
+
21
+ def synchronize_version(repository: Path, requested_version: str) -> None:
22
+ """Update the editable lock entry and Workfold compatibility requirement."""
23
+
24
+ try:
25
+ normalized = str(Version(requested_version))
26
+ except InvalidVersion as error:
27
+ raise ValueError(f"invalid release version: {requested_version!r}") from error
28
+
29
+ pyproject_path = repository / "pyproject.toml"
30
+ lock_path = repository / "uv.lock"
31
+ project = tomllib.loads(pyproject_path.read_text(encoding="utf-8"))
32
+ configured = str(project["project"]["version"])
33
+ if Version(configured) != Version(normalized):
34
+ raise ValueError(f"release version {normalized!r} does not match pyproject.toml version {configured!r}")
35
+
36
+ _synchronize_compatibility_requirement(repository, normalized)
37
+
38
+ lines = lock_path.read_text(encoding="utf-8").splitlines(keepends=True)
39
+ matching_blocks = _editable_project_blocks(lines)
40
+ if len(matching_blocks) != 1:
41
+ raise ValueError(f"expected one editable wuf package in uv.lock, found {len(matching_blocks)}")
42
+ start, end = matching_blocks[0]
43
+ version_indexes = tuple(index for index in range(start, end) if _VERSION_LINE.fullmatch(lines[index]) is not None)
44
+ if len(version_indexes) != 1:
45
+ raise ValueError("editable wuf package must contain exactly one version in uv.lock")
46
+
47
+ index = version_indexes[0]
48
+ newline = "\r\n" if lines[index].endswith("\r\n") else "\n"
49
+ replacement = f'version = "{normalized}"{newline}'
50
+ if lines[index] != replacement:
51
+ lines[index] = replacement
52
+ _atomic_write(lock_path, "".join(lines))
53
+
54
+ locked = tomllib.loads(lock_path.read_text(encoding="utf-8"))
55
+ packages = tuple(
56
+ package
57
+ for package in locked.get("package", ())
58
+ if package.get("name") == "wuf" and package.get("source") == {"editable": "."}
59
+ )
60
+ if len(packages) != 1 or Version(str(packages[0].get("version"))) != Version(normalized):
61
+ raise RuntimeError("uv.lock version synchronization did not validate")
62
+
63
+
64
+ def _synchronize_compatibility_requirement(repository: Path, normalized: str) -> None:
65
+ compatibility_path = repository / "compat" / "workfold" / "pyproject.toml"
66
+ content = compatibility_path.read_text(encoding="utf-8")
67
+ project = tomllib.loads(content)
68
+ configured = str(project["project"]["version"])
69
+ if Version(configured) != Version(normalized):
70
+ raise ValueError(f"release version {normalized!r} does not match compatibility package version {configured!r}")
71
+
72
+ matches = tuple(_COMPATIBILITY_DEPENDENCIES_LINE.finditer(content))
73
+ if len(matches) != 1:
74
+ raise ValueError(f"expected one Wuf compatibility dependency, found {len(matches)}")
75
+ match = matches[0]
76
+ newline = match.group("newline") or ""
77
+ replacement = f'dependencies = ["wuf=={normalized}"]{newline}'
78
+ if match.group(0) != replacement:
79
+ content = content[: match.start()] + replacement + content[match.end() :]
80
+ _atomic_write(compatibility_path, content)
81
+
82
+ synchronized = tomllib.loads(compatibility_path.read_text(encoding="utf-8"))
83
+ dependencies = synchronized["project"].get("dependencies", ())
84
+ if dependencies != [f"wuf=={normalized}"]:
85
+ raise RuntimeError("Workfold compatibility dependency synchronization did not validate")
86
+
87
+
88
+ def _editable_project_blocks(lines: list[str]) -> tuple[tuple[int, int], ...]:
89
+ starts = [index for index, line in enumerate(lines) if line.rstrip("\r\n") == "[[package]]"]
90
+ blocks: list[tuple[int, int]] = []
91
+ for ordinal, start in enumerate(starts):
92
+ end = starts[ordinal + 1] if ordinal + 1 < len(starts) else len(lines)
93
+ content = {line.rstrip("\r\n") for line in lines[start:end]}
94
+ if 'name = "wuf"' in content and 'source = { editable = "." }' in content:
95
+ blocks.append((start, end))
96
+ return tuple(blocks)
97
+
98
+
99
+ def _atomic_write(path: Path, content: str) -> None:
100
+ mode = path.stat().st_mode
101
+ descriptor, temporary_name = tempfile.mkstemp(prefix=f".{path.name}.", dir=path.parent)
102
+ temporary = Path(temporary_name)
103
+ try:
104
+ with os.fdopen(descriptor, "w", encoding="utf-8", newline="") as stream:
105
+ stream.write(content)
106
+ stream.flush()
107
+ os.fsync(stream.fileno())
108
+ os.chmod(temporary, mode)
109
+ os.replace(temporary, path)
110
+ finally:
111
+ temporary.unlink(missing_ok=True)
112
+
113
+
114
+ def main(argv: list[str] | None = None) -> int:
115
+ arguments = sys.argv[1:] if argv is None else argv
116
+ if len(arguments) != 1:
117
+ raise SystemExit("usage: sync_lock_version.py VERSION")
118
+ synchronize_version(Path(__file__).resolve().parents[2], arguments[0])
119
+ return 0
120
+
121
+
122
+ if __name__ == "__main__":
123
+ raise SystemExit(main())
wuf-0.1.0a7/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jan T. Müller
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,4 @@
1
+ recursive-include tests *.py
2
+ recursive-include benchmarks *.py *.md
3
+ include .github/scripts/sync_lock_version.py
4
+ include THIRD_PARTY_NOTICES.md
wuf-0.1.0a7/PKG-INFO ADDED
@@ -0,0 +1,214 @@
1
+ Metadata-Version: 2.4
2
+ Name: wuf
3
+ Version: 0.1.0a7
4
+ Summary: Unify local Git and filesystem timestamp footprints in a representative weekly activity view.
5
+ Author-email: "Jan T. Müller" <mail@jantmueller.com>
6
+ License-Expression: MIT
7
+ Project-URL: homepage, https://github.com/janthmueller/wuf
8
+ Project-URL: documentation, https://janthmueller.github.io/wuf/
9
+ Project-URL: source, https://github.com/janthmueller/wuf
10
+ Project-URL: tracker, https://github.com/janthmueller/wuf/issues
11
+ Classifier: Development Status :: 2 - Pre-Alpha
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Requires-Python: >=3.11
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: pathspec<2,>=1
24
+ Requires-Dist: rich<15,>=14.3
25
+ Requires-Dist: tzdata>=2025.2; sys_platform == "win32"
26
+ Requires-Dist: tzlocal<6,>=5
27
+ Provides-Extra: audit
28
+ Requires-Dist: pip-audit<3,>=2.9; extra == "audit"
29
+ Provides-Extra: dev
30
+ Requires-Dist: pyright>=1.1; extra == "dev"
31
+ Requires-Dist: pytest>=8; extra == "dev"
32
+ Requires-Dist: pytest-cov>=6; extra == "dev"
33
+ Requires-Dist: ruff>=0.8; extra == "dev"
34
+ Provides-Extra: release
35
+ Requires-Dist: build>=1; extra == "release"
36
+ Requires-Dist: pyinstaller>=6; extra == "release"
37
+ Requires-Dist: python-semantic-release<11,>=10; extra == "release"
38
+ Requires-Dist: twine>=5; extra == "release"
39
+ Dynamic: license-file
40
+
41
+ # Wuf
42
+
43
+ [![PyPI Latest Release](https://img.shields.io/pypi/v/wuf.svg)](https://pypi.org/project/wuf/)
44
+ [![Pepy Total Downloads](https://img.shields.io/pepy/dt/wuf)](https://pepy.tech/project/wuf)
45
+ [![GitHub License](https://img.shields.io/github/license/janthmueller/wuf)](https://github.com/janthmueller/wuf/blob/main/LICENSE)
46
+
47
+ **WUF Unifies Footprints.**
48
+
49
+ Wuf folds local Git and filesystem timestamp footprints onto one representative
50
+ Monday-to-Sunday week. It highlights activity outside your intended working
51
+ hours without pretending that timestamped events are hours worked.
52
+
53
+ ![Wuf terminal output](https://raw.githubusercontent.com/janthmueller/wuf/main/docs/public/wuf-output.svg)
54
+
55
+ Wuf is local, private by design, terminal-only, and currently alpha.
56
+
57
+ ## Install
58
+
59
+ ```bash
60
+ pip install wuf
61
+ wuf --help
62
+ ```
63
+
64
+ For an isolated installation, use `uv tool install wuf` or
65
+ `pipx install wuf`. Standalone binaries are available from
66
+ [GitHub releases](https://github.com/janthmueller/wuf/releases).
67
+
68
+ ### Renamed from Workfold
69
+
70
+ Wuf is the continuation of Workfold under its shorter, permanent name. Wuf
71
+ packages also install `workfold` as a compatibility command, but `wuf` is the
72
+ canonical command going forward. Existing configuration should be renamed from
73
+ `workfold.toml` to `wuf.toml`, and `[tool.workfold]` tables should become
74
+ `[tool.wuf]`.
75
+
76
+ ## Quick start
77
+
78
+ Run `wuf` inside a Git repository to see the current ISO week:
79
+
80
+ ```bash
81
+ wuf
82
+ ```
83
+
84
+ The selectors you will use most are:
85
+
86
+ ```bash
87
+ wuf . -t 2w3d # rolling elapsed window
88
+ wuf . -t 2026-W31 # one ISO week
89
+ wuf . -p fs # current filesystem metadata
90
+ wuf . -p both # low-noise Git + filesystem view
91
+ wuf . -p portable -t all # evidence stored in Git objects
92
+ wuf . -p full -t all --git-commits-from all-refs --include-ignored
93
+ wuf . -e git:tag:tagger fs:file:modified
94
+ ```
95
+
96
+ They control separate parts of the request:
97
+
98
+ | Selector | Purpose | Values |
99
+ | --- | --- | --- |
100
+ | `-t`, `--time` | Date scope | `this-week`, `2w3d`, `YYYY-Www`, `DATE..DATE`, `all` |
101
+ | `-p`, `--profile` | Named event set | `git`, `fs`, `both`, `portable`, `full` |
102
+ | `-e`, `--events` | Exact event set (alternative to a profile) | IDs and wildcards |
103
+
104
+ The profiles answer different questions:
105
+
106
+ - `git`: What does ordinary commit activity look like? This is the default.
107
+ - `fs`: What birth and modification metadata exists for current regular files?
108
+ - `both`: What does the combined low-noise Git and filesystem pattern look like?
109
+ - `portable`: What dated evidence is stored inside Git objects?
110
+ - `full`: Which timestamps exist across every supported Git and filesystem event kind?
111
+
112
+ Profiles expand only to event sets. Time, Git reachability, ignored files, and
113
+ explicit exclusions remain independently configurable.
114
+
115
+ For exact control, `-e/--events` accepts identifiers such as
116
+ `git:commit:author`, `git:tag:tagger`, and `fs:file:modified`; quote wildcards
117
+ such as `'git:*'`. `-l/--list` appends bounded event details. Paths must appear
118
+ before either space-separated selector, or after an option-terminating `--`.
119
+
120
+ Schedules support daily intervals, breaks, overnight shifts, and `all`:
121
+
122
+ ```bash
123
+ wuf . --hours 'Mo-Thu 08:00-16:30; Fr 08:00-14:00'
124
+ wuf . --hours 'Mo-Fr 22:00-06:00'
125
+ wuf . --hours all
126
+ ```
127
+
128
+ See the [usage guide](https://janthmueller.github.io/wuf/guides/usage/)
129
+ for clustering, fixed bands, identity markers, day hiding, grids, exact event
130
+ selection, configuration, and every CLI option.
131
+
132
+ ## Configuration
133
+
134
+ Put personal defaults in the platform configuration directory, or project
135
+ defaults in `wuf.toml`:
136
+
137
+ ```toml
138
+ timezone = "Europe/Berlin"
139
+ hours = "Mo-Thu 08:00-16:30; Fr 08:00-14:00"
140
+ profile = "portable"
141
+ cluster-anchor = "midnight"
142
+ band-label = "start"
143
+ show-empty-bands = true
144
+ count-grouping = "visual"
145
+ grid = "vertical"
146
+ hide-empty-days = ["weekend"]
147
+
148
+ [styles."git:tag:*"]
149
+ symbol = "◆"
150
+ color = "magenta"
151
+ outside-symbol = "◇"
152
+ outside-color = "bright_red"
153
+ ```
154
+
155
+ Python projects may use `[tool.wuf]` in `pyproject.toml` instead. Values
156
+ resolve as built-in → global → nearest project → CLI. Inspect the result and
157
+ each value's origin without collecting timestamps:
158
+
159
+ ```bash
160
+ wuf . --show-config
161
+ ```
162
+
163
+ Use `--config FILE` for one exact file or `--no-config` for built-ins plus CLI
164
+ only. The [usage guide](https://janthmueller.github.io/wuf/guides/usage/#configuration-files)
165
+ documents locations, discovery, merging, and every supported key.
166
+
167
+ ## Reading the chart
168
+
169
+ - By default, circles are Git events and squares are filesystem events.
170
+ - Default green/blue filled markers are inside the schedule; red hollow markers
171
+ are outside. Colorless output preserves the shape distinction, and event
172
+ style rules may replace both symbol pairs and their colors.
173
+ - One symbol is one event; busy cells use exact `×N` counts. Counts stay
174
+ separate per event kind by default; `--count-grouping visual` merges kinds
175
+ only when their resolved symbol and configured color match.
176
+ - Empty time is compressed, and `⋮` reports a meaningful gap.
177
+ - Identity-marker mode replaces Git circles with codes mapped in the key.
178
+ - Event-selector style rules can replace source-marker symbols and colors while
179
+ keeping collection and coverage unchanged.
180
+
181
+ The summary independently splits all events by schedule and by calendar day.
182
+ Weekend events can therefore also be outside working hours.
183
+
184
+ ## Accuracy and privacy
185
+
186
+ - Events are discrete timestamp observations, not work sessions or duration.
187
+ - Collection is local: Wuf does not contact a Git host or telemetry service.
188
+ - Git history can be rewritten; reflogs can expire; filesystem metadata is a
189
+ mutable snapshot and birth time depends on platform and filesystem support.
190
+ - Coverage reports unavailable, unsupported, and unreadable evidence that can
191
+ prevent a complete answer. Known timestamps outside the requested time or
192
+ identity scope are not counted as coverage outcomes.
193
+
194
+ See the [documentation](https://janthmueller.github.io/wuf/) for every CLI
195
+ option, collector semantics, coverage guarantees, and platform notes.
196
+
197
+ ## Development
198
+
199
+ The Python package lives directly in `wuf/`; there is no `src/` wrapper.
200
+
201
+ ```bash
202
+ nix develop
203
+ uv sync --locked --extra dev
204
+ uv run --locked pytest
205
+ uv run --locked ruff check .
206
+ uv run --locked ruff format --check .
207
+ uv run --locked pyright
208
+ ```
209
+
210
+ Use `nix run .#docs-dev` for the documentation site and
211
+ `nix run .#docs-check` to validate it. The
212
+ [architecture guide](https://janthmueller.github.io/wuf/reference/architecture/)
213
+ documents the package boundaries, dependency rules, data pipeline, and test
214
+ layout.
wuf-0.1.0a7/README.md ADDED
@@ -0,0 +1,174 @@
1
+ # Wuf
2
+
3
+ [![PyPI Latest Release](https://img.shields.io/pypi/v/wuf.svg)](https://pypi.org/project/wuf/)
4
+ [![Pepy Total Downloads](https://img.shields.io/pepy/dt/wuf)](https://pepy.tech/project/wuf)
5
+ [![GitHub License](https://img.shields.io/github/license/janthmueller/wuf)](https://github.com/janthmueller/wuf/blob/main/LICENSE)
6
+
7
+ **WUF Unifies Footprints.**
8
+
9
+ Wuf folds local Git and filesystem timestamp footprints onto one representative
10
+ Monday-to-Sunday week. It highlights activity outside your intended working
11
+ hours without pretending that timestamped events are hours worked.
12
+
13
+ ![Wuf terminal output](https://raw.githubusercontent.com/janthmueller/wuf/main/docs/public/wuf-output.svg)
14
+
15
+ Wuf is local, private by design, terminal-only, and currently alpha.
16
+
17
+ ## Install
18
+
19
+ ```bash
20
+ pip install wuf
21
+ wuf --help
22
+ ```
23
+
24
+ For an isolated installation, use `uv tool install wuf` or
25
+ `pipx install wuf`. Standalone binaries are available from
26
+ [GitHub releases](https://github.com/janthmueller/wuf/releases).
27
+
28
+ ### Renamed from Workfold
29
+
30
+ Wuf is the continuation of Workfold under its shorter, permanent name. Wuf
31
+ packages also install `workfold` as a compatibility command, but `wuf` is the
32
+ canonical command going forward. Existing configuration should be renamed from
33
+ `workfold.toml` to `wuf.toml`, and `[tool.workfold]` tables should become
34
+ `[tool.wuf]`.
35
+
36
+ ## Quick start
37
+
38
+ Run `wuf` inside a Git repository to see the current ISO week:
39
+
40
+ ```bash
41
+ wuf
42
+ ```
43
+
44
+ The selectors you will use most are:
45
+
46
+ ```bash
47
+ wuf . -t 2w3d # rolling elapsed window
48
+ wuf . -t 2026-W31 # one ISO week
49
+ wuf . -p fs # current filesystem metadata
50
+ wuf . -p both # low-noise Git + filesystem view
51
+ wuf . -p portable -t all # evidence stored in Git objects
52
+ wuf . -p full -t all --git-commits-from all-refs --include-ignored
53
+ wuf . -e git:tag:tagger fs:file:modified
54
+ ```
55
+
56
+ They control separate parts of the request:
57
+
58
+ | Selector | Purpose | Values |
59
+ | --- | --- | --- |
60
+ | `-t`, `--time` | Date scope | `this-week`, `2w3d`, `YYYY-Www`, `DATE..DATE`, `all` |
61
+ | `-p`, `--profile` | Named event set | `git`, `fs`, `both`, `portable`, `full` |
62
+ | `-e`, `--events` | Exact event set (alternative to a profile) | IDs and wildcards |
63
+
64
+ The profiles answer different questions:
65
+
66
+ - `git`: What does ordinary commit activity look like? This is the default.
67
+ - `fs`: What birth and modification metadata exists for current regular files?
68
+ - `both`: What does the combined low-noise Git and filesystem pattern look like?
69
+ - `portable`: What dated evidence is stored inside Git objects?
70
+ - `full`: Which timestamps exist across every supported Git and filesystem event kind?
71
+
72
+ Profiles expand only to event sets. Time, Git reachability, ignored files, and
73
+ explicit exclusions remain independently configurable.
74
+
75
+ For exact control, `-e/--events` accepts identifiers such as
76
+ `git:commit:author`, `git:tag:tagger`, and `fs:file:modified`; quote wildcards
77
+ such as `'git:*'`. `-l/--list` appends bounded event details. Paths must appear
78
+ before either space-separated selector, or after an option-terminating `--`.
79
+
80
+ Schedules support daily intervals, breaks, overnight shifts, and `all`:
81
+
82
+ ```bash
83
+ wuf . --hours 'Mo-Thu 08:00-16:30; Fr 08:00-14:00'
84
+ wuf . --hours 'Mo-Fr 22:00-06:00'
85
+ wuf . --hours all
86
+ ```
87
+
88
+ See the [usage guide](https://janthmueller.github.io/wuf/guides/usage/)
89
+ for clustering, fixed bands, identity markers, day hiding, grids, exact event
90
+ selection, configuration, and every CLI option.
91
+
92
+ ## Configuration
93
+
94
+ Put personal defaults in the platform configuration directory, or project
95
+ defaults in `wuf.toml`:
96
+
97
+ ```toml
98
+ timezone = "Europe/Berlin"
99
+ hours = "Mo-Thu 08:00-16:30; Fr 08:00-14:00"
100
+ profile = "portable"
101
+ cluster-anchor = "midnight"
102
+ band-label = "start"
103
+ show-empty-bands = true
104
+ count-grouping = "visual"
105
+ grid = "vertical"
106
+ hide-empty-days = ["weekend"]
107
+
108
+ [styles."git:tag:*"]
109
+ symbol = "◆"
110
+ color = "magenta"
111
+ outside-symbol = "◇"
112
+ outside-color = "bright_red"
113
+ ```
114
+
115
+ Python projects may use `[tool.wuf]` in `pyproject.toml` instead. Values
116
+ resolve as built-in → global → nearest project → CLI. Inspect the result and
117
+ each value's origin without collecting timestamps:
118
+
119
+ ```bash
120
+ wuf . --show-config
121
+ ```
122
+
123
+ Use `--config FILE` for one exact file or `--no-config` for built-ins plus CLI
124
+ only. The [usage guide](https://janthmueller.github.io/wuf/guides/usage/#configuration-files)
125
+ documents locations, discovery, merging, and every supported key.
126
+
127
+ ## Reading the chart
128
+
129
+ - By default, circles are Git events and squares are filesystem events.
130
+ - Default green/blue filled markers are inside the schedule; red hollow markers
131
+ are outside. Colorless output preserves the shape distinction, and event
132
+ style rules may replace both symbol pairs and their colors.
133
+ - One symbol is one event; busy cells use exact `×N` counts. Counts stay
134
+ separate per event kind by default; `--count-grouping visual` merges kinds
135
+ only when their resolved symbol and configured color match.
136
+ - Empty time is compressed, and `⋮` reports a meaningful gap.
137
+ - Identity-marker mode replaces Git circles with codes mapped in the key.
138
+ - Event-selector style rules can replace source-marker symbols and colors while
139
+ keeping collection and coverage unchanged.
140
+
141
+ The summary independently splits all events by schedule and by calendar day.
142
+ Weekend events can therefore also be outside working hours.
143
+
144
+ ## Accuracy and privacy
145
+
146
+ - Events are discrete timestamp observations, not work sessions or duration.
147
+ - Collection is local: Wuf does not contact a Git host or telemetry service.
148
+ - Git history can be rewritten; reflogs can expire; filesystem metadata is a
149
+ mutable snapshot and birth time depends on platform and filesystem support.
150
+ - Coverage reports unavailable, unsupported, and unreadable evidence that can
151
+ prevent a complete answer. Known timestamps outside the requested time or
152
+ identity scope are not counted as coverage outcomes.
153
+
154
+ See the [documentation](https://janthmueller.github.io/wuf/) for every CLI
155
+ option, collector semantics, coverage guarantees, and platform notes.
156
+
157
+ ## Development
158
+
159
+ The Python package lives directly in `wuf/`; there is no `src/` wrapper.
160
+
161
+ ```bash
162
+ nix develop
163
+ uv sync --locked --extra dev
164
+ uv run --locked pytest
165
+ uv run --locked ruff check .
166
+ uv run --locked ruff format --check .
167
+ uv run --locked pyright
168
+ ```
169
+
170
+ Use `nix run .#docs-dev` for the documentation site and
171
+ `nix run .#docs-check` to validate it. The
172
+ [architecture guide](https://janthmueller.github.io/wuf/reference/architecture/)
173
+ documents the package boundaries, dependency rules, data pipeline, and test
174
+ layout.
@@ -0,0 +1,19 @@
1
+ # Third-party notices for standalone Wuf bundles
2
+
3
+ The standalone archives are produced with PyInstaller and include portions of
4
+ the Python runtime plus Wuf's runtime dependencies. Their exact license
5
+ files are copied into the archive's `licenses/` directory at build time.
6
+
7
+ - Python and its standard library: Python Software Foundation License.
8
+ - pathspec: Mozilla Public License 2.0.
9
+ - Rich: MIT License.
10
+ - markdown-it-py and mdurl: MIT License.
11
+ - Pygments: BSD 2-Clause License.
12
+ - tzlocal: MIT License.
13
+ - tzdata, when bundled on Windows: licenses shipped by the Python tzdata
14
+ distribution and the underlying IANA time-zone database.
15
+ - PyInstaller bootloader: GNU General Public License with PyInstaller's
16
+ bootloader exception; its shipped notices are included.
17
+
18
+ This notice is informational. The complete license texts included beside the
19
+ binary control the corresponding components.
@@ -0,0 +1,87 @@
1
+ # Wuf benchmarks
2
+
3
+ These are opt-in end-to-end measurements, not ordinary correctness tests. A
4
+ benchmark starts a complete Wuf process and includes configuration,
5
+ collection, folding, terminal rendering, and the local Git subprocesses it
6
+ uses.
7
+
8
+ ## Run a suite
9
+
10
+ From the repository root:
11
+
12
+ ```bash
13
+ uv run python -m benchmarks.run ~/linux --suite quick
14
+ uv run python -m benchmarks.run ~/linux --suite scope --json benchmarks/results/linux.json
15
+ uv run python -m benchmarks.run --fixture medium --suite complete
16
+ ```
17
+
18
+ The built-in suites are:
19
+
20
+ - `quick`: low-noise Git, filesystem, and combined current-week profiles;
21
+ - `scope`: those three profiles for both current-week and all-date selection;
22
+ - `profiles`: deliberately broad portable and full workloads with bounded and
23
+ all-date selections; Git cases use all locally stored refs and filesystem
24
+ cases include ignored entries where applicable;
25
+ - `complete`: every named case.
26
+
27
+ Repeat `--case NAME` to select an exact subset instead. Run `--help` for the
28
+ case names and process controls. `--wuf-executable PATH` measures an
29
+ installed script or release binary instead of `python -m wuf` from the
30
+ checkout.
31
+
32
+ ## Measurements
33
+
34
+ Each recorded sample includes:
35
+
36
+ - monotonic wall time;
37
+ - child user and system CPU time where the platform exposes it;
38
+ - the Wuf process's kernel-recorded high-water RSS on Linux;
39
+ - sampled peak RSS summed across the Wuf process tree on Linux;
40
+ - minor and major page faults and voluntary/involuntary context switches;
41
+ - exit status, timeout state, stdout/stderr sizes, and a stdout digest;
42
+ - the exact `Events` summary count.
43
+
44
+ Every workload adds `--strict`, so incomplete collection fails instead of
45
+ becoming a successful timing sample. The runner also rejects missing summaries,
46
+ changed event counts, or changed stdout across repetitions. Raw JSON records Python, Wuf, Git,
47
+ OS, CPU, load, total memory, Wuf checkout revision/dirty state, target
48
+ HEAD/tracked dirty state, all-ref commit count, Git object-store statistics,
49
+ commands, cache policy, and every individual sample.
50
+
51
+ Main RSS is the stable high-water mark for Wuf itself. Summed tree RSS also
52
+ captures concurrently running Git children, but it is not proportional-set
53
+ size: shared pages can appear in more than one process. Sampling defaults to 5
54
+ ms, so extremely short-lived child peaks can be missed. Non-Linux hosts report
55
+ memory as `n/a`; timing and output validation still work.
56
+
57
+ Warmups are explicit and the runner never drops the operating system's page
58
+ cache. Compare results only under the same host, target, command, sample
59
+ interval, and warmup policy. Do not use fixed cross-machine timing thresholds
60
+ in the normal test workflow. Global and system Git configuration are disabled
61
+ for repeatability; repository-local configuration still applies.
62
+
63
+ ## Synthetic fixtures
64
+
65
+ `--fixture small|medium|directory-heavy|large` creates an automatically removed repository with
66
+ historical and current-week commits, distinct author/committer identities,
67
+ annotated tags, reflogs, tracked files, untracked files, ignored files,
68
+ directories, and symlinks where supported.
69
+
70
+ The `directory-heavy` fixture places one tracked, untracked, or ignored file in
71
+ each directory. It isolates directory traversal and parent-handle validation
72
+ costs that file-dense fixtures can hide:
73
+
74
+ ```bash
75
+ uv run python -m benchmarks.run --fixture directory-heavy --case fs-all
76
+ ```
77
+
78
+ To keep a fixture for profiling tools, create one explicitly in a new or empty
79
+ directory:
80
+
81
+ ```bash
82
+ uv run python -m benchmarks.fixture /tmp/wuf-medium --size medium
83
+ uv run python -m benchmarks.run /tmp/wuf-medium --suite complete
84
+ ```
85
+
86
+ Fixture generation refuses non-empty targets and never deletes an existing
87
+ repository.
@@ -0,0 +1 @@
1
+ """Opt-in end-to-end performance benchmarks for Wuf."""