text_utils.py 2.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172
  1. """Outils de mise en forme du texte pour Discord."""
  2. from __future__ import annotations
  3. import re
  4. DISCORD_LIMIT = 2000
  5. _FENCE_RE = re.compile(r"^\s*```(\S*)")
  6. def split_message(text: str, limit: int = DISCORD_LIMIT) -> list[str]:
  7. """Découpe un long texte en morceaux de `limit` caractères maximum.
  8. - coupe de préférence entre les lignes, puis entre les mots ;
  9. - si un bloc de code (```) est coupé, il est refermé à la fin du morceau
  10. et rouvert (avec le même langage) au début du suivant.
  11. """
  12. text = text.strip()
  13. if not text:
  14. return []
  15. if len(text) <= limit:
  16. return [text]
  17. chunks: list[str] = []
  18. current = ""
  19. open_fence: str | None = None # ligne d'ouverture du bloc de code en cours, ex. "```python"
  20. def closing_len() -> int:
  21. return len("\n```") if open_fence is not None else 0
  22. def flush() -> None:
  23. nonlocal current
  24. body = current.rstrip("\n")
  25. if open_fence is not None:
  26. body += "\n```"
  27. if body.strip():
  28. chunks.append(body)
  29. current = (open_fence + "\n") if open_fence is not None else ""
  30. for line in text.split("\n"):
  31. # Une ligne trop longue à elle seule est découpée en morceaux plus petits.
  32. for piece in _split_long_line(line, limit - 10 - (len(open_fence) if open_fence else 0)):
  33. addition = piece + "\n"
  34. if len(current) + len(addition) + closing_len() > limit:
  35. flush()
  36. current += addition
  37. match = _FENCE_RE.match(line)
  38. if match:
  39. open_fence = None if open_fence is not None else line.strip()
  40. # Dernier morceau : pas de bloc à refermer artificiellement s'il est déjà fermé.
  41. body = current.rstrip("\n")
  42. if body.strip() and body.strip() != (open_fence or ""):
  43. chunks.append(body)
  44. return chunks
  45. def _split_long_line(line: str, limit: int) -> list[str]:
  46. if len(line) <= limit:
  47. return [line]
  48. pieces: list[str] = []
  49. while len(line) > limit:
  50. cut = line.rfind(" ", 0, limit)
  51. if cut <= limit // 2: # pas d'espace raisonnable : coupe nette
  52. cut = limit
  53. pieces.append(line[:cut].rstrip())
  54. line = line[cut:].lstrip()
  55. if line:
  56. pieces.append(line)
  57. return pieces