"""Export des donnees du dashboard en formats structures.""" from __future__ import annotations import json import re from dataclasses import asdict from gitea_dashboard.collector import MilestoneData, RepoData # Caracteres de controle ASCII (0x00-0x1F) sauf \t (0x09), \n (0x0A), \r (0x0D) _CONTROL_CHAR_RE = re.compile(r"[\x00-\x08\x0b\x0c\x0e-\x1f]") def _sanitize_control_chars(text: str) -> str: """Supprime les caracteres de controle ASCII (0x00-0x1F) sauf \\n, \\r et \\t. Ces caracteres peuvent provenir de descriptions de repos Gitea et causent des erreurs JSON ('Invalid control character'). """ return _CONTROL_CHAR_RE.sub("", text) def repos_to_dicts(repos: list[RepoData]) -> list[dict]: """Convertit une liste de RepoData en liste de dicts serialisables. Sanitize les champs texte (name, full_name, description) pour supprimer les caracteres de controle invalides en JSON. """ result = [] for repo in repos: d = asdict(repo) for field in ("name", "full_name", "description"): if isinstance(d.get(field), str): d[field] = _sanitize_control_chars(d[field]) result.append(d) return result def export_json(repos: list[RepoData], indent: int = 2) -> str: """Exporte les repos en JSON formate. Returns: Chaine JSON indentee, prete pour stdout ou ecriture fichier. """ return json.dumps(repos_to_dicts(repos), indent=indent, ensure_ascii=False) def milestones_to_dicts(milestones: list[MilestoneData]) -> list[dict]: """Convertit une liste de MilestoneData en liste de dicts serialisables. Sanitize les champs texte (repo_name, title) pour les caracteres de controle. """ result = [] for ms in milestones: d = asdict(ms) for field in ("repo_name", "title"): if isinstance(d.get(field), str): d[field] = _sanitize_control_chars(d[field]) result.append(d) return result def export_milestones_json(milestones: list[MilestoneData], indent: int = 2) -> str: """Exporte les milestones en JSON formate.""" return json.dumps(milestones_to_dicts(milestones), indent=indent, ensure_ascii=False)