model.py 3.9 KB

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