model.py 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  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. from . import registry
  10. @dataclass(**KWONLY_SLOTS)
  11. class DocumentationPart:
  12. title: Optional[str] = None
  13. description: Optional[str] = None
  14. link: Optional[str] = None
  15. function: Optional[Callable] = None
  16. @property
  17. def link_target(self) -> Optional[str]:
  18. """Return the link target for in-page navigation."""
  19. return self.title.lower().replace(' ', '_') if self.title else None
  20. class Documentation(abc.ABC):
  21. def __init__(self,
  22. route: str, *,
  23. title: str,
  24. subtitle: str,
  25. back_link: Optional[str] = None) -> None:
  26. self.route = route
  27. self.title = title
  28. self.subtitle = subtitle
  29. self.back_link = back_link
  30. self._content: List[DocumentationPart] = []
  31. self.content()
  32. registry.add(self)
  33. def __iter__(self) -> Iterator[DocumentationPart]:
  34. return iter(self._content)
  35. def __len__(self) -> int:
  36. return len(self._content)
  37. def add_markdown(self, title: str, description: str) -> None:
  38. """Add a markdown section to the documentation."""
  39. self._content.append(DocumentationPart(title=title, description=description))
  40. def add_markdown_demo(self, title: str, description: str) -> Callable[[Callable], Callable]:
  41. """Add a markdown section to the documentation."""
  42. def decorator(function: Callable) -> Callable:
  43. self._content.append(DocumentationPart(title=title, description=description, function=function))
  44. return function
  45. return decorator
  46. def add_element_intro(self, documentation: UiElementDocumentation) -> None:
  47. """Add an element intro section to the documentation."""
  48. documentation.back_link = self.route
  49. self.add_main_element_demo(documentation, intro_only=True)
  50. def add_main_element_demo(self, documentation: UiElementDocumentation, *, intro_only: bool = False) -> None:
  51. """Add a demo section for an element to the documentation."""
  52. title, doc = documentation.element.__init__.__doc__.split('\n', 1) # type: ignore
  53. doc = remove_indentation(doc).replace('param ', '')
  54. html = apply_tailwind(docutils.core.publish_parts(doc, writer_name='html5_polyglot')['html_body'])
  55. if intro_only:
  56. html = re.sub(r'<dl class=".* simple">.*?</dl>', '', html, flags=re.DOTALL)
  57. self._content.append(DocumentationPart(
  58. title=title,
  59. description=html,
  60. link=documentation.route if intro_only else None,
  61. function=documentation.main_demo,
  62. ))
  63. def add_raw_nicegui(self, function: Callable) -> Callable:
  64. """Add a raw NiceGUI section to the documentation."""
  65. self._content.append(DocumentationPart(function=function))
  66. return function
  67. @abc.abstractmethod
  68. def content(self) -> None:
  69. """Add documentation content here."""
  70. class SectionDocumentation(Documentation):
  71. _title: str
  72. _route: str
  73. def __init_subclass__(cls, title: str, name: str) -> None:
  74. cls._title = title
  75. cls._route = f'/documentation/section_{name}'
  76. return super().__init_subclass__()
  77. def __init__(self) -> None:
  78. super().__init__(self._route, subtitle='Documentation', title=self._title, back_link='/documentation')
  79. class UiElementDocumentation(Documentation):
  80. _element: type
  81. def __init_subclass__(cls, element: type) -> None:
  82. cls._element = element
  83. return super().__init_subclass__()
  84. def __init__(self) -> None:
  85. self.element = self._element
  86. name = self.element.__name__.lower()
  87. super().__init__(f'/documentation/{name}', subtitle='Documentation', title=f'ui.*{name}*')
  88. @abc.abstractmethod
  89. def main_demo(self) -> None:
  90. """Add a demo for the element here."""
  91. def more(self) -> None:
  92. """Add more demos for the element here."""
  93. def content(self) -> None:
  94. self.add_main_element_demo(self)
  95. self.more()