feat: JSON, Excel export and time summary
- #23: add JSONFormatter and XLSXFormatter - add openpyxl dependency for .xlsx export - #22: add --summary flag and calculate_summary() in report_builder - ReportRow now carries raw hours for summary calculations - update CLI help and README with .json/.xlsx formats and --summary - add tests for new formatters and summary computation Closes #22, closes #23
This commit is contained in:
@@ -4,15 +4,19 @@ from .base import Formatter
|
||||
from .console import CompactFormatter, TableFormatter
|
||||
from .csv import CSVFormatter
|
||||
from .html import HTMLFormatter
|
||||
from .json import JSONFormatter
|
||||
from .markdown import MarkdownFormatter
|
||||
from .xlsx import XLSXFormatter
|
||||
|
||||
# Словарь для сопоставления расширений файлов с классами форматтеров.
|
||||
# ODT намеренно отсутствует — его импорт отложен (ленивый), так как odfpy
|
||||
# может быть не установлен. См. get_formatter_by_extension.
|
||||
# ODT и XLSX намеренно отсутствуют — их импорт отложен (ленивый), так как
|
||||
# odfpy/openpyxl может быть не установлен. См. get_formatter_by_extension.
|
||||
FORMATTER_MAP: Dict[str, Type[Formatter]] = {
|
||||
".csv": CSVFormatter,
|
||||
".json": JSONFormatter,
|
||||
".md": MarkdownFormatter,
|
||||
".html": HTMLFormatter,
|
||||
".xlsx": XLSXFormatter,
|
||||
}
|
||||
|
||||
|
||||
|
||||
31
redmine_reporter/formatters/json.py
Normal file
31
redmine_reporter/formatters/json.py
Normal file
@@ -0,0 +1,31 @@
|
||||
import json
|
||||
from typing import List
|
||||
|
||||
from ..types import ReportRow
|
||||
from .base import Formatter
|
||||
|
||||
|
||||
class JSONFormatter(Formatter):
|
||||
"""Форматтер для экспорта отчёта в JSON."""
|
||||
|
||||
def __init__(self, **_kwargs):
|
||||
super().__init__()
|
||||
|
||||
def format(self, rows: List[ReportRow]) -> str:
|
||||
data = [
|
||||
{
|
||||
"project": r["project"],
|
||||
"version": r["version"],
|
||||
"issue_id": r["issue_id"],
|
||||
"subject": r["subject"],
|
||||
"status": r["status_ru"],
|
||||
"time": r["time_text"],
|
||||
}
|
||||
for r in rows
|
||||
]
|
||||
return json.dumps(data, ensure_ascii=False, indent=2)
|
||||
|
||||
def save(self, rows: List[ReportRow], output_path: str) -> None:
|
||||
content = self.format(rows)
|
||||
with open(output_path, "w", encoding="utf-8") as f:
|
||||
f.write(content)
|
||||
42
redmine_reporter/formatters/xlsx.py
Normal file
42
redmine_reporter/formatters/xlsx.py
Normal file
@@ -0,0 +1,42 @@
|
||||
from typing import List
|
||||
|
||||
from openpyxl import Workbook
|
||||
from openpyxl.styles import Font
|
||||
|
||||
from ..types import ReportRow
|
||||
from .base import Formatter
|
||||
|
||||
|
||||
class XLSXFormatter(Formatter):
|
||||
"""Форматтер для экспорта отчёта в Excel (.xlsx)."""
|
||||
|
||||
def __init__(self, **_kwargs):
|
||||
super().__init__()
|
||||
|
||||
def format(self, rows: List[ReportRow]) -> Workbook:
|
||||
wb = Workbook()
|
||||
ws = wb.active
|
||||
ws.title = "Report"
|
||||
|
||||
headers = ["Project", "Version", "Issue ID", "Subject", "Status", "Spent Time"]
|
||||
ws.append(headers)
|
||||
for cell in ws[1]:
|
||||
cell.font = Font(bold=True)
|
||||
|
||||
for r in rows:
|
||||
ws.append(
|
||||
[
|
||||
r["project"],
|
||||
r["version"],
|
||||
r["issue_id"],
|
||||
r["subject"],
|
||||
r["status_ru"],
|
||||
r["time_text"],
|
||||
]
|
||||
)
|
||||
|
||||
return wb
|
||||
|
||||
def save(self, rows: List[ReportRow], output_path: str) -> None:
|
||||
wb = self.format(rows)
|
||||
wb.save(output_path)
|
||||
Reference in New Issue
Block a user