topmark 0.6.2__tar.gz → 0.8.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.
- {topmark-0.6.2/src/topmark.egg-info → topmark-0.8.0}/PKG-INFO +72 -2
- {topmark-0.6.2 → topmark-0.8.0}/README.md +71 -1
- {topmark-0.6.2 → topmark-0.8.0}/pyproject.toml +1 -1
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/__init__.py +2 -0
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/api/__init__.py +57 -37
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/cli/__init__.py +1 -1
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/cli/cli_types.py +2 -2
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/cli/cmd_common.py +8 -5
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/cli/commands/check.py +19 -16
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/cli/commands/dump_config.py +14 -11
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/cli/commands/filetypes.py +22 -17
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/cli/commands/init_config.py +2 -2
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/cli/commands/processors.py +26 -21
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/cli/commands/show_defaults.py +2 -2
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/cli/commands/strip.py +16 -13
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/cli/commands/version.py +29 -9
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/cli/config_resolver.py +8 -8
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/cli/console_helpers.py +1 -1
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/cli/console_std.py +3 -3
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/cli/errors.py +2 -2
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/cli/io.py +14 -11
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/cli/main.py +8 -6
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/cli/options.py +17 -14
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/cli/utils.py +19 -14
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/cli_shared/utils.py +18 -11
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/config/__init__.py +23 -23
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/config/io.py +4 -3
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/config/logging.py +8 -7
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/constants.py +4 -2
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/file_resolver.py +18 -10
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/filetypes/__init__.py +2 -0
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/filetypes/base.py +13 -7
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/filetypes/instances.py +91 -8
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/filetypes/policy.py +7 -1
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/filetypes/registry.py +5 -4
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/pipeline/__init__.py +2 -0
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/pipeline/context.py +8 -7
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/pipeline/contracts.py +2 -0
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/pipeline/processors/__init__.py +9 -6
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/pipeline/processors/base.py +66 -47
- topmark-0.8.0/src/topmark/pipeline/processors/cblock.py +119 -0
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/pipeline/processors/pound.py +20 -9
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/pipeline/processors/slash.py +5 -6
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/pipeline/processors/xml.py +27 -21
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/pipeline/runner.py +10 -7
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/pipeline/steps/__init__.py +2 -0
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/pipeline/steps/builder.py +9 -7
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/pipeline/steps/comparer.py +6 -4
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/pipeline/steps/patcher.py +5 -3
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/pipeline/steps/reader.py +16 -8
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/pipeline/steps/renderer.py +9 -5
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/pipeline/steps/resolver.py +32 -22
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/pipeline/steps/scanner.py +6 -4
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/pipeline/steps/stripper.py +8 -5
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/pipeline/steps/updater.py +13 -9
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/registry/__init__.py +1 -1
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/registry/filetypes.py +3 -3
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/registry/processors.py +2 -1
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/rendering/api.py +10 -5
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/rendering/formats.py +2 -0
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/utils/__init__.py +2 -0
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/utils/diff.py +6 -4
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/utils/file.py +4 -4
- topmark-0.8.0/src/topmark/utils/version.py +83 -0
- {topmark-0.6.2 → topmark-0.8.0/src/topmark.egg-info}/PKG-INFO +72 -2
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark.egg-info/SOURCES.txt +3 -1
- {topmark-0.6.2 → topmark-0.8.0}/LICENSE +0 -0
- {topmark-0.6.2 → topmark-0.8.0}/setup.cfg +0 -0
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/api/public_types.py +0 -0
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/api/types.py +0 -0
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/cli/commands/__init__.py +0 -0
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/cli/console.py +0 -0
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/cli_shared/__init__.py +0 -0
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/cli_shared/console_api.py +0 -0
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/cli_shared/exit_codes.py +0 -0
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/config/topmark-default.toml +0 -0
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/pipeline/pipelines.py +0 -0
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/py.typed +0 -0
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark/registry/registry.py +0 -0
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark.egg-info/dependency_links.txt +0 -0
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark.egg-info/entry_points.txt +0 -0
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark.egg-info/requires.txt +0 -0
- {topmark-0.6.2 → topmark-0.8.0}/src/topmark.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: topmark
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.0
|
|
4
4
|
Summary: A Python CLI to inspect, validate, and manage license and copyright headers.
|
|
5
5
|
Author-email: Olivier Biot <olivier.biot@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -79,6 +79,73 @@ Full documentation is available on Read the Docs: <https://topmark.readthedocs.i
|
|
|
79
79
|
This README is the canonical, complete introduction for GitHub/PyPI. The docs site provides a
|
|
80
80
|
concise landing page and deep links into topics (install, usage, CI, API, etc.).
|
|
81
81
|
|
|
82
|
+
## 📝 Example headers
|
|
83
|
+
|
|
84
|
+
TopMark inserts headers that are tailored to the comment style and conventions of each file type
|
|
85
|
+
(line comments like `#`/`//`, and block comments like `/* … */`). Here are examples of how TopMark
|
|
86
|
+
headers appear in different file formats:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
#!/bin/bash
|
|
90
|
+
|
|
91
|
+
# topmark:header:start
|
|
92
|
+
#
|
|
93
|
+
# project : TopMark
|
|
94
|
+
# file : script.sh
|
|
95
|
+
# license : MIT
|
|
96
|
+
# copyright : (c) 2025 Olivier Biot
|
|
97
|
+
|
|
98
|
+
# topmark:header:end
|
|
99
|
+
|
|
100
|
+
echo "Hello, World!"
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
```xml
|
|
104
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
105
|
+
<!--
|
|
106
|
+
topmark:header:start
|
|
107
|
+
|
|
108
|
+
project : TopMark
|
|
109
|
+
file : config.xml
|
|
110
|
+
license : MIT
|
|
111
|
+
copyright : (c) 2025 Olivier Biot
|
|
112
|
+
|
|
113
|
+
topmark:header:end
|
|
114
|
+
-->
|
|
115
|
+
|
|
116
|
+
<configuration>
|
|
117
|
+
<!-- XML content here -->
|
|
118
|
+
</configuration>
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
```javascript
|
|
122
|
+
// topmark:header:start
|
|
123
|
+
//
|
|
124
|
+
// project : TopMark
|
|
125
|
+
// file : app.js
|
|
126
|
+
// license : MIT
|
|
127
|
+
// copyright : (c) 2025 Olivier Biot
|
|
128
|
+
//
|
|
129
|
+
// topmark:header:end
|
|
130
|
+
|
|
131
|
+
console.log("Hello, World!");
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
```css
|
|
135
|
+
/*
|
|
136
|
+
* topmark:header:start
|
|
137
|
+
*
|
|
138
|
+
* project : TopMark
|
|
139
|
+
* file : styles.css
|
|
140
|
+
* license : MIT
|
|
141
|
+
* copyright : (c) 2025 Olivier Biot
|
|
142
|
+
*
|
|
143
|
+
* topmark:header:end
|
|
144
|
+
*/
|
|
145
|
+
|
|
146
|
+
body { margin: 0; }
|
|
147
|
+
```
|
|
148
|
+
|
|
82
149
|
## ✨ Features
|
|
83
150
|
|
|
84
151
|
- File header detection, insertion, and replacement
|
|
@@ -193,9 +260,12 @@ topmark show-defaults
|
|
|
193
260
|
# Output a starter configuration to stdout
|
|
194
261
|
topmark init-config
|
|
195
262
|
|
|
196
|
-
#
|
|
263
|
+
# Output TopMark version (PEP 440) in JSON format
|
|
197
264
|
topmark version --format json
|
|
198
265
|
|
|
266
|
+
# Output TopMark version (SemVer) to stdout
|
|
267
|
+
topmark version --semver
|
|
268
|
+
|
|
199
269
|
# Apply changes to files in-place
|
|
200
270
|
topmark check --apply src/
|
|
201
271
|
|
|
@@ -28,6 +28,73 @@ Full documentation is available on Read the Docs: <https://topmark.readthedocs.i
|
|
|
28
28
|
This README is the canonical, complete introduction for GitHub/PyPI. The docs site provides a
|
|
29
29
|
concise landing page and deep links into topics (install, usage, CI, API, etc.).
|
|
30
30
|
|
|
31
|
+
## 📝 Example headers
|
|
32
|
+
|
|
33
|
+
TopMark inserts headers that are tailored to the comment style and conventions of each file type
|
|
34
|
+
(line comments like `#`/`//`, and block comments like `/* … */`). Here are examples of how TopMark
|
|
35
|
+
headers appear in different file formats:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
#!/bin/bash
|
|
39
|
+
|
|
40
|
+
# topmark:header:start
|
|
41
|
+
#
|
|
42
|
+
# project : TopMark
|
|
43
|
+
# file : script.sh
|
|
44
|
+
# license : MIT
|
|
45
|
+
# copyright : (c) 2025 Olivier Biot
|
|
46
|
+
|
|
47
|
+
# topmark:header:end
|
|
48
|
+
|
|
49
|
+
echo "Hello, World!"
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
```xml
|
|
53
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
54
|
+
<!--
|
|
55
|
+
topmark:header:start
|
|
56
|
+
|
|
57
|
+
project : TopMark
|
|
58
|
+
file : config.xml
|
|
59
|
+
license : MIT
|
|
60
|
+
copyright : (c) 2025 Olivier Biot
|
|
61
|
+
|
|
62
|
+
topmark:header:end
|
|
63
|
+
-->
|
|
64
|
+
|
|
65
|
+
<configuration>
|
|
66
|
+
<!-- XML content here -->
|
|
67
|
+
</configuration>
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
```javascript
|
|
71
|
+
// topmark:header:start
|
|
72
|
+
//
|
|
73
|
+
// project : TopMark
|
|
74
|
+
// file : app.js
|
|
75
|
+
// license : MIT
|
|
76
|
+
// copyright : (c) 2025 Olivier Biot
|
|
77
|
+
//
|
|
78
|
+
// topmark:header:end
|
|
79
|
+
|
|
80
|
+
console.log("Hello, World!");
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
```css
|
|
84
|
+
/*
|
|
85
|
+
* topmark:header:start
|
|
86
|
+
*
|
|
87
|
+
* project : TopMark
|
|
88
|
+
* file : styles.css
|
|
89
|
+
* license : MIT
|
|
90
|
+
* copyright : (c) 2025 Olivier Biot
|
|
91
|
+
*
|
|
92
|
+
* topmark:header:end
|
|
93
|
+
*/
|
|
94
|
+
|
|
95
|
+
body { margin: 0; }
|
|
96
|
+
```
|
|
97
|
+
|
|
31
98
|
## ✨ Features
|
|
32
99
|
|
|
33
100
|
- File header detection, insertion, and replacement
|
|
@@ -142,9 +209,12 @@ topmark show-defaults
|
|
|
142
209
|
# Output a starter configuration to stdout
|
|
143
210
|
topmark init-config
|
|
144
211
|
|
|
145
|
-
#
|
|
212
|
+
# Output TopMark version (PEP 440) in JSON format
|
|
146
213
|
topmark version --format json
|
|
147
214
|
|
|
215
|
+
# Output TopMark version (SemVer) to stdout
|
|
216
|
+
topmark version --semver
|
|
217
|
+
|
|
148
218
|
# Apply changes to files in-place
|
|
149
219
|
topmark check --apply src/
|
|
150
220
|
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
[project]
|
|
12
12
|
name = "topmark"
|
|
13
|
-
version = "0.
|
|
13
|
+
version = "0.8.0" # PEP 440: "X.Y.Z.devN", "X.Y.ZrcN", or "X.Y.Z"
|
|
14
14
|
description = "A Python CLI to inspect, validate, and manage license and copyright headers."
|
|
15
15
|
authors = [{ name = "Olivier Biot", email = "olivier.biot@gmail.com" }]
|
|
16
16
|
license = "MIT"
|
|
@@ -65,7 +65,7 @@ from __future__ import annotations
|
|
|
65
65
|
|
|
66
66
|
from dataclasses import replace
|
|
67
67
|
from pathlib import Path
|
|
68
|
-
from typing import TYPE_CHECKING, Any, Iterable, Sequence, cast
|
|
68
|
+
from typing import TYPE_CHECKING, Any, Iterable, Mapping, Sequence, cast
|
|
69
69
|
|
|
70
70
|
from topmark.cli.cmd_common import (
|
|
71
71
|
filter_view_results,
|
|
@@ -74,7 +74,7 @@ from topmark.cli.cmd_common import (
|
|
|
74
74
|
from topmark.cli.io import InputPlan
|
|
75
75
|
from topmark.cli_shared.utils import write_updates
|
|
76
76
|
from topmark.config import Config, MutableConfig
|
|
77
|
-
from topmark.config.logging import get_logger
|
|
77
|
+
from topmark.config.logging import TopmarkLogger, get_logger
|
|
78
78
|
from topmark.constants import TOPMARK_VERSION
|
|
79
79
|
from topmark.pipeline.context import (
|
|
80
80
|
ComparisonStatus,
|
|
@@ -90,10 +90,12 @@ from .types import DiagnosticTotals, FileResult, FileTypeInfo, Outcome, Processo
|
|
|
90
90
|
if TYPE_CHECKING:
|
|
91
91
|
from collections.abc import Mapping, Sequence
|
|
92
92
|
|
|
93
|
-
|
|
93
|
+
from topmark.cli_shared.exit_codes import ExitCode
|
|
94
94
|
|
|
95
|
+
logger: TopmarkLogger = get_logger(__name__)
|
|
95
96
|
|
|
96
|
-
|
|
97
|
+
|
|
98
|
+
__all__: list[str] = [
|
|
97
99
|
"Outcome",
|
|
98
100
|
"FileResult",
|
|
99
101
|
"RunResult",
|
|
@@ -174,7 +176,7 @@ def _map_outcome(r: ProcessingContext, *, apply: bool) -> Outcome:
|
|
|
174
176
|
"""
|
|
175
177
|
if r.status.file is not FileStatus.RESOLVED:
|
|
176
178
|
# Treat unsupported/matched-but-unhandled types as non-errors for API consumers.
|
|
177
|
-
unsupported = {
|
|
179
|
+
unsupported: set[FileStatus] = {
|
|
178
180
|
FileStatus.SKIPPED_UNSUPPORTED,
|
|
179
181
|
FileStatus.SKIPPED_KNOWN_NO_HEADERS,
|
|
180
182
|
}
|
|
@@ -194,8 +196,8 @@ def _map_outcome(r: ProcessingContext, *, apply: bool) -> Outcome:
|
|
|
194
196
|
def _to_file_result(r: ProcessingContext, *, apply: bool) -> FileResult:
|
|
195
197
|
"""Convert a ProcessingContext into a FileResult."""
|
|
196
198
|
# Prefer a unified diff when available; otherwise None (human views may omit diffs).
|
|
197
|
-
diff = r.header_diff or None
|
|
198
|
-
message =
|
|
199
|
+
diff: str | None = r.header_diff or None
|
|
200
|
+
message: str | None = r.summary or None
|
|
199
201
|
return FileResult(
|
|
200
202
|
path=Path(str(r.path)), outcome=_map_outcome(r, apply=apply), diff=diff, message=message
|
|
201
203
|
)
|
|
@@ -216,7 +218,7 @@ def _run_pipeline(
|
|
|
216
218
|
base_config: Mapping[str, Any] | Config | None,
|
|
217
219
|
file_types: Sequence[str] | None,
|
|
218
220
|
apply_changes: bool,
|
|
219
|
-
) -> tuple[Config, list[Path], list[ProcessingContext],
|
|
221
|
+
) -> tuple[Config, list[Path], list[ProcessingContext], ExitCode | None]:
|
|
220
222
|
"""Resolve config + files, register processors, and run the pipeline.
|
|
221
223
|
|
|
222
224
|
- Uses the same planner/resolver as the CLI (via `build_config_and_file_list`)
|
|
@@ -232,14 +234,16 @@ def _run_pipeline(
|
|
|
232
234
|
apply_changes (bool): True if changes should be applied, False if dry-run.
|
|
233
235
|
|
|
234
236
|
Returns:
|
|
235
|
-
tuple[Config, list[Path], list[ProcessingContext],
|
|
237
|
+
tuple[Config, list[Path], list[ProcessingContext], ExitCode | None]: Tuple containing:
|
|
236
238
|
- the resulting Config object;
|
|
237
239
|
- the resolved (optionally filtered if `file_types` was specified) list of paths
|
|
238
240
|
to process;
|
|
239
241
|
- the results of running the pipeline as list of `ProcessingContext` instances;
|
|
240
|
-
- the encountered error code
|
|
242
|
+
- the encountered `ExitCode` error code
|
|
241
243
|
"""
|
|
242
244
|
logger.info("Building config and file list for paths: %s", paths)
|
|
245
|
+
cfg: Config
|
|
246
|
+
file_list: list[Path]
|
|
243
247
|
cfg, file_list = _build_cfg_and_files_via_cli_helpers(
|
|
244
248
|
paths, base_config=base_config, file_types=file_types
|
|
245
249
|
)
|
|
@@ -253,8 +257,10 @@ def _run_pipeline(
|
|
|
253
257
|
Registry.ensure_processors_registered()
|
|
254
258
|
|
|
255
259
|
# Use a snapshot that carries the apply intent
|
|
256
|
-
cfg_for_run = replace(cfg, apply_changes=apply_changes)
|
|
260
|
+
cfg_for_run: Config = replace(cfg, apply_changes=apply_changes)
|
|
257
261
|
|
|
262
|
+
results: list[ProcessingContext] = []
|
|
263
|
+
encountered_error_code: ExitCode | None
|
|
258
264
|
results, encountered_error_code = run_steps_for_files(
|
|
259
265
|
file_list, pipeline_name=pipeline_name, config=cfg_for_run
|
|
260
266
|
)
|
|
@@ -275,10 +281,10 @@ def _apply_view_filter(
|
|
|
275
281
|
`skipped_count` equals `len(results) - len(filtered_results)` and reflects only
|
|
276
282
|
view-level filtering (not internal SKIPPED statuses).
|
|
277
283
|
"""
|
|
278
|
-
view_results = filter_view_results(
|
|
284
|
+
view_results: list[ProcessingContext] = filter_view_results(
|
|
279
285
|
results, skip_compliant=skip_compliant, skip_unsupported=skip_unsupported
|
|
280
286
|
)
|
|
281
|
-
skipped = len(results) - len(view_results)
|
|
287
|
+
skipped: int = len(results) - len(view_results)
|
|
282
288
|
return view_results, skipped
|
|
283
289
|
|
|
284
290
|
|
|
@@ -305,19 +311,21 @@ def _collect_diagnostic_totals(results: list[ProcessingContext]) -> DiagnosticTo
|
|
|
305
311
|
Counts reflect only the supplied `results` (typically the **view** after
|
|
306
312
|
filtering), matching what is returned to the caller.
|
|
307
313
|
"""
|
|
308
|
-
total_info
|
|
314
|
+
total_info: int = 0
|
|
315
|
+
total_warn: int = 0
|
|
316
|
+
total_error: int = 0
|
|
309
317
|
for r in results:
|
|
310
318
|
if not r.diagnostics:
|
|
311
319
|
continue
|
|
312
320
|
for d in r.diagnostics:
|
|
313
|
-
lv = d.level.value
|
|
321
|
+
lv: str = d.level.value
|
|
314
322
|
if lv == "info":
|
|
315
323
|
total_info += 1
|
|
316
324
|
elif lv == "warning":
|
|
317
325
|
total_warn += 1
|
|
318
326
|
elif lv == "error":
|
|
319
327
|
total_error += 1
|
|
320
|
-
total = total_info + total_warn + total_error
|
|
328
|
+
total: int = total_info + total_warn + total_error
|
|
321
329
|
return {"info": total_info, "warning": total_warn, "error": total_error, "total": total}
|
|
322
330
|
|
|
323
331
|
|
|
@@ -362,7 +370,7 @@ def _build_cfg_and_files_via_cli_helpers(
|
|
|
362
370
|
# normalization of paths.
|
|
363
371
|
if base_config is not None:
|
|
364
372
|
# Start from defaults so that header fields/values are present unless overridden
|
|
365
|
-
draft_defaults = MutableConfig.from_defaults()
|
|
373
|
+
draft_defaults: MutableConfig = MutableConfig.from_defaults()
|
|
366
374
|
draft = draft_defaults.merge_with(
|
|
367
375
|
draft
|
|
368
376
|
) # 'draft' came from _ensure_mutable_config(base_config)
|
|
@@ -372,8 +380,8 @@ def _build_cfg_and_files_via_cli_helpers(
|
|
|
372
380
|
logger.debug("Found %d input paths", len(draft.files))
|
|
373
381
|
if file_types:
|
|
374
382
|
draft.file_types = set(file_types)
|
|
375
|
-
cfg = draft.freeze()
|
|
376
|
-
file_list = resolve_file_list(cfg)
|
|
383
|
+
cfg: Config = draft.freeze()
|
|
384
|
+
file_list: list[Path] = resolve_file_list(cfg)
|
|
377
385
|
logger.debug("Files found: %s", len(file_list))
|
|
378
386
|
return cfg, file_list
|
|
379
387
|
|
|
@@ -405,7 +413,7 @@ def _build_cfg_and_files_via_cli_helpers(
|
|
|
405
413
|
local_toml = Path(local_toml_file)
|
|
406
414
|
if local_toml.exists():
|
|
407
415
|
logger.info("Loading local config from: %s", local_toml)
|
|
408
|
-
local_config = MutableConfig.from_toml_file(local_toml)
|
|
416
|
+
local_config: MutableConfig | None = MutableConfig.from_toml_file(local_toml)
|
|
409
417
|
if local_config is not None:
|
|
410
418
|
draft = draft.merge_with(local_config)
|
|
411
419
|
break # only first one
|
|
@@ -415,11 +423,11 @@ def _build_cfg_and_files_via_cli_helpers(
|
|
|
415
423
|
"Sequence[str | Path]", args_mapping.get("config_files", ())
|
|
416
424
|
)
|
|
417
425
|
for entry in config_files_seq:
|
|
418
|
-
p = entry if isinstance(entry, Path) else Path(entry)
|
|
426
|
+
p: Path = entry if isinstance(entry, Path) else Path(entry)
|
|
419
427
|
logger.debug("Adding config file '%s' (type=%s)", entry, type(entry).__name__)
|
|
420
428
|
if p.exists():
|
|
421
429
|
logger.info("Loading config override from: %s", p)
|
|
422
|
-
extra = MutableConfig.from_toml_file(p)
|
|
430
|
+
extra: MutableConfig | None = MutableConfig.from_toml_file(p)
|
|
423
431
|
if extra is not None:
|
|
424
432
|
draft = draft.merge_with(extra)
|
|
425
433
|
else:
|
|
@@ -475,6 +483,9 @@ def check(
|
|
|
475
483
|
raise ValueError("Options add_only and update_only are mutually exclusive.")
|
|
476
484
|
|
|
477
485
|
# Always use 'apply' pipeline to compute updated content even in dry-run
|
|
486
|
+
file_list: list[Path] = []
|
|
487
|
+
results: list[ProcessingContext] = []
|
|
488
|
+
encountered_error_code: ExitCode | None = None
|
|
478
489
|
_, file_list, results, encountered_error_code = _run_pipeline(
|
|
479
490
|
pipeline_name="apply",
|
|
480
491
|
paths=paths,
|
|
@@ -485,20 +496,23 @@ def check(
|
|
|
485
496
|
if not file_list:
|
|
486
497
|
return RunResult(files=(), summary={}, had_errors=False)
|
|
487
498
|
|
|
499
|
+
view_results: list[ProcessingContext]
|
|
500
|
+
skipped: int
|
|
488
501
|
view_results, skipped = _apply_view_filter(
|
|
489
502
|
results, skip_compliant=skip_compliant, skip_unsupported=skip_unsupported
|
|
490
503
|
)
|
|
491
504
|
|
|
492
|
-
files = tuple(_to_file_result(r, apply=apply) for r in view_results)
|
|
493
|
-
summary = _summarize(files)
|
|
494
|
-
had_errors = any(_map_outcome(r, apply=apply) is Outcome.ERROR for r in results) or (
|
|
505
|
+
files: tuple[FileResult, ...] = tuple(_to_file_result(r, apply=apply) for r in view_results)
|
|
506
|
+
summary: Mapping[str, int] = _summarize(files)
|
|
507
|
+
had_errors: bool = any(_map_outcome(r, apply=apply) is Outcome.ERROR for r in results) or (
|
|
495
508
|
encountered_error_code is not None
|
|
496
509
|
)
|
|
497
|
-
diagnostics = _collect_diagnostics(view_results)
|
|
498
|
-
diagnostic_totals = _collect_diagnostic_totals(view_results)
|
|
499
|
-
diagnostic_totals_all = _collect_diagnostic_totals(results)
|
|
510
|
+
diagnostics: dict[str, list[PublicDiagnostic]] = _collect_diagnostics(view_results)
|
|
511
|
+
diagnostic_totals: DiagnosticTotals = _collect_diagnostic_totals(view_results)
|
|
512
|
+
diagnostic_totals_all: DiagnosticTotals = _collect_diagnostic_totals(results)
|
|
500
513
|
|
|
501
|
-
written
|
|
514
|
+
written: int = 0
|
|
515
|
+
failed: int = 0
|
|
502
516
|
if apply:
|
|
503
517
|
|
|
504
518
|
def _should_write_check(r: ProcessingContext) -> bool:
|
|
@@ -559,6 +573,9 @@ def strip(
|
|
|
559
573
|
pipeline write decisions.
|
|
560
574
|
"""
|
|
561
575
|
# The 'strip' pipeline computes removal-ready updates even in dry-run
|
|
576
|
+
file_list: list[Path] = []
|
|
577
|
+
results: list[ProcessingContext] = []
|
|
578
|
+
encountered_error_code: ExitCode | None = None
|
|
562
579
|
_, file_list, results, encountered_error_code = _run_pipeline(
|
|
563
580
|
pipeline_name="strip",
|
|
564
581
|
paths=paths,
|
|
@@ -569,20 +586,23 @@ def strip(
|
|
|
569
586
|
if not file_list:
|
|
570
587
|
return RunResult(files=(), summary={}, had_errors=False)
|
|
571
588
|
|
|
589
|
+
view_results: list[ProcessingContext]
|
|
590
|
+
skipped: int = 0
|
|
572
591
|
view_results, skipped = _apply_view_filter(
|
|
573
592
|
results, skip_compliant=skip_compliant, skip_unsupported=skip_unsupported
|
|
574
593
|
)
|
|
575
594
|
|
|
576
|
-
files = tuple(_to_file_result(r, apply=apply) for r in view_results)
|
|
577
|
-
summary = _summarize(files)
|
|
578
|
-
had_errors = any(_map_outcome(r, apply=apply) is Outcome.ERROR for r in results) or (
|
|
595
|
+
files: tuple[FileResult, ...] = tuple(_to_file_result(r, apply=apply) for r in view_results)
|
|
596
|
+
summary: Mapping[str, int] = _summarize(files)
|
|
597
|
+
had_errors: bool = any(_map_outcome(r, apply=apply) is Outcome.ERROR for r in results) or (
|
|
579
598
|
encountered_error_code is not None
|
|
580
599
|
)
|
|
581
|
-
diagnostics = _collect_diagnostics(view_results)
|
|
582
|
-
diagnostic_totals = _collect_diagnostic_totals(view_results)
|
|
583
|
-
diagnostic_totals_all = _collect_diagnostic_totals(results)
|
|
600
|
+
diagnostics: dict[str, list[PublicDiagnostic]] = _collect_diagnostics(view_results)
|
|
601
|
+
diagnostic_totals: DiagnosticTotals = _collect_diagnostic_totals(view_results)
|
|
602
|
+
diagnostic_totals_all: DiagnosticTotals = _collect_diagnostic_totals(results)
|
|
584
603
|
|
|
585
|
-
written
|
|
604
|
+
written: int = 0
|
|
605
|
+
failed: int = 0
|
|
586
606
|
if apply:
|
|
587
607
|
|
|
588
608
|
def _should_write_strip(r: ProcessingContext) -> bool:
|
|
@@ -625,7 +645,7 @@ def get_filetype_info(long: bool = False) -> list[FileTypeInfo]:
|
|
|
625
645
|
for name, ft in Registry.filetypes().items():
|
|
626
646
|
processor = proc_reg.get(name, None) if proc_reg else None
|
|
627
647
|
supported = bool(processor)
|
|
628
|
-
processor_name = processor.__class__.__name__ if processor else None
|
|
648
|
+
processor_name: str | None = processor.__class__.__name__ if processor else None
|
|
629
649
|
info: FileTypeInfo = {
|
|
630
650
|
"name": name,
|
|
631
651
|
"description": getattr(ft, "description", ""),
|
|
@@ -213,7 +213,7 @@ class EnumChoiceParam(ParamTypeBase, Generic[E]):
|
|
|
213
213
|
for choice in cast("Iterable[E]", self.enum_cls)
|
|
214
214
|
}
|
|
215
215
|
|
|
216
|
-
key = value.lower()
|
|
216
|
+
key: str = value.lower()
|
|
217
217
|
if key in lookup:
|
|
218
218
|
return lookup[key]
|
|
219
219
|
|
|
@@ -240,7 +240,7 @@ class EnumChoiceParam(ParamTypeBase, Generic[E]):
|
|
|
240
240
|
CompletionItem as RuntimeCompletionItem,
|
|
241
241
|
) # Click 8.x
|
|
242
242
|
|
|
243
|
-
prefix = (incomplete or "").lower()
|
|
243
|
+
prefix: str = (incomplete or "").lower()
|
|
244
244
|
items: list["ClickCompletionItem"] = []
|
|
245
245
|
for e in cast("Iterable[E]", self.enum_cls):
|
|
246
246
|
val = str(getattr(e, "value", e))
|
|
@@ -24,20 +24,23 @@ import click
|
|
|
24
24
|
from topmark.cli.config_resolver import resolve_config_from_click
|
|
25
25
|
from topmark.cli.console_helpers import get_console_safely
|
|
26
26
|
from topmark.cli_shared.exit_codes import ExitCode
|
|
27
|
-
from topmark.config.logging import get_logger
|
|
27
|
+
from topmark.config.logging import TopmarkLogger, get_logger
|
|
28
28
|
from topmark.file_resolver import resolve_file_list
|
|
29
29
|
from topmark.pipeline import runner
|
|
30
30
|
from topmark.pipeline.context import ComparisonStatus, FileStatus, ProcessingContext
|
|
31
31
|
from topmark.pipeline.pipelines import get_pipeline
|
|
32
32
|
|
|
33
33
|
if TYPE_CHECKING:
|
|
34
|
+
from collections.abc import Sequence
|
|
34
35
|
from pathlib import Path
|
|
35
36
|
|
|
36
37
|
from topmark.cli.io import InputPlan
|
|
38
|
+
from topmark.cli_shared.console_api import ConsoleLike
|
|
37
39
|
from topmark.config import Config, MutableConfig
|
|
40
|
+
from topmark.pipeline.contracts import Step
|
|
38
41
|
from topmark.rendering.formats import HeaderOutputFormat
|
|
39
42
|
|
|
40
|
-
logger = get_logger(__name__)
|
|
43
|
+
logger: TopmarkLogger = get_logger(__name__)
|
|
41
44
|
|
|
42
45
|
|
|
43
46
|
def get_effective_verbosity(ctx: click.Context, config: "Config | None" = None) -> int:
|
|
@@ -48,7 +51,7 @@ def get_effective_verbosity(ctx: click.Context, config: "Config | None" = None)
|
|
|
48
51
|
2. ctx.obj["verbosity_level"] if present
|
|
49
52
|
3. 0 (terse)
|
|
50
53
|
"""
|
|
51
|
-
cfg_level =
|
|
54
|
+
cfg_level: int | None = config.verbosity_level if config else None
|
|
52
55
|
if cfg_level is not None:
|
|
53
56
|
return int(cfg_level)
|
|
54
57
|
return int(ctx.obj.get("verbosity_level", 0))
|
|
@@ -83,8 +86,8 @@ def run_steps_for_files(
|
|
|
83
86
|
ENCODING_ERROR → UnicodeDecodeError
|
|
84
87
|
PIPELINE_ERROR → any other unexpected exception
|
|
85
88
|
"""
|
|
86
|
-
console = get_console_safely()
|
|
87
|
-
steps = get_pipeline(pipeline_name)
|
|
89
|
+
console: ConsoleLike = get_console_safely()
|
|
90
|
+
steps: Sequence[Step] = get_pipeline(pipeline_name)
|
|
88
91
|
results: list[ProcessingContext] = []
|
|
89
92
|
encountered_error_code: ExitCode | None = None
|
|
90
93
|
|
|
@@ -58,7 +58,7 @@ from topmark.cli.cmd_common import (
|
|
|
58
58
|
run_steps_for_files,
|
|
59
59
|
)
|
|
60
60
|
from topmark.cli.errors import TopmarkIOError, TopmarkUsageError
|
|
61
|
-
from topmark.cli.io import plan_cli_inputs
|
|
61
|
+
from topmark.cli.io import InputPlan, plan_cli_inputs
|
|
62
62
|
from topmark.cli.options import (
|
|
63
63
|
CONTEXT_SETTINGS,
|
|
64
64
|
common_config_options,
|
|
@@ -80,7 +80,7 @@ from topmark.cli_shared.utils import (
|
|
|
80
80
|
safe_unlink,
|
|
81
81
|
write_updates,
|
|
82
82
|
)
|
|
83
|
-
from topmark.config.logging import get_logger
|
|
83
|
+
from topmark.config.logging import TopmarkLogger, get_logger
|
|
84
84
|
from topmark.pipeline.context import (
|
|
85
85
|
ComparisonStatus,
|
|
86
86
|
FileStatus,
|
|
@@ -90,10 +90,13 @@ from topmark.pipeline.context import (
|
|
|
90
90
|
)
|
|
91
91
|
|
|
92
92
|
if TYPE_CHECKING:
|
|
93
|
+
from pathlib import Path
|
|
94
|
+
|
|
93
95
|
from topmark.cli_shared.console_api import ConsoleLike
|
|
96
|
+
from topmark.config import Config, MutableConfig
|
|
94
97
|
from topmark.rendering.formats import HeaderOutputFormat
|
|
95
98
|
|
|
96
|
-
logger = get_logger(__name__)
|
|
99
|
+
logger: TopmarkLogger = get_logger(__name__)
|
|
97
100
|
|
|
98
101
|
|
|
99
102
|
@click.command(
|
|
@@ -236,7 +239,7 @@ def check_command(
|
|
|
236
239
|
PIPELINE_ERROR (70): An internal processing step failed.
|
|
237
240
|
UNEXPECTED_ERROR (255): An unhandled error occurred.
|
|
238
241
|
"""
|
|
239
|
-
ctx = click.get_current_context()
|
|
242
|
+
ctx: click.Context = click.get_current_context()
|
|
240
243
|
ctx.ensure_object(dict)
|
|
241
244
|
console: ConsoleLike = ctx.obj["console"]
|
|
242
245
|
|
|
@@ -253,7 +256,7 @@ def check_command(
|
|
|
253
256
|
)
|
|
254
257
|
|
|
255
258
|
# === Build Config and file list ===
|
|
256
|
-
plan = plan_cli_inputs(
|
|
259
|
+
plan: InputPlan = plan_cli_inputs(
|
|
257
260
|
ctx=ctx,
|
|
258
261
|
files_from=files_from,
|
|
259
262
|
include_from=include_from,
|
|
@@ -262,7 +265,7 @@ def check_command(
|
|
|
262
265
|
exclude_patterns=exclude_patterns,
|
|
263
266
|
stdin_filename=stdin_filename,
|
|
264
267
|
)
|
|
265
|
-
draft_config = build_config_common(
|
|
268
|
+
draft_config: MutableConfig = build_config_common(
|
|
266
269
|
ctx=ctx,
|
|
267
270
|
plan=plan,
|
|
268
271
|
no_config=no_config,
|
|
@@ -276,18 +279,18 @@ def check_command(
|
|
|
276
279
|
# Propagate runtime intent for updater (terminal vs preview write status)
|
|
277
280
|
draft_config.apply_changes = bool(apply_changes)
|
|
278
281
|
|
|
279
|
-
config = draft_config.freeze()
|
|
282
|
+
config: Config = draft_config.freeze()
|
|
280
283
|
|
|
281
|
-
temp_path = plan.temp_path # for cleanup/STDIN-apply branch
|
|
282
|
-
stdin_mode = plan.stdin_mode
|
|
283
|
-
file_list = build_file_list(
|
|
284
|
+
temp_path: Path | None = plan.temp_path # for cleanup/STDIN-apply branch
|
|
285
|
+
stdin_mode: bool = plan.stdin_mode
|
|
286
|
+
file_list: list[Path] = build_file_list(
|
|
284
287
|
config,
|
|
285
288
|
stdin_mode=stdin_mode,
|
|
286
289
|
temp_path=temp_path,
|
|
287
290
|
)
|
|
288
291
|
|
|
289
292
|
# Determine effective program-output verbosity for gating extra details
|
|
290
|
-
vlevel = get_effective_verbosity(ctx, config)
|
|
293
|
+
vlevel: int = get_effective_verbosity(ctx, config)
|
|
291
294
|
|
|
292
295
|
logger.trace("Config after merging args and resolving file list: %s", config)
|
|
293
296
|
|
|
@@ -304,7 +307,7 @@ def check_command(
|
|
|
304
307
|
|
|
305
308
|
# Always run the 'apply' pipeline when --apply is set so updated_file_lines are computed.
|
|
306
309
|
# The --summary flag only affects how we render output, not which steps run.
|
|
307
|
-
pipeline_name = "apply" if apply_changes else ("summary" if summary_mode else "apply")
|
|
310
|
+
pipeline_name: str = "apply" if apply_changes else ("summary" if summary_mode else "apply")
|
|
308
311
|
results: list[ProcessingContext] = []
|
|
309
312
|
encountered_error_code: ExitCode | None = None
|
|
310
313
|
|
|
@@ -313,7 +316,7 @@ def check_command(
|
|
|
313
316
|
)
|
|
314
317
|
|
|
315
318
|
# Optional filtering
|
|
316
|
-
view_results = filter_view_results(
|
|
319
|
+
view_results: list[ProcessingContext] = filter_view_results(
|
|
317
320
|
results, skip_compliant=skip_compliant, skip_unsupported=skip_unsupported
|
|
318
321
|
)
|
|
319
322
|
|
|
@@ -358,8 +361,8 @@ def check_command(
|
|
|
358
361
|
return
|
|
359
362
|
|
|
360
363
|
if apply_changes:
|
|
361
|
-
written = 0
|
|
362
|
-
failed = 0
|
|
364
|
+
written: int = 0
|
|
365
|
+
failed: int = 0
|
|
363
366
|
|
|
364
367
|
def _should_write_check(r: ProcessingContext) -> bool:
|
|
365
368
|
"""Determine whether to write this file in check mode."""
|
|
@@ -376,7 +379,7 @@ def check_command(
|
|
|
376
379
|
written, failed = write_updates(results, should_write=_should_write_check)
|
|
377
380
|
|
|
378
381
|
if fmt == OutputFormat.DEFAULT:
|
|
379
|
-
msg = (
|
|
382
|
+
msg: str = (
|
|
380
383
|
f"\n✅ Applied changes to {written} file(s)."
|
|
381
384
|
if written
|
|
382
385
|
else "\n✅ No changes to apply."
|