nm-tool-forge 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. nm_tool_forge-0.1.0/LICENSE +21 -0
  2. nm_tool_forge-0.1.0/PKG-INFO +198 -0
  3. nm_tool_forge-0.1.0/README.md +167 -0
  4. nm_tool_forge-0.1.0/pyproject.toml +67 -0
  5. nm_tool_forge-0.1.0/setup.cfg +4 -0
  6. nm_tool_forge-0.1.0/src/loganalysis/__init__.py +16 -0
  7. nm_tool_forge-0.1.0/src/loganalysis/__main__.py +5 -0
  8. nm_tool_forge-0.1.0/src/loganalysis/analysis.py +175 -0
  9. nm_tool_forge-0.1.0/src/loganalysis/cli.py +88 -0
  10. nm_tool_forge-0.1.0/src/loganalysis/constants.py +126 -0
  11. nm_tool_forge-0.1.0/src/loganalysis/converters.py +150 -0
  12. nm_tool_forge-0.1.0/src/loganalysis/csv_export.py +18 -0
  13. nm_tool_forge-0.1.0/src/loganalysis/encoding.py +22 -0
  14. nm_tool_forge-0.1.0/src/loganalysis/filesystem.py +26 -0
  15. nm_tool_forge-0.1.0/src/loganalysis/models.py +63 -0
  16. nm_tool_forge-0.1.0/src/loganalysis/normalization.py +97 -0
  17. nm_tool_forge-0.1.0/src/loganalysis/parsing.py +69 -0
  18. nm_tool_forge-0.1.0/src/loganalysis/report_html.py +378 -0
  19. nm_tool_forge-0.1.0/src/loganalysis/report_markdown.py +209 -0
  20. nm_tool_forge-0.1.0/src/loganalysis/report_models.py +31 -0
  21. nm_tool_forge-0.1.0/src/loganalysis/report_pdf.py +74 -0
  22. nm_tool_forge-0.1.0/src/loganalysis/selftest.py +58 -0
  23. nm_tool_forge-0.1.0/src/nm_tool_forge.egg-info/PKG-INFO +198 -0
  24. nm_tool_forge-0.1.0/src/nm_tool_forge.egg-info/SOURCES.txt +31 -0
  25. nm_tool_forge-0.1.0/src/nm_tool_forge.egg-info/dependency_links.txt +1 -0
  26. nm_tool_forge-0.1.0/src/nm_tool_forge.egg-info/entry_points.txt +3 -0
  27. nm_tool_forge-0.1.0/src/nm_tool_forge.egg-info/requires.txt +10 -0
  28. nm_tool_forge-0.1.0/src/nm_tool_forge.egg-info/top_level.txt +1 -0
  29. nm_tool_forge-0.1.0/tests/test_analysis.py +53 -0
  30. nm_tool_forge-0.1.0/tests/test_normalization.py +23 -0
  31. nm_tool_forge-0.1.0/tests/test_parsing.py +38 -0
  32. nm_tool_forge-0.1.0/tests/test_report_html.py +42 -0
  33. nm_tool_forge-0.1.0/tests/test_report_markdown.py +44 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026
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,198 @@
1
+ Metadata-Version: 2.4
2
+ Name: nm-tool-forge
3
+ Version: 0.1.0
4
+ Summary: Analyze MigMan log files and generate aggregated CSV, Markdown, HTML, and optional PDF reports.
5
+ Author-email: Stefan Ewald <s.ew@outlook.de>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/Jack736-ui/migman_log
8
+ Project-URL: Issues, https://github.com/Jack736-ui/migman_log/issues
9
+ Keywords: migman,logs,analysis,reporting,csv,markdown,pdf
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
18
+ Classifier: Topic :: Utilities
19
+ Requires-Python: >=3.10
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: chardet>=5.0
23
+ Provides-Extra: pdf
24
+ Requires-Dist: weasyprint>=62; extra == "pdf"
25
+ Provides-Extra: dev
26
+ Requires-Dist: pytest>=8.0; extra == "dev"
27
+ Requires-Dist: build>=1.2; extra == "dev"
28
+ Requires-Dist: twine>=5.0; extra == "dev"
29
+ Requires-Dist: ruff>=0.11; extra == "dev"
30
+ Dynamic: license-file
31
+
32
+ # nm-tool-forge
33
+
34
+ `nm-tool-forge` analyzes MigMan text log files with severity tokens such as `INFO`, `ERROR`, and `WARNING` and generates aggregated CSV, Markdown, HTML, and optional PDF reports.
35
+
36
+ The project uses a package-ready `src` layout. The legacy `log_analysis.py` file remains available as a thin compatibility entry point for older local setups.
37
+
38
+ ## Features
39
+
40
+ - Parse logical log entries from multi-line text logs
41
+ - Normalize recurring error patterns for better aggregation
42
+ - Generate aggregated CSV reports
43
+ - Generate Markdown summary reports
44
+ - Optionally convert reports to HTML and PDF
45
+ - Keep a backup copy of analyzed log files
46
+ - Run built-in self-tests from the CLI
47
+
48
+ ## Installation
49
+
50
+ Basic installation from a local checkout:
51
+
52
+ ```powershell
53
+ python -m pip install .
54
+ ```
55
+
56
+ Installation with optional PDF support and developer tools:
57
+
58
+ ```powershell
59
+ python -m pip install .[pdf,dev]
60
+ ```
61
+
62
+ ## Command-line usage
63
+
64
+ After installation, both entry points are available:
65
+
66
+ ```powershell
67
+ python -m loganalysis --help
68
+ loganalysis --help
69
+ nm-tool-forge --help
70
+ ```
71
+
72
+ Typical analysis run:
73
+
74
+ ```powershell
75
+ nm-tool-forge --logs-dir logs --out-dir log_analyse_out
76
+ ```
77
+
78
+ Analysis with HTML/PDF conversion:
79
+
80
+ ```powershell
81
+ nm-tool-forge --logs-dir logs --out-dir log_analyse_out --convert
82
+ ```
83
+
84
+ Self-test mode:
85
+
86
+ ```powershell
87
+ python -m loganalysis --self-test
88
+ ```
89
+
90
+ Legacy compatibility call:
91
+
92
+ ```powershell
93
+ python .\log_analysis.py --convert
94
+ ```
95
+
96
+ ## Supported CLI options
97
+
98
+ - `--logs-dir`
99
+ - `--out-dir`
100
+ - `--backup-dir`
101
+ - `--top-examples`
102
+ - `--convert`
103
+ - `--self-test`
104
+
105
+ ## Library usage
106
+
107
+ ```python
108
+ from pathlib import Path
109
+
110
+ from loganalysis import (
111
+ analyze_file,
112
+ convert_report_md_to_html_pdf,
113
+ iter_logical_entries,
114
+ normalize_message,
115
+ )
116
+
117
+ result = analyze_file(Path("logs/app.txt"))
118
+ print(result["norm_counts"])
119
+
120
+ print(normalize_message(
121
+ 'Conversion: X =3100110. 138 The record was not found in table "Teile".'
122
+ ))
123
+
124
+ for entry in iter_logical_entries(Path("logs/app.txt")):
125
+ print(entry)
126
+
127
+ convert_report_md_to_html_pdf(
128
+ Path("log_analyse_out/report.md"),
129
+ Path("log_analyse_out/report.html"),
130
+ Path("log_analyse_out/report.pdf"),
131
+ )
132
+ ```
133
+
134
+ ## Project structure
135
+
136
+ ```text
137
+ .
138
+ ├─ pyproject.toml
139
+ ├─ src/loganalysis/
140
+ ├─ tests/
141
+ ├─ docs/
142
+ └─ log_analysis.py
143
+ ```
144
+
145
+ Important modules:
146
+
147
+ - `analysis.py` - file-level and overall aggregation
148
+ - `parsing.py` - logical entry detection and parsing
149
+ - `normalization.py` - message normalization
150
+ - `report_markdown.py` - Markdown report model and rendering
151
+ - `report_html.py` - HTML/CSS rendering
152
+ - `report_pdf.py` - PDF engine selection and fallback handling
153
+ - `converters.py` - Markdown-to-HTML/PDF conversion
154
+ - `cli.py` - command-line entry point
155
+
156
+ ## HTML/PDF conversion
157
+
158
+ Report conversion is intentionally optional:
159
+
160
+ - `report.md` remains the primary human-readable output
161
+ - `report.html` is generated from the internal report model
162
+ - `report.pdf` is created when supported PDF tooling is available
163
+
164
+ PDF engine preference order:
165
+
166
+ 1. `weasyprint`
167
+ 2. `wkhtmltopdf`
168
+ 3. `pandoc` + `xelatex` or `pdflatex`
169
+
170
+ If no supported PDF engine is available, the analysis still succeeds and generates Markdown and HTML output.
171
+
172
+ Windows-specific setup notes:
173
+
174
+ - `docs/install_gtk_weasyprint_windows.md`
175
+ - `docs/install_xelatex_windows.md`
176
+
177
+ ## Tests
178
+
179
+ ```powershell
180
+ pytest
181
+ ```
182
+
183
+ ## Local build
184
+
185
+ ```powershell
186
+ python -m build
187
+ ```
188
+
189
+ Expected artifacts:
190
+
191
+ - `dist/*.tar.gz`
192
+ - `dist/*.whl`
193
+
194
+ ## Notes
195
+
196
+ The package name on PyPI/TestPyPI is `nm-tool-forge`, while the current Python import package remains `loganalysis`.
197
+
198
+ This keeps the first public release small and low-risk. A later follow-up release can still rename the import package if desired.
@@ -0,0 +1,167 @@
1
+ # nm-tool-forge
2
+
3
+ `nm-tool-forge` analyzes MigMan text log files with severity tokens such as `INFO`, `ERROR`, and `WARNING` and generates aggregated CSV, Markdown, HTML, and optional PDF reports.
4
+
5
+ The project uses a package-ready `src` layout. The legacy `log_analysis.py` file remains available as a thin compatibility entry point for older local setups.
6
+
7
+ ## Features
8
+
9
+ - Parse logical log entries from multi-line text logs
10
+ - Normalize recurring error patterns for better aggregation
11
+ - Generate aggregated CSV reports
12
+ - Generate Markdown summary reports
13
+ - Optionally convert reports to HTML and PDF
14
+ - Keep a backup copy of analyzed log files
15
+ - Run built-in self-tests from the CLI
16
+
17
+ ## Installation
18
+
19
+ Basic installation from a local checkout:
20
+
21
+ ```powershell
22
+ python -m pip install .
23
+ ```
24
+
25
+ Installation with optional PDF support and developer tools:
26
+
27
+ ```powershell
28
+ python -m pip install .[pdf,dev]
29
+ ```
30
+
31
+ ## Command-line usage
32
+
33
+ After installation, both entry points are available:
34
+
35
+ ```powershell
36
+ python -m loganalysis --help
37
+ loganalysis --help
38
+ nm-tool-forge --help
39
+ ```
40
+
41
+ Typical analysis run:
42
+
43
+ ```powershell
44
+ nm-tool-forge --logs-dir logs --out-dir log_analyse_out
45
+ ```
46
+
47
+ Analysis with HTML/PDF conversion:
48
+
49
+ ```powershell
50
+ nm-tool-forge --logs-dir logs --out-dir log_analyse_out --convert
51
+ ```
52
+
53
+ Self-test mode:
54
+
55
+ ```powershell
56
+ python -m loganalysis --self-test
57
+ ```
58
+
59
+ Legacy compatibility call:
60
+
61
+ ```powershell
62
+ python .\log_analysis.py --convert
63
+ ```
64
+
65
+ ## Supported CLI options
66
+
67
+ - `--logs-dir`
68
+ - `--out-dir`
69
+ - `--backup-dir`
70
+ - `--top-examples`
71
+ - `--convert`
72
+ - `--self-test`
73
+
74
+ ## Library usage
75
+
76
+ ```python
77
+ from pathlib import Path
78
+
79
+ from loganalysis import (
80
+ analyze_file,
81
+ convert_report_md_to_html_pdf,
82
+ iter_logical_entries,
83
+ normalize_message,
84
+ )
85
+
86
+ result = analyze_file(Path("logs/app.txt"))
87
+ print(result["norm_counts"])
88
+
89
+ print(normalize_message(
90
+ 'Conversion: X =3100110. 138 The record was not found in table "Teile".'
91
+ ))
92
+
93
+ for entry in iter_logical_entries(Path("logs/app.txt")):
94
+ print(entry)
95
+
96
+ convert_report_md_to_html_pdf(
97
+ Path("log_analyse_out/report.md"),
98
+ Path("log_analyse_out/report.html"),
99
+ Path("log_analyse_out/report.pdf"),
100
+ )
101
+ ```
102
+
103
+ ## Project structure
104
+
105
+ ```text
106
+ .
107
+ ├─ pyproject.toml
108
+ ├─ src/loganalysis/
109
+ ├─ tests/
110
+ ├─ docs/
111
+ └─ log_analysis.py
112
+ ```
113
+
114
+ Important modules:
115
+
116
+ - `analysis.py` - file-level and overall aggregation
117
+ - `parsing.py` - logical entry detection and parsing
118
+ - `normalization.py` - message normalization
119
+ - `report_markdown.py` - Markdown report model and rendering
120
+ - `report_html.py` - HTML/CSS rendering
121
+ - `report_pdf.py` - PDF engine selection and fallback handling
122
+ - `converters.py` - Markdown-to-HTML/PDF conversion
123
+ - `cli.py` - command-line entry point
124
+
125
+ ## HTML/PDF conversion
126
+
127
+ Report conversion is intentionally optional:
128
+
129
+ - `report.md` remains the primary human-readable output
130
+ - `report.html` is generated from the internal report model
131
+ - `report.pdf` is created when supported PDF tooling is available
132
+
133
+ PDF engine preference order:
134
+
135
+ 1. `weasyprint`
136
+ 2. `wkhtmltopdf`
137
+ 3. `pandoc` + `xelatex` or `pdflatex`
138
+
139
+ If no supported PDF engine is available, the analysis still succeeds and generates Markdown and HTML output.
140
+
141
+ Windows-specific setup notes:
142
+
143
+ - `docs/install_gtk_weasyprint_windows.md`
144
+ - `docs/install_xelatex_windows.md`
145
+
146
+ ## Tests
147
+
148
+ ```powershell
149
+ pytest
150
+ ```
151
+
152
+ ## Local build
153
+
154
+ ```powershell
155
+ python -m build
156
+ ```
157
+
158
+ Expected artifacts:
159
+
160
+ - `dist/*.tar.gz`
161
+ - `dist/*.whl`
162
+
163
+ ## Notes
164
+
165
+ The package name on PyPI/TestPyPI is `nm-tool-forge`, while the current Python import package remains `loganalysis`.
166
+
167
+ This keeps the first public release small and low-risk. A later follow-up release can still rename the import package if desired.
@@ -0,0 +1,67 @@
1
+ [build-system]
2
+ requires = ["setuptools>=69", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "nm-tool-forge"
7
+ version = "0.1.0"
8
+ description = "Analyze MigMan log files and generate aggregated CSV, Markdown, HTML, and optional PDF reports."
9
+ readme = { file = "README.md", content-type = "text/markdown" }
10
+ requires-python = ">=3.10"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ authors = [
14
+ { name = "Stefan Ewald", email = "s.ew@outlook.de" }
15
+ ]
16
+ keywords = ["migman", "logs", "analysis", "reporting", "csv", "markdown", "pdf"]
17
+ classifiers = [
18
+ "Development Status :: 4 - Beta",
19
+ "Intended Audience :: Developers",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.10",
22
+ "Programming Language :: Python :: 3.11",
23
+ "Programming Language :: Python :: 3.12",
24
+ "Programming Language :: Python :: 3.13",
25
+ "Topic :: Software Development :: Libraries :: Python Modules",
26
+ "Topic :: Utilities",
27
+ ]
28
+
29
+ dependencies = [
30
+ "chardet>=5.0",
31
+ ]
32
+
33
+ [project.optional-dependencies]
34
+ pdf = [
35
+ "weasyprint>=62",
36
+ ]
37
+ dev = [
38
+ "pytest>=8.0",
39
+ "build>=1.2",
40
+ "twine>=5.0",
41
+ "ruff>=0.11",
42
+ ]
43
+
44
+ [project.urls]
45
+ Homepage = "https://github.com/Jack736-ui/migman_log"
46
+ Issues = "https://github.com/Jack736-ui/migman_log/issues"
47
+
48
+ [project.scripts]
49
+ nm-tool-forge = "loganalysis.cli:main"
50
+ loganalysis = "loganalysis.cli:main"
51
+
52
+ [tool.setuptools]
53
+ package-dir = { "" = "src" }
54
+
55
+ [tool.setuptools.packages.find]
56
+ where = ["src"]
57
+
58
+ [tool.pytest.ini_options]
59
+ testpaths = ["tests"]
60
+ addopts = "--basetemp=tests_tmp"
61
+
62
+ [tool.ruff]
63
+ line-length = 120
64
+ target-version = "py310"
65
+
66
+ [tool.ruff.lint]
67
+ select = ["E", "F", "I", "B", "UP"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,16 @@
1
+ from __future__ import annotations
2
+
3
+ from .analysis import analyze_file, run_analysis
4
+ from .converters import convert_report_md_to_html_pdf
5
+ from .normalization import normalize_message
6
+ from .parsing import iter_logical_entries
7
+
8
+ __all__ = [
9
+ "analyze_file",
10
+ "convert_report_md_to_html_pdf",
11
+ "iter_logical_entries",
12
+ "normalize_message",
13
+ "run_analysis",
14
+ ]
15
+
16
+ __version__ = "0.1.0"
@@ -0,0 +1,5 @@
1
+ from __future__ import annotations
2
+
3
+ from .cli import main
4
+
5
+ raise SystemExit(main())
@@ -0,0 +1,175 @@
1
+ from __future__ import annotations
2
+
3
+ from collections import Counter, defaultdict
4
+ from pathlib import Path
5
+
6
+ from .constants import DEFAULT_OUT_DIR, DEFAULT_TOP_EXAMPLES, EXIT_NO_LOG_FILES
7
+ from .csv_export import write_csv
8
+ from .encoding import count_physical_lines, detect_encoding
9
+ from .filesystem import backup_file, ensure_dir
10
+ from .models import AnalysisConfig, AnalysisRunResult, AnalysisSummary, FileAnalysis, MessageKey
11
+ from .normalization import normalize_message
12
+ from .parsing import iter_logical_entries, parse_entry
13
+ from .report_markdown import build_markdown_report
14
+
15
+
16
+ class NoLogFilesError(FileNotFoundError):
17
+ """Raised when no matching log files are found for an analysis run."""
18
+
19
+ exit_code = EXIT_NO_LOG_FILES
20
+
21
+
22
+ def analyze_file(file_path: Path) -> FileAnalysis:
23
+ """Analyze one log file and aggregate raw and normalized message counts."""
24
+
25
+ raw_counts: Counter[MessageKey] = Counter()
26
+ norm_counts: Counter[MessageKey] = Counter()
27
+ norm_examples: dict[MessageKey, Counter[str]] = defaultdict(Counter)
28
+
29
+ unknown_lines = 0
30
+ total_entries = 0
31
+
32
+ encoding = detect_encoding(file_path)
33
+ total_lines = count_physical_lines(file_path, encoding=encoding)
34
+
35
+ for entry in iter_logical_entries(file_path, encoding=encoding):
36
+ total_entries += 1
37
+ parsed = parse_entry(entry)
38
+ if not parsed:
39
+ continue
40
+
41
+ severity = parsed.severity
42
+ message = parsed.message
43
+
44
+ if severity == "UNKNOWN":
45
+ unknown_lines += 1
46
+
47
+ raw_counts[(severity, message)] += 1
48
+
49
+ normalized_message = normalize_message(message)
50
+ norm_key = (severity, normalized_message)
51
+ norm_counts[norm_key] += 1
52
+ norm_examples[norm_key][message] += 1
53
+
54
+ return FileAnalysis(
55
+ file=file_path,
56
+ total_lines=total_lines,
57
+ total_entries=total_entries,
58
+ unknown_lines=unknown_lines,
59
+ raw_counts=raw_counts,
60
+ norm_counts=norm_counts,
61
+ norm_examples=dict(norm_examples),
62
+ )
63
+
64
+
65
+ def sorted_rows(counter: Counter[MessageKey]) -> list[tuple[str, str, int]]:
66
+ """Return aggregated rows in a stable severity/count/message order."""
67
+
68
+ return [
69
+ (severity, message, count)
70
+ for (severity, message), count in sorted(
71
+ counter.items(),
72
+ key=lambda item: (item[0][0], -item[1], item[0][1]),
73
+ )
74
+ ]
75
+
76
+
77
+ def find_log_files(logs_dir: Path) -> list[Path]:
78
+ """Return all supported log files from the configured logs directory."""
79
+
80
+ return sorted(logs_dir.glob("*.txt"))
81
+
82
+
83
+ def build_default_config(
84
+ *,
85
+ logs_dir: Path,
86
+ out_dir: Path | None = None,
87
+ backup_dir: Path | None = None,
88
+ top_examples: int = DEFAULT_TOP_EXAMPLES,
89
+ convert: bool = False,
90
+ ) -> AnalysisConfig:
91
+ """Build an analysis configuration with default output locations."""
92
+
93
+ resolved_out_dir = out_dir or Path(DEFAULT_OUT_DIR)
94
+ resolved_backup_dir = backup_dir or (resolved_out_dir / "backup")
95
+ return AnalysisConfig(
96
+ logs_dir=logs_dir,
97
+ out_dir=resolved_out_dir,
98
+ backup_dir=resolved_backup_dir,
99
+ top_examples=top_examples,
100
+ convert=convert,
101
+ )
102
+
103
+
104
+ def run_analysis(config: AnalysisConfig) -> AnalysisRunResult:
105
+ """Run the full analysis workflow and write CSV and report outputs."""
106
+
107
+ logs_dir = config.logs_dir
108
+ out_dir = config.out_dir
109
+ backup_dir = config.backup_dir or (out_dir / "backup")
110
+
111
+ ensure_dir(out_dir)
112
+ ensure_dir(backup_dir)
113
+
114
+ log_files = find_log_files(logs_dir)
115
+ if not log_files:
116
+ raise NoLogFilesError(f"No *.txt files found in: {logs_dir.resolve()}")
117
+
118
+ summary = AnalysisSummary(
119
+ analyses=[],
120
+ global_raw=Counter(),
121
+ global_norm=Counter(),
122
+ global_norm_examples={},
123
+ )
124
+
125
+ for log_file in log_files:
126
+ backup_path = backup_file(log_file, backup_dir)
127
+ analysis = analyze_file(log_file)
128
+ analysis.backup_path = backup_path
129
+ summary.analyses.append(analysis)
130
+
131
+ summary.global_raw.update(analysis.raw_counts)
132
+ summary.global_norm.update(analysis.norm_counts)
133
+ for key, counter in analysis.norm_examples.items():
134
+ summary.global_norm_examples.setdefault(key, Counter()).update(counter)
135
+
136
+ write_csv(
137
+ out_dir / f"{log_file.stem}.aggregated.csv",
138
+ sorted_rows(analysis.raw_counts),
139
+ headers=["SEVERITY", "MESSAGE", "COUNT"],
140
+ )
141
+ write_csv(
142
+ out_dir / f"{log_file.stem}.aggregated.normalized.csv",
143
+ sorted_rows(analysis.norm_counts),
144
+ headers=["SEVERITY", "MESSAGE_NORMALIZED", "COUNT"],
145
+ )
146
+
147
+ write_csv(
148
+ out_dir / "summary.all_files.csv",
149
+ sorted_rows(summary.global_raw),
150
+ headers=["SEVERITY", "MESSAGE", "COUNT"],
151
+ )
152
+ write_csv(
153
+ out_dir / "summary.all_files.normalized.csv",
154
+ sorted_rows(summary.global_norm),
155
+ headers=["SEVERITY", "MESSAGE_NORMALIZED", "COUNT"],
156
+ )
157
+
158
+ report_path = out_dir / "report.md"
159
+ report_path.write_text(build_markdown_report(summary, config), encoding="utf-8", newline="\n")
160
+
161
+ result = AnalysisRunResult(
162
+ out_dir=out_dir,
163
+ backup_dir=backup_dir,
164
+ report_path=report_path,
165
+ summary=summary,
166
+ )
167
+
168
+ if config.convert:
169
+ from .converters import convert_report_md_to_html_pdf
170
+
171
+ result.html_path = out_dir / "report.html"
172
+ result.pdf_path = out_dir / "report.pdf"
173
+ result.convert_status = convert_report_md_to_html_pdf(result.report_path, result.html_path, result.pdf_path)
174
+
175
+ return result