elspais 0.11.1__py3-none-any.whl → 0.43.5__py3-none-any.whl
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.
- elspais/__init__.py +2 -11
- elspais/{sponsors/__init__.py → associates.py} +102 -58
- elspais/cli.py +395 -79
- elspais/commands/__init__.py +9 -3
- elspais/commands/analyze.py +121 -173
- elspais/commands/changed.py +15 -30
- elspais/commands/config_cmd.py +13 -16
- elspais/commands/edit.py +60 -44
- elspais/commands/example_cmd.py +319 -0
- elspais/commands/hash_cmd.py +167 -183
- elspais/commands/health.py +1177 -0
- elspais/commands/index.py +98 -114
- elspais/commands/init.py +103 -26
- elspais/commands/reformat_cmd.py +41 -444
- elspais/commands/rules_cmd.py +7 -3
- elspais/commands/trace.py +444 -321
- elspais/commands/validate.py +195 -415
- elspais/config/__init__.py +799 -5
- elspais/{core/content_rules.py → content_rules.py} +20 -3
- elspais/docs/cli/assertions.md +67 -0
- elspais/docs/cli/commands.md +304 -0
- elspais/docs/cli/config.md +262 -0
- elspais/docs/cli/format.md +66 -0
- elspais/docs/cli/git.md +45 -0
- elspais/docs/cli/health.md +190 -0
- elspais/docs/cli/hierarchy.md +60 -0
- elspais/docs/cli/ignore.md +72 -0
- elspais/docs/cli/mcp.md +245 -0
- elspais/docs/cli/quickstart.md +58 -0
- elspais/docs/cli/traceability.md +89 -0
- elspais/docs/cli/validation.md +96 -0
- elspais/graph/GraphNode.py +383 -0
- elspais/graph/__init__.py +40 -0
- elspais/graph/annotators.py +927 -0
- elspais/graph/builder.py +1886 -0
- elspais/graph/deserializer.py +248 -0
- elspais/graph/factory.py +284 -0
- elspais/graph/metrics.py +127 -0
- elspais/graph/mutations.py +161 -0
- elspais/graph/parsers/__init__.py +156 -0
- elspais/graph/parsers/code.py +213 -0
- elspais/graph/parsers/comments.py +112 -0
- elspais/graph/parsers/config_helpers.py +29 -0
- elspais/graph/parsers/heredocs.py +225 -0
- elspais/graph/parsers/journey.py +131 -0
- elspais/graph/parsers/remainder.py +79 -0
- elspais/graph/parsers/requirement.py +347 -0
- elspais/graph/parsers/results/__init__.py +6 -0
- elspais/graph/parsers/results/junit_xml.py +229 -0
- elspais/graph/parsers/results/pytest_json.py +313 -0
- elspais/graph/parsers/test.py +305 -0
- elspais/graph/relations.py +78 -0
- elspais/graph/serialize.py +216 -0
- elspais/html/__init__.py +8 -0
- elspais/html/generator.py +731 -0
- elspais/html/templates/trace_view.html.j2 +2151 -0
- elspais/mcp/__init__.py +47 -29
- elspais/mcp/__main__.py +5 -1
- elspais/mcp/file_mutations.py +138 -0
- elspais/mcp/server.py +2016 -247
- elspais/testing/__init__.py +4 -4
- elspais/testing/config.py +3 -0
- elspais/testing/mapper.py +1 -1
- elspais/testing/result_parser.py +25 -21
- elspais/testing/scanner.py +301 -12
- elspais/utilities/__init__.py +1 -0
- elspais/utilities/docs_loader.py +115 -0
- elspais/utilities/git.py +607 -0
- elspais/{core → utilities}/hasher.py +8 -22
- elspais/utilities/md_renderer.py +189 -0
- elspais/{core → utilities}/patterns.py +58 -57
- elspais/utilities/reference_config.py +626 -0
- elspais/validation/__init__.py +19 -0
- elspais/validation/format.py +264 -0
- {elspais-0.11.1.dist-info → elspais-0.43.5.dist-info}/METADATA +7 -4
- elspais-0.43.5.dist-info/RECORD +80 -0
- elspais/config/defaults.py +0 -173
- elspais/config/loader.py +0 -494
- elspais/core/__init__.py +0 -21
- elspais/core/git.py +0 -352
- elspais/core/models.py +0 -320
- elspais/core/parser.py +0 -640
- elspais/core/rules.py +0 -514
- elspais/mcp/context.py +0 -171
- elspais/mcp/serializers.py +0 -112
- elspais/reformat/__init__.py +0 -50
- elspais/reformat/detector.py +0 -119
- elspais/reformat/hierarchy.py +0 -246
- elspais/reformat/line_breaks.py +0 -220
- elspais/reformat/prompts.py +0 -123
- elspais/reformat/transformer.py +0 -264
- elspais/trace_view/__init__.py +0 -54
- elspais/trace_view/coverage.py +0 -183
- elspais/trace_view/generators/__init__.py +0 -12
- elspais/trace_view/generators/base.py +0 -329
- elspais/trace_view/generators/csv.py +0 -122
- elspais/trace_view/generators/markdown.py +0 -175
- elspais/trace_view/html/__init__.py +0 -31
- elspais/trace_view/html/generator.py +0 -1006
- elspais/trace_view/html/templates/base.html +0 -283
- elspais/trace_view/html/templates/components/code_viewer_modal.html +0 -14
- elspais/trace_view/html/templates/components/file_picker_modal.html +0 -20
- elspais/trace_view/html/templates/components/legend_modal.html +0 -69
- elspais/trace_view/html/templates/components/review_panel.html +0 -118
- elspais/trace_view/html/templates/partials/review/help/help-panel.json +0 -244
- elspais/trace_view/html/templates/partials/review/help/onboarding.json +0 -77
- elspais/trace_view/html/templates/partials/review/help/tooltips.json +0 -237
- elspais/trace_view/html/templates/partials/review/review-comments.js +0 -928
- elspais/trace_view/html/templates/partials/review/review-data.js +0 -961
- elspais/trace_view/html/templates/partials/review/review-help.js +0 -679
- elspais/trace_view/html/templates/partials/review/review-init.js +0 -177
- elspais/trace_view/html/templates/partials/review/review-line-numbers.js +0 -429
- elspais/trace_view/html/templates/partials/review/review-packages.js +0 -1029
- elspais/trace_view/html/templates/partials/review/review-position.js +0 -540
- elspais/trace_view/html/templates/partials/review/review-resize.js +0 -115
- elspais/trace_view/html/templates/partials/review/review-status.js +0 -659
- elspais/trace_view/html/templates/partials/review/review-sync.js +0 -992
- elspais/trace_view/html/templates/partials/review-styles.css +0 -2238
- elspais/trace_view/html/templates/partials/scripts.js +0 -1741
- elspais/trace_view/html/templates/partials/styles.css +0 -1756
- elspais/trace_view/models.py +0 -353
- elspais/trace_view/review/__init__.py +0 -60
- elspais/trace_view/review/branches.py +0 -1149
- elspais/trace_view/review/models.py +0 -1205
- elspais/trace_view/review/position.py +0 -609
- elspais/trace_view/review/server.py +0 -1056
- elspais/trace_view/review/status.py +0 -470
- elspais/trace_view/review/storage.py +0 -1367
- elspais/trace_view/scanning.py +0 -213
- elspais/trace_view/specs/README.md +0 -84
- elspais/trace_view/specs/tv-d00001-template-architecture.md +0 -36
- elspais/trace_view/specs/tv-d00002-css-extraction.md +0 -37
- elspais/trace_view/specs/tv-d00003-js-extraction.md +0 -43
- elspais/trace_view/specs/tv-d00004-build-embedding.md +0 -40
- elspais/trace_view/specs/tv-d00005-test-format.md +0 -78
- elspais/trace_view/specs/tv-d00010-review-data-models.md +0 -33
- elspais/trace_view/specs/tv-d00011-review-storage.md +0 -33
- elspais/trace_view/specs/tv-d00012-position-resolution.md +0 -33
- elspais/trace_view/specs/tv-d00013-git-branches.md +0 -31
- elspais/trace_view/specs/tv-d00014-review-api-server.md +0 -31
- elspais/trace_view/specs/tv-d00015-status-modifier.md +0 -27
- elspais/trace_view/specs/tv-d00016-js-integration.md +0 -33
- elspais/trace_view/specs/tv-p00001-html-generator.md +0 -33
- elspais/trace_view/specs/tv-p00002-review-system.md +0 -29
- elspais-0.11.1.dist-info/RECORD +0 -101
- {elspais-0.11.1.dist-info → elspais-0.43.5.dist-info}/WHEEL +0 -0
- {elspais-0.11.1.dist-info → elspais-0.43.5.dist-info}/entry_points.txt +0 -0
- {elspais-0.11.1.dist-info → elspais-0.43.5.dist-info}/licenses/LICENSE +0 -0
elspais/commands/hash_cmd.py
CHANGED
|
@@ -1,220 +1,204 @@
|
|
|
1
|
+
# Implements: REQ-int-d00003 (CLI Extension)
|
|
1
2
|
"""
|
|
2
|
-
elspais.commands.hash_cmd -
|
|
3
|
+
elspais.commands.hash_cmd - Manage requirement hashes.
|
|
3
4
|
|
|
4
|
-
|
|
5
|
+
Uses the graph-based system for hash verification and updates.
|
|
5
6
|
"""
|
|
6
7
|
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
7
10
|
import argparse
|
|
8
11
|
import sys
|
|
9
|
-
from pathlib import Path
|
|
10
12
|
|
|
11
|
-
from elspais.
|
|
12
|
-
from elspais.config.loader import find_config_file, get_spec_directories, load_config
|
|
13
|
-
from elspais.core.hasher import calculate_hash, verify_hash
|
|
14
|
-
from elspais.core.models import Requirement
|
|
15
|
-
from elspais.core.parser import RequirementParser
|
|
16
|
-
from elspais.core.patterns import PatternConfig
|
|
13
|
+
from elspais.graph import NodeKind
|
|
17
14
|
|
|
18
15
|
|
|
19
16
|
def run(args: argparse.Namespace) -> int:
|
|
20
|
-
"""Run the hash command.
|
|
21
|
-
if not args.hash_action:
|
|
22
|
-
print("Usage: elspais hash {verify|update}")
|
|
23
|
-
return 1
|
|
24
|
-
|
|
25
|
-
if args.hash_action == "verify":
|
|
26
|
-
return run_verify(args)
|
|
27
|
-
elif args.hash_action == "update":
|
|
28
|
-
return run_update(args)
|
|
29
|
-
|
|
30
|
-
return 1
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def run_verify(args: argparse.Namespace) -> int:
|
|
34
|
-
"""Verify all requirement hashes."""
|
|
35
|
-
config, requirements = load_requirements(args)
|
|
36
|
-
if not requirements:
|
|
37
|
-
return 1
|
|
38
|
-
|
|
39
|
-
hash_length = config.get("validation", {}).get("hash_length", 8)
|
|
40
|
-
algorithm = config.get("validation", {}).get("hash_algorithm", "sha256")
|
|
41
|
-
|
|
42
|
-
mismatches = []
|
|
43
|
-
missing = []
|
|
17
|
+
"""Run the hash command.
|
|
44
18
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
if not verify_hash(req.body, req.hash, length=hash_length, algorithm=algorithm):
|
|
51
|
-
mismatches.append((req_id, req.hash, expected))
|
|
19
|
+
Subcommands:
|
|
20
|
+
- verify: Check hashes match content
|
|
21
|
+
- update: Recalculate and update hashes
|
|
22
|
+
"""
|
|
23
|
+
from elspais.graph.factory import build_graph
|
|
52
24
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
print(f"Missing hashes: {len(missing)}")
|
|
56
|
-
for req_id in missing:
|
|
57
|
-
print(f" - {req_id}")
|
|
58
|
-
|
|
59
|
-
if mismatches:
|
|
60
|
-
print(f"\nHash mismatches: {len(mismatches)}")
|
|
61
|
-
for req_id, current, expected in mismatches:
|
|
62
|
-
print(f" - {req_id}: {current} (expected: {expected})")
|
|
63
|
-
|
|
64
|
-
if not missing and not mismatches:
|
|
65
|
-
print(f"✓ All {len(requirements)} hashes verified")
|
|
66
|
-
return 0
|
|
25
|
+
spec_dir = getattr(args, "spec_dir", None)
|
|
26
|
+
config_path = getattr(args, "config", None)
|
|
67
27
|
|
|
68
|
-
|
|
28
|
+
graph = build_graph(
|
|
29
|
+
spec_dirs=[spec_dir] if spec_dir else None,
|
|
30
|
+
config_path=config_path,
|
|
31
|
+
)
|
|
69
32
|
|
|
33
|
+
action = getattr(args, "hash_action", None)
|
|
70
34
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
35
|
+
if action == "verify":
|
|
36
|
+
return _verify_hashes(graph, args)
|
|
37
|
+
elif action == "update":
|
|
38
|
+
return _update_hashes(graph, args)
|
|
39
|
+
else:
|
|
40
|
+
print("Usage: elspais hash <verify|update>", file=sys.stderr)
|
|
75
41
|
return 1
|
|
76
42
|
|
|
77
|
-
hash_length = config.get("validation", {}).get("hash_length", 8)
|
|
78
|
-
algorithm = config.get("validation", {}).get("hash_algorithm", "sha256")
|
|
79
43
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
if args.req_id not in requirements:
|
|
83
|
-
print(f"Requirement not found: {args.req_id}")
|
|
84
|
-
return 1
|
|
85
|
-
requirements = {args.req_id: requirements[args.req_id]}
|
|
44
|
+
def _get_requirement_body(node) -> str:
|
|
45
|
+
"""Extract hashable body content from a requirement node.
|
|
86
46
|
|
|
87
|
-
|
|
47
|
+
Per spec/requirements-spec.md:
|
|
48
|
+
> The hash SHALL be calculated from:
|
|
49
|
+
> - every line AFTER the Header line
|
|
50
|
+
> - every line BEFORE the Footer line
|
|
88
51
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
updates.append((req_id, req, expected))
|
|
52
|
+
The body_text is extracted during parsing and stored in the node.
|
|
53
|
+
This includes metadata, intro text, assertions - everything between
|
|
54
|
+
the header and footer markers.
|
|
93
55
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
return 0
|
|
56
|
+
Args:
|
|
57
|
+
node: The requirement GraphNode.
|
|
97
58
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
print(f" {req_id}: {old_hash} -> {new_hash}")
|
|
104
|
-
else:
|
|
105
|
-
print(f"Updating {len(updates)} hashes...")
|
|
106
|
-
for req_id, req, new_hash in updates:
|
|
107
|
-
result = update_hash_in_file(req, new_hash)
|
|
108
|
-
if result['updated']:
|
|
109
|
-
print(f" ✓ {req_id}")
|
|
110
|
-
old_hash = result['old_hash'] or "(none)"
|
|
111
|
-
print(f" [INFO] Hash: {old_hash} -> {result['new_hash']}")
|
|
112
|
-
if result['title_fixed']:
|
|
113
|
-
print(f" [INFO] Title fixed: \"{result['old_title']}\" -> \"{req.title}\"")
|
|
114
|
-
else:
|
|
115
|
-
print(f" ✗ {req_id}")
|
|
116
|
-
print(f" [WARN] Could not find End marker to update")
|
|
59
|
+
Returns:
|
|
60
|
+
Body text for hashing.
|
|
61
|
+
"""
|
|
62
|
+
# Use the stored body_text which was extracted during parsing
|
|
63
|
+
return node.get_field("body_text", "")
|
|
117
64
|
|
|
118
|
-
return 0
|
|
119
65
|
|
|
66
|
+
def _verify_hashes(graph, args) -> int:
|
|
67
|
+
"""Verify all hashes match content."""
|
|
68
|
+
from elspais.utilities.hasher import calculate_hash
|
|
120
69
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
config_path = args.config or find_config_file(Path.cwd())
|
|
124
|
-
if config_path and config_path.exists():
|
|
125
|
-
config = load_config(config_path)
|
|
126
|
-
else:
|
|
127
|
-
config = DEFAULT_CONFIG
|
|
70
|
+
mismatches = []
|
|
71
|
+
missing = []
|
|
128
72
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
73
|
+
for node in graph.nodes_by_kind(NodeKind.REQUIREMENT):
|
|
74
|
+
stored_hash = node.hash
|
|
75
|
+
if not stored_hash:
|
|
76
|
+
missing.append(node.id)
|
|
77
|
+
continue
|
|
78
|
+
|
|
79
|
+
# Get body content from the node's assertions
|
|
80
|
+
body = _get_requirement_body(node)
|
|
81
|
+
if body:
|
|
82
|
+
computed = calculate_hash(body)
|
|
83
|
+
if computed != stored_hash:
|
|
84
|
+
mismatches.append(
|
|
85
|
+
{
|
|
86
|
+
"id": node.id,
|
|
87
|
+
"stored": stored_hash,
|
|
88
|
+
"computed": computed,
|
|
89
|
+
}
|
|
90
|
+
)
|
|
133
91
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
92
|
+
# Report results
|
|
93
|
+
if not getattr(args, "quiet", False):
|
|
94
|
+
if missing:
|
|
95
|
+
print(f"Missing hashes: {len(missing)}")
|
|
96
|
+
for req_id in missing[:10]: # Show first 10
|
|
97
|
+
print(f" {req_id}")
|
|
98
|
+
if len(missing) > 10:
|
|
99
|
+
print(f" ... and {len(missing) - 10} more")
|
|
100
|
+
|
|
101
|
+
if mismatches:
|
|
102
|
+
print(f"Hash mismatches: {len(mismatches)}")
|
|
103
|
+
for m in mismatches[:10]:
|
|
104
|
+
print(f" {m['id']}: stored={m['stored']} computed={m['computed']}")
|
|
105
|
+
if len(mismatches) > 10:
|
|
106
|
+
print(f" ... and {len(mismatches) - 10} more")
|
|
107
|
+
|
|
108
|
+
if not missing and not mismatches:
|
|
109
|
+
print("All hashes valid")
|
|
139
110
|
|
|
140
|
-
|
|
141
|
-
requirements = parser.parse_directories(spec_dirs, skip_files=skip_files)
|
|
142
|
-
except Exception as e:
|
|
143
|
-
print(f"Error parsing requirements: {e}", file=sys.stderr)
|
|
144
|
-
return config, {}
|
|
111
|
+
return 1 if mismatches else 0
|
|
145
112
|
|
|
146
|
-
return config, requirements
|
|
147
113
|
|
|
114
|
+
def _update_hashes(graph, args) -> int:
|
|
115
|
+
"""Update hashes in spec files.
|
|
148
116
|
|
|
149
|
-
|
|
150
|
-
|
|
117
|
+
Finds requirements with mismatched hashes and updates them.
|
|
118
|
+
Supports --dry-run to preview changes without applying them.
|
|
119
|
+
Supports --req-id to update a specific requirement only.
|
|
120
|
+
"""
|
|
121
|
+
from pathlib import Path
|
|
151
122
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
marker title doesn't match the header title.
|
|
123
|
+
from elspais.mcp.file_mutations import update_hash_in_file
|
|
124
|
+
from elspais.utilities.hasher import calculate_hash
|
|
155
125
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
126
|
+
dry_run = getattr(args, "dry_run", False)
|
|
127
|
+
target_req_id = getattr(args, "req_id", None)
|
|
128
|
+
json_output = getattr(args, "json_output", False)
|
|
159
129
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
'
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
130
|
+
# Get repo root from graph or default to cwd
|
|
131
|
+
repo_root = getattr(graph, "_repo_root", None) or Path.cwd()
|
|
132
|
+
|
|
133
|
+
updates = []
|
|
134
|
+
for node in graph.nodes_by_kind(NodeKind.REQUIREMENT):
|
|
135
|
+
# Filter to specific requirement if requested
|
|
136
|
+
if target_req_id and node.id != target_req_id:
|
|
137
|
+
continue
|
|
138
|
+
|
|
139
|
+
stored_hash = node.hash
|
|
140
|
+
body = _get_requirement_body(node)
|
|
141
|
+
|
|
142
|
+
# Skip if no body content (can't compute hash)
|
|
143
|
+
if not body:
|
|
144
|
+
continue
|
|
145
|
+
|
|
146
|
+
computed_hash = calculate_hash(body)
|
|
147
|
+
|
|
148
|
+
# Check if hash needs updating
|
|
149
|
+
if stored_hash != computed_hash:
|
|
150
|
+
# Get file path from source location
|
|
151
|
+
source = node.source
|
|
152
|
+
if source is None:
|
|
153
|
+
continue
|
|
154
|
+
|
|
155
|
+
file_path = Path(repo_root) / source.path
|
|
156
|
+
|
|
157
|
+
updates.append(
|
|
158
|
+
{
|
|
159
|
+
"id": node.id,
|
|
160
|
+
"old_hash": stored_hash or "(none)",
|
|
161
|
+
"new_hash": computed_hash,
|
|
162
|
+
"file": str(file_path),
|
|
163
|
+
}
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
# Handle dry run
|
|
167
|
+
if dry_run:
|
|
168
|
+
if json_output:
|
|
169
|
+
import json
|
|
170
|
+
|
|
171
|
+
print(json.dumps({"updates": updates, "count": len(updates)}, indent=2))
|
|
194
172
|
else:
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
if old_title != req.title:
|
|
203
|
-
result['title_fixed'] = True
|
|
204
|
-
result['old_title'] = old_title
|
|
205
|
-
|
|
206
|
-
# Replace entire line (only first match to avoid affecting other reqs)
|
|
207
|
-
content = re.sub(pattern_by_hash, new_end_line, content, count=1, flags=re.MULTILINE)
|
|
208
|
-
result['updated'] = True
|
|
209
|
-
else:
|
|
210
|
-
# Add hash to end marker (no existing hash)
|
|
211
|
-
# Pattern: *End* *Title* (without hash)
|
|
212
|
-
pattern = rf"^(\*End\*\s+\*{re.escape(req.title)}\*)(?!\s*\|\s*\*\*Hash\*\*)\s*$"
|
|
213
|
-
content, count = re.subn(pattern, new_end_line, content, flags=re.MULTILINE)
|
|
214
|
-
if count > 0:
|
|
215
|
-
result['updated'] = True
|
|
173
|
+
if not updates:
|
|
174
|
+
print("All hashes are up to date.")
|
|
175
|
+
else:
|
|
176
|
+
print(f"Would update {len(updates)} hash(es):")
|
|
177
|
+
for u in updates:
|
|
178
|
+
print(f" {u['id']}: {u['old_hash']} -> {u['new_hash']}")
|
|
179
|
+
return 0
|
|
216
180
|
|
|
217
|
-
|
|
218
|
-
|
|
181
|
+
# Apply updates
|
|
182
|
+
updated_count = 0
|
|
183
|
+
for u in updates:
|
|
184
|
+
success = update_hash_in_file(
|
|
185
|
+
file_path=Path(u["file"]),
|
|
186
|
+
req_id=u["id"],
|
|
187
|
+
new_hash=u["new_hash"],
|
|
188
|
+
)
|
|
189
|
+
if success:
|
|
190
|
+
updated_count += 1
|
|
191
|
+
if not json_output:
|
|
192
|
+
print(f"Updated {u['id']}: {u['old_hash']} -> {u['new_hash']}")
|
|
193
|
+
|
|
194
|
+
if json_output:
|
|
195
|
+
import json
|
|
196
|
+
|
|
197
|
+
print(json.dumps({"updated": updated_count, "total": len(updates)}, indent=2))
|
|
198
|
+
else:
|
|
199
|
+
if updated_count == 0:
|
|
200
|
+
print("No hashes needed updating.")
|
|
201
|
+
else:
|
|
202
|
+
print(f"Updated {updated_count} hash(es).")
|
|
219
203
|
|
|
220
|
-
return
|
|
204
|
+
return 0
|