api.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. from __future__ import annotations
  2. import inspect
  3. import sys
  4. import types
  5. from copy import deepcopy
  6. from pathlib import Path
  7. from types import ModuleType
  8. from typing import Any, Callable, Dict, Optional, Union, overload
  9. from nicegui import app as nicegui_app
  10. from nicegui import ui as nicegui_ui
  11. from nicegui.elements.markdown import remove_indentation
  12. from .page import DocumentationPage
  13. from .part import Demo, DocumentationPart
  14. registry: Dict[str, DocumentationPage] = {}
  15. redirects: Dict[str, str] = {}
  16. def get_page(documentation: ModuleType) -> DocumentationPage:
  17. """Return the documentation page for the given documentation module."""
  18. target_name = _removesuffix(documentation.__name__.split('.')[-1], '_documentation')
  19. assert target_name in registry, f'Documentation page {target_name} does not exist'
  20. return registry[target_name]
  21. def _get_current_page() -> DocumentationPage:
  22. frame = sys._getframe(2) # pylint: disable=protected-access
  23. module = inspect.getmodule(frame)
  24. assert module is not None and module.__file__ is not None
  25. name = _removesuffix(Path(module.__file__).stem, '_documentation')
  26. if name == 'overview':
  27. name = ''
  28. if name not in registry:
  29. registry[name] = DocumentationPage(name=name)
  30. return registry[name]
  31. def title(title_: Optional[str] = None, subtitle: Optional[str] = None) -> None:
  32. """Set the title of the current documentation page."""
  33. page = _get_current_page()
  34. page.title = title_
  35. page.subtitle = subtitle
  36. def text(title_: str, description: str) -> None:
  37. """Add a text block to the current documentation page."""
  38. _get_current_page().parts.append(DocumentationPart(title=title_, description=description))
  39. @overload
  40. def demo(title_: str,
  41. description: str, /, *,
  42. tab: Optional[Union[str, Callable]] = None,
  43. lazy: bool = True,
  44. ) -> Callable[[Callable], Callable]:
  45. ...
  46. @overload
  47. def demo(element: type, /,
  48. tab: Optional[Union[str, Callable]] = None,
  49. lazy: bool = True,
  50. ) -> Callable[[Callable], Callable]:
  51. ...
  52. @overload
  53. def demo(function: Callable, /,
  54. tab: Optional[Union[str, Callable]] = None,
  55. lazy: bool = True,
  56. ) -> Callable[[Callable], Callable]:
  57. ...
  58. def demo(*args, **kwargs) -> Callable[[Callable], Callable]:
  59. """Add a demo section to the current documentation page."""
  60. if len(args) == 2:
  61. element = None
  62. title_, description = args
  63. is_markdown = True
  64. else:
  65. element = args[0]
  66. doc = element.__doc__
  67. if isinstance(element, type) and not doc:
  68. doc = element.__init__.__doc__ # type: ignore
  69. title_, description = doc.split('\n', 1)
  70. is_markdown = False
  71. description = remove_indentation(description)
  72. page = _get_current_page()
  73. def decorator(function: Callable) -> Callable:
  74. if not page.parts and element:
  75. ui_name = _find_attribute(nicegui_ui, element.__name__)
  76. app_name = _find_attribute(nicegui_app, element.__name__)
  77. if ui_name:
  78. page.title = f'ui.*{ui_name}*'
  79. if app_name:
  80. page.title = f'app.*{app_name}*'
  81. page.parts.append(DocumentationPart(
  82. title=title_,
  83. description=description,
  84. description_format='md' if is_markdown else 'rst',
  85. demo=Demo(function=function, lazy=kwargs.get('lazy', True), tab=kwargs.get('tab')),
  86. ))
  87. return function
  88. return decorator
  89. def ui(function: Callable) -> Callable:
  90. """Add arbitrary UI to the current documentation page."""
  91. _get_current_page().parts.append(DocumentationPart(ui=function))
  92. return function
  93. def intro(documentation: types.ModuleType) -> None:
  94. """Add an intro section to the current documentation page."""
  95. current_page = _get_current_page()
  96. target_page = get_page(documentation)
  97. target_page.back_link = current_page.name
  98. part = deepcopy(target_page.parts[0])
  99. part.link = target_page.name
  100. current_page.parts.append(part)
  101. def reference(element: type, *,
  102. title: str = 'Reference', # pylint: disable=redefined-outer-name
  103. ) -> None:
  104. """Add a reference section to the current documentation page."""
  105. _get_current_page().parts.append(DocumentationPart(title=title, reference=element))
  106. def extra_column(function: Callable) -> Callable:
  107. """Add an extra column to the current documentation page."""
  108. _get_current_page().extra_column = function
  109. return function
  110. def _find_attribute(obj: Any, name: str) -> Optional[str]:
  111. for attr in dir(obj):
  112. if attr.lower().replace('_', '') == name.lower().replace('_', ''):
  113. return attr
  114. return None
  115. def _removesuffix(string: str, suffix: str) -> str:
  116. # NOTE: Remove this once we drop Python 3.8 support
  117. if string.endswith(suffix):
  118. return string[:-len(suffix)]
  119. return string