1
0

api.py 4.4 KB

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