From 245ea0a3fa74a55b79b3cb597d0ea2408cc74d8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9A=D0=BE=D0=BA=D0=BE=D1=81=20=D0=90=D1=80=D1=82=D0=B5?= =?UTF-8?q?=D0=BC=20=D0=9D=D0=B8=D0=BA=D0=BE=D0=BB=D0=B0=D0=B5=D0=B2=D0=B8?= =?UTF-8?q?=D1=87?= Date: Thu, 22 Jan 2026 16:52:18 +0700 Subject: [PATCH] Add Markdown format Closes #6 --- .gitignore | 1 + README.md | 2 +- redmine_reporter/cli.py | 11 +++++++--- redmine_reporter/formatter_md.py | 35 ++++++++++++++++++++++++++++++++ 4 files changed, 45 insertions(+), 4 deletions(-) create mode 100644 redmine_reporter/formatter_md.py diff --git a/.gitignore b/.gitignore index a65102e..0fce242 100644 --- a/.gitignore +++ b/.gitignore @@ -90,3 +90,4 @@ secrets.json .~* report.odt report.csv +report.md diff --git a/README.md b/README.md index 27004b4..ca1b0da 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ - Перевод статусов на русский язык - Простой CLI с понятными аргументами - Поддержка настройки диапазона дат по умолчанию через `.env` -- Экспорт в ODT с автоматическим заголовком (автор + месяц) +- Экспорт в ODT, CSV и Markdown --- diff --git a/redmine_reporter/cli.py b/redmine_reporter/cli.py index aee2f97..0602a26 100644 --- a/redmine_reporter/cli.py +++ b/redmine_reporter/cli.py @@ -8,6 +8,7 @@ from .client import fetch_issues_with_spent_time from .formatter import format_compact, format_table from .formatter_odt import format_odt from .formatter_csv import format_csv +from .formatter_md import format_md def parse_date_range(date_arg: str) -> tuple[str, str]: @@ -76,8 +77,8 @@ def main(argv: Optional[List[str]] = None) -> int: print(f"✅ Total issues: {len(issue_hours)} [{args.date}]") if args.output: - if not (args.output.endswith(".odt") or args.output.endswith(".csv")): - print("❌ Output file must end with .odt or .csv", file=sys.stderr) + if not (args.output.endswith(".odt") or args.output.endswith(".csv") or args.output.endswith(".md")): + print("❌ Output file must end with .odt, .csv or .md", file=sys.stderr) return 1 try: @@ -94,6 +95,10 @@ def main(argv: Optional[List[str]] = None) -> int: csv_content = format_csv(issue_hours, fill_time=not args.no_time) with open(args.output, "w", encoding="utf-8", newline="") as f: f.write(csv_content) + elif args.output.endswith(".md"): + md_content = format_md(issue_hours, fill_time=not args.no_time) + with open(args.output, "w", encoding="utf-8") as f: + f.write(md_content) print(f"✅ Report saved to {args.output}") except ImportError as e: @@ -103,7 +108,7 @@ def main(argv: Optional[List[str]] = None) -> int: print(f"❌ Import error: {e}", file=sys.stderr) return 1 except Exception as e: - fmt = "ODT" if args.output.endswith(".odt") else "CSV" + fmt = "ODT" if args.output.endswith(".odt") else ("CSV" if args.output.endswith(".csv") else "Markdown") print(f"❌ {fmt} export error: {e}", file=sys.stderr) return 1 else: diff --git a/redmine_reporter/formatter_md.py b/redmine_reporter/formatter_md.py new file mode 100644 index 0000000..cf20f0a --- /dev/null +++ b/redmine_reporter/formatter_md.py @@ -0,0 +1,35 @@ +from typing import List, Tuple +from redminelib.resources import Issue +from .formatter import get_version, hours_to_human, STATUS_TRANSLATION + + +def format_md(issue_hours: List[Tuple[Issue, float]], fill_time: bool = True) -> str: + """ + Formats the list of issues with spent time into a Markdown table. + Returns a string containing the Markdown content. + """ + + lines = [] + lines.append("| Проект | Версия | Задача | Статус | Затрачено |") + lines.append("|--------|--------|--------|--------|-----------|") + + prev_project = None + prev_version = None + + for issue, hours in issue_hours: + project = str(issue.project) + version = get_version(issue) + status_en = str(issue.status) + status_ru = STATUS_TRANSLATION.get(status_en, status_en) + time_text = hours_to_human(hours) if fill_time else "" + + display_project = project if project != prev_project else "" + display_version = version if (project != prev_project or version != prev_version) else "" + + task_cell = f"{issue.id}. {issue.subject}" + lines.append(f"| {display_project} | {display_version} | {task_cell} | {status_ru} | {time_text} |") + + prev_project = project + prev_version = version + + return "\n".join(lines)