reminder-aggregator 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.
- reminder_aggregator-0.1.0/PKG-INFO +42 -0
- reminder_aggregator-0.1.0/README.md +32 -0
- reminder_aggregator-0.1.0/pyproject.toml +42 -0
- reminder_aggregator-0.1.0/src/reminder_aggregator/__init__.py +0 -0
- reminder_aggregator-0.1.0/src/reminder_aggregator/__main__.py +4 -0
- reminder_aggregator-0.1.0/src/reminder_aggregator/cli.py +144 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: reminder-aggregator
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A simple cli application that aggregates codereminder tags like TODO, etc. in a report
|
|
5
|
+
Requires-Dist: click>=8.3.0
|
|
6
|
+
Requires-Dist: pathspec==0.12.1
|
|
7
|
+
Requires-Dist: pip==25.2
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
|
|
11
|
+
# Reminder Aggregator
|
|
12
|
+
|
|
13
|
+
A simple python tool that scans files in a directory for common reminder tags such as `TODO`, `FIXME`, etc. and generates a report from them.
|
|
14
|
+
|
|
15
|
+
## Requirements
|
|
16
|
+
|
|
17
|
+
- Python 3.13+
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
With [uv](https://docs.astral.sh/uv/getting-started/installation/) (recommended):
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
uv sync
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
reminder_aggregator --help
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Future Changes
|
|
34
|
+
|
|
35
|
+
- Filter to check whether a reminder-tag is inside a comment (currently causes false positives)
|
|
36
|
+
- Support for multiple output formats
|
|
37
|
+
|
|
38
|
+
## License
|
|
39
|
+
|
|
40
|
+
GNU General Public License v3.0 or later
|
|
41
|
+
|
|
42
|
+
See [LICENSE](./LICENSE) to see the full text.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Reminder Aggregator
|
|
2
|
+
|
|
3
|
+
A simple python tool that scans files in a directory for common reminder tags such as `TODO`, `FIXME`, etc. and generates a report from them.
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
- Python 3.13+
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
With [uv](https://docs.astral.sh/uv/getting-started/installation/) (recommended):
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
uv sync
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
reminder_aggregator --help
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Future Changes
|
|
24
|
+
|
|
25
|
+
- Filter to check whether a reminder-tag is inside a comment (currently causes false positives)
|
|
26
|
+
- Support for multiple output formats
|
|
27
|
+
|
|
28
|
+
## License
|
|
29
|
+
|
|
30
|
+
GNU General Public License v3.0 or later
|
|
31
|
+
|
|
32
|
+
See [LICENSE](./LICENSE) to see the full text.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "reminder-aggregator"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "A simple cli application that aggregates codereminder tags like TODO, etc. in a report"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.10"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"click>=8.3.0",
|
|
9
|
+
"pathspec==0.12.1",
|
|
10
|
+
"pip==25.2",
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
[project.scripts]
|
|
14
|
+
reminder-aggregator = "reminder_aggregator.cli:cli"
|
|
15
|
+
|
|
16
|
+
[build-system]
|
|
17
|
+
requires = ["uv_build>=0.9.2,<0.10.0"]
|
|
18
|
+
build-backend = "uv_build"
|
|
19
|
+
|
|
20
|
+
[tool.ruff]
|
|
21
|
+
line-length = 120
|
|
22
|
+
indent-width = 4
|
|
23
|
+
|
|
24
|
+
[tool.ruff.format]
|
|
25
|
+
quote-style = "double"
|
|
26
|
+
indent-style = "space"
|
|
27
|
+
line-ending = "auto"
|
|
28
|
+
|
|
29
|
+
[tool.ruff.lint]
|
|
30
|
+
select = [
|
|
31
|
+
"F", # pyflakes
|
|
32
|
+
"E", # pycodestyle
|
|
33
|
+
"I", # isort
|
|
34
|
+
"ANN", # flake8 type annotations
|
|
35
|
+
"RUF", # ruff-specific rules
|
|
36
|
+
]
|
|
37
|
+
fixable = ["ALL"]
|
|
38
|
+
|
|
39
|
+
[dependency-groups]
|
|
40
|
+
dev = [
|
|
41
|
+
"ruff>=0.14.0",
|
|
42
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import pathlib
|
|
4
|
+
import re
|
|
5
|
+
from typing import Any, Counter
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
import pathspec
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _write_report(filename: str, data: list[dict[str, Any]]) -> None:
|
|
12
|
+
counter = Counter(match["type"].upper() for match in data)
|
|
13
|
+
summary = dict(counter)
|
|
14
|
+
summary["total"] = sum(counter.values())
|
|
15
|
+
|
|
16
|
+
report = {
|
|
17
|
+
"summary": summary,
|
|
18
|
+
"details": data,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
with open(filename, "w", encoding="utf-8") as file:
|
|
22
|
+
json.dump(report, file, indent=2)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _parse_file(path: pathlib.Path, path_root: pathlib.Path, pattern: re.Pattern) -> list[dict[str, Any]]:
|
|
26
|
+
# FIXME: Ensure that only comments count to matches.
|
|
27
|
+
matches: list[dict[str, Any]] = []
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
for line_number, line in enumerate(open(path)):
|
|
31
|
+
line = line.strip()
|
|
32
|
+
|
|
33
|
+
if match := re.search(pattern, line):
|
|
34
|
+
matches.append(
|
|
35
|
+
{
|
|
36
|
+
"type": match.group(1),
|
|
37
|
+
"file": str(path.relative_to(path_root)),
|
|
38
|
+
"line": line_number + 1,
|
|
39
|
+
"pos": match.start(1),
|
|
40
|
+
"content": line.strip(),
|
|
41
|
+
}
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
except UnicodeDecodeError:
|
|
45
|
+
print(f"Error reading {path}")
|
|
46
|
+
|
|
47
|
+
return matches
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _parse_directory(
|
|
51
|
+
directory: pathlib.Path,
|
|
52
|
+
path_root: pathlib.Path,
|
|
53
|
+
match_regex: re.Pattern,
|
|
54
|
+
ignore_spec: pathspec.PathSpec,
|
|
55
|
+
) -> list[dict[str, Any]]:
|
|
56
|
+
matches: list[dict[str, Any]] = []
|
|
57
|
+
|
|
58
|
+
for path in directory.rglob("*"):
|
|
59
|
+
if not path.is_file():
|
|
60
|
+
continue
|
|
61
|
+
|
|
62
|
+
if ignore_spec.match_file(path):
|
|
63
|
+
continue
|
|
64
|
+
|
|
65
|
+
match = _parse_file(path, path_root, match_regex)
|
|
66
|
+
|
|
67
|
+
if len(match) == 0:
|
|
68
|
+
continue
|
|
69
|
+
|
|
70
|
+
matches.extend(match)
|
|
71
|
+
|
|
72
|
+
return matches
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _load_ignore_spec(file_path: str) -> pathspec.PathSpec:
|
|
76
|
+
file = pathlib.Path(file_path)
|
|
77
|
+
|
|
78
|
+
if not file.is_file():
|
|
79
|
+
return pathspec.PathSpec.from_lines("gitwildmatch", [])
|
|
80
|
+
|
|
81
|
+
with open(file, "r", encoding="utf-8") as file:
|
|
82
|
+
return pathspec.PathSpec.from_lines("gitwildmatch", file)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
CONTEXT_SETTINGS = {"max_content_width": os.get_terminal_size().columns - 10}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@click.group(context_settings=CONTEXT_SETTINGS)
|
|
89
|
+
def cli() -> None:
|
|
90
|
+
pass
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@cli.command("report", short_help="Generate a report")
|
|
94
|
+
@click.option(
|
|
95
|
+
"--out-file",
|
|
96
|
+
"-o",
|
|
97
|
+
default="report.json",
|
|
98
|
+
show_default=True,
|
|
99
|
+
type=click.Path(),
|
|
100
|
+
help=" Specify path where the report will be saved",
|
|
101
|
+
)
|
|
102
|
+
@click.option(
|
|
103
|
+
"--format",
|
|
104
|
+
"-f",
|
|
105
|
+
default="json",
|
|
106
|
+
show_default=True,
|
|
107
|
+
type=click.Choice(["json"]),
|
|
108
|
+
help="Specify the format of the generated report",
|
|
109
|
+
)
|
|
110
|
+
@click.option(
|
|
111
|
+
"--ignore-file",
|
|
112
|
+
default=".gitignore",
|
|
113
|
+
show_default=True,
|
|
114
|
+
type=click.Path(exists=True),
|
|
115
|
+
help="Specify ignore file to use",
|
|
116
|
+
)
|
|
117
|
+
@click.argument("path", envvar="RA_SEARCH_DIR", default=".", type=click.Path())
|
|
118
|
+
def report(path: str, out_file: str, format: str, ignore_file: str) -> None:
|
|
119
|
+
"""
|
|
120
|
+
\b
|
|
121
|
+
positional arguments:
|
|
122
|
+
PATH Specify the path that will be scanned [default: .]
|
|
123
|
+
"""
|
|
124
|
+
# TODO: Add support for multiple output formats (junitxml, json, etc.)
|
|
125
|
+
|
|
126
|
+
path_root: pathlib.Path = pathlib.Path("./")
|
|
127
|
+
search_directory: pathlib.Path = pathlib.Path(path)
|
|
128
|
+
|
|
129
|
+
output_path: str = out_file
|
|
130
|
+
|
|
131
|
+
re_match_str: str = r"(TODO|FIXME|HACK|OPTIMIZE|REVIEW)"
|
|
132
|
+
re_match: re.Pattern = re.compile(re_match_str)
|
|
133
|
+
|
|
134
|
+
matches: list[dict[str, Any]] = []
|
|
135
|
+
|
|
136
|
+
ignore_spec: pathspec.PathSpec = _load_ignore_spec(ignore_file)
|
|
137
|
+
|
|
138
|
+
matches = _parse_directory(search_directory, path_root, re_match, ignore_spec)
|
|
139
|
+
|
|
140
|
+
_write_report(output_path, matches)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
if __name__ == "__main__":
|
|
144
|
+
cli()
|