model.py 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. from __future__ import annotations
  2. import abc
  3. import re
  4. from dataclasses import dataclass
  5. from typing import Callable, Iterator, List, Optional
  6. import docutils.core
  7. from nicegui.dataclasses import KWONLY_SLOTS
  8. from nicegui.elements.markdown import apply_tailwind, remove_indentation
  9. @dataclass(**KWONLY_SLOTS)
  10. class DocumentationPart:
  11. title: Optional[str] = None
  12. description: Optional[str] = None
  13. link: Optional[str] = None
  14. function: Optional[Callable] = None
  15. class Documentation(abc.ABC):
  16. TITLE: Optional[str] = None
  17. def __init__(self, route: str, back_link: Optional[str] = None) -> None:
  18. self.route = route
  19. self.back_link = back_link
  20. self._content: List[DocumentationPart] = []
  21. self.content()
  22. def __iter__(self) -> Iterator[DocumentationPart]:
  23. return iter(self._content)
  24. def add_markdown(self, title: str, description: str) -> None:
  25. """Add a markdown section to the documentation."""
  26. self._content.append(DocumentationPart(title=title, description=description))
  27. def add_markdown_demo(self, title: str, description: str) -> Callable[[Callable], Callable]:
  28. """Add a markdown section to the documentation."""
  29. def decorator(function: Callable) -> Callable:
  30. self._content.append(DocumentationPart(title=title, description=description, function=function))
  31. return function
  32. return decorator
  33. def add_element_intro(self, documentation: ElementDocumentation) -> None:
  34. """Add an element intro section to the documentation."""
  35. self.add_main_element_demo(documentation, intro_only=True)
  36. def add_main_element_demo(self, documentation: ElementDocumentation, *, intro_only: bool = False) -> None:
  37. """Add a demo section for an element to the documentation."""
  38. title, doc = documentation.element.__init__.__doc__.split('\n', 1) # type: ignore
  39. doc = remove_indentation(doc).replace('param ', '')
  40. html = apply_tailwind(docutils.core.publish_parts(doc, writer_name='html5_polyglot')['html_body'])
  41. if intro_only:
  42. html = re.sub(r'<dl class=".* simple">.*?</dl>', '', html, flags=re.DOTALL)
  43. self._content.append(DocumentationPart(
  44. title=title,
  45. description=html,
  46. link=documentation.route if intro_only else None,
  47. function=documentation.main_demo,
  48. ))
  49. def add_raw_nicegui(self, function: Callable) -> Callable:
  50. """Add a raw NiceGUI section to the documentation."""
  51. self._content.append(DocumentationPart(function=function))
  52. return function
  53. @abc.abstractmethod
  54. def content(self) -> None:
  55. """Add documentation content here."""
  56. class SectionDocumentation(Documentation):
  57. element_documentations: List[ElementDocumentation]
  58. def __init_subclass__(cls, title: str) -> None:
  59. cls.TITLE = title
  60. cls.element_documentations = []
  61. return super().__init_subclass__()
  62. def add_element_intro(self, documentation: ElementDocumentation) -> None:
  63. self.element_documentations.append(documentation)
  64. super().add_element_intro(documentation)
  65. class ElementDocumentation(Documentation):
  66. element: type
  67. def __init_subclass__(cls, element: type) -> None:
  68. cls.element = element
  69. return super().__init_subclass__()
  70. def __init__(self) -> None:
  71. super().__init__(self.element.__name__.lower())
  72. @abc.abstractmethod
  73. def main_demo(self) -> None:
  74. """Add a demo for the element here."""
  75. def more_demos(self) -> None:
  76. """Add more demos for the element here."""
  77. def content(self) -> None:
  78. self.add_main_element_demo(self)
  79. self.more_demos()