From 195d31766d4e718509c525f5e29d99b8bdff2fae Mon Sep 17 00:00:00 2001 From: Carson Date: Mon, 25 Apr 2022 17:42:07 -0500 Subject: [PATCH 1/3] Close #137: fix logic for inferring window title from Tag/TagList objects --- shiny/ui/_bootstrap.py | 2 +- shiny/ui/_page.py | 33 ++------------------------------- shiny/ui/_utils.py | 35 ++++++++++++++++++++++++++++++++++- 3 files changed, 37 insertions(+), 33 deletions(-) diff --git a/shiny/ui/_bootstrap.py b/shiny/ui/_bootstrap.py index 0e1a1416a..0cf856c72 100644 --- a/shiny/ui/_bootstrap.py +++ b/shiny/ui/_bootstrap.py @@ -37,7 +37,7 @@ from .._docstring import add_example from ._html_dependencies import jqui_deps -from ._page import get_window_title +from ._utils import get_window_title # TODO: make a python version of the layout guide? diff --git a/shiny/ui/_page.py b/shiny/ui/_page.py index 3478cba8a..bc3691d9e 100644 --- a/shiny/ui/_page.py +++ b/shiny/ui/_page.py @@ -6,8 +6,7 @@ ) import sys -from typing import Optional, Any, List, Union, cast -from warnings import warn +from typing import Optional, Any, Union if sys.version_info >= (3, 8): from typing import Literal @@ -15,19 +14,18 @@ from typing_extensions import Literal from htmltools import ( - HTMLDependency, tags, Tag, TagList, div, TagChildArg, - head_content, ) from .._docstring import add_example from ._html_dependencies import bootstrap_deps from ._navs import navs_bar from ..types import MISSING, MISSING_TYPE +from ._utils import get_window_title def page_navbar( @@ -127,24 +125,6 @@ def page_navbar( ) -def get_window_title( - title: Optional[Union[str, Tag, TagList]], - window_title: Union[str, MISSING_TYPE] = MISSING, -) -> Optional[HTMLDependency]: - if title is not None and isinstance(window_title, MISSING_TYPE): - # Try to infer window_title from contents of title - window_title = " ".join(_find_characters(title)) - if not window_title: - warn( - "Unable to infer a `window_title` default from `title`. Consider providing a character string to `window_title`." - ) - - if isinstance(window_title, MISSING_TYPE): - return None - else: - return head_content(tags.title(window_title)) - - @add_example() def page_fluid( *args: Any, title: Optional[str] = None, lang: Optional[str] = None, **kwargs: str @@ -254,12 +234,3 @@ def page_bootstrap( page = TagList(*bootstrap_deps(), *args) head = tags.title(title) if title else None return tags.html(tags.head(head), tags.body(page), lang=lang) - - -def _find_characters(x: Any) -> List[str]: - if isinstance(x, str): - return [x] - elif isinstance(x, list): - return [y for y in cast(List[Any], x) if isinstance(y, str)] - else: - return [] diff --git a/shiny/ui/_utils.py b/shiny/ui/_utils.py index a779f732d..c986a2c23 100644 --- a/shiny/ui/_utils.py +++ b/shiny/ui/_utils.py @@ -1,6 +1,39 @@ -from htmltools import tags, Tag, TagChildArg +from typing import Optional, Union + +from htmltools import ( + tags, + Tag, + TagList, + TagChildArg, + TagChild, + HTMLDependency, + head_content, +) + +from ..types import MISSING, MISSING_TYPE def shiny_input_label(id: str, label: TagChildArg = None) -> Tag: cls = "control-label" + ("" if label else " shiny-label-null") return tags.label(label, class_=cls, id=id + "-label", for_=id) + + +def get_window_title( + title: Optional[Union[str, Tag, TagList]], + window_title: Union[str, MISSING_TYPE] = MISSING, +) -> Optional[HTMLDependency]: + if title is not None and isinstance(window_title, MISSING_TYPE): + window_title = _find_child_strings(title) + + if isinstance(window_title, MISSING_TYPE): + return None + else: + return head_content(tags.title(window_title)) + + +def _find_child_strings(x: Union[Tag, TagList, TagChild]) -> str: + if isinstance(x, Tag): + x = x.children + if isinstance(x, TagList): + return " ".join([_find_child_strings(y) for y in x]) + return x if isinstance(x, str) else "" From 28e4bb6c6cd64066529f1932c09450fe61c5e434 Mon Sep 17 00:00:00 2001 From: Carson Date: Tue, 26 Apr 2022 10:12:45 -0500 Subject: [PATCH 2/3] Add unit test --- tests/test_ui.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 tests/test_ui.py diff --git a/tests/test_ui.py b/tests/test_ui.py new file mode 100644 index 000000000..e4d08bee8 --- /dev/null +++ b/tests/test_ui.py @@ -0,0 +1,22 @@ +import textwrap + +from shiny import ui +from htmltools import HTMLDocument + + +def test_panel_title(): + x = HTMLDocument(ui.panel_title("Hello Shiny UI")).render()["html"] + assert x == textwrap.dedent( + """\ + + + + + + Hello Shiny UI + + +

Hello Shiny UI

+ + """ + ) From 893cf994ffed2c79ecc56e8f83872f1a4e541c1b Mon Sep 17 00:00:00 2001 From: Carson Date: Wed, 27 Apr 2022 09:59:01 -0500 Subject: [PATCH 3/3] Ignore script/style when searching for strings; make sure 'void' elements don't produce extra spaces --- shiny/ui/_utils.py | 9 ++++++--- tests/test_ui.py | 31 ++++++++++++++++++++++++++++++- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/shiny/ui/_utils.py b/shiny/ui/_utils.py index c986a2c23..3e5a3e929 100644 --- a/shiny/ui/_utils.py +++ b/shiny/ui/_utils.py @@ -32,8 +32,11 @@ def get_window_title( def _find_child_strings(x: Union[Tag, TagList, TagChild]) -> str: - if isinstance(x, Tag): + if isinstance(x, Tag) and x.name not in ("script", "style"): x = x.children if isinstance(x, TagList): - return " ".join([_find_child_strings(y) for y in x]) - return x if isinstance(x, str) else "" + strings = [_find_child_strings(y) for y in x] + return " ".join(filter(lambda x: x != "", strings)) + if isinstance(x, str): + return x + return "" diff --git a/tests/test_ui.py b/tests/test_ui.py index e4d08bee8..4a0e2cd14 100644 --- a/tests/test_ui.py +++ b/tests/test_ui.py @@ -1,7 +1,7 @@ import textwrap from shiny import ui -from htmltools import HTMLDocument +from htmltools import HTMLDocument, TagList, tags def test_panel_title(): @@ -20,3 +20,32 @@ def test_panel_title(): """ ) + + title = TagList( + tags.h1("A title"), + tags.script("foo"), + tags.style("foo"), + tags.h5(tags.script("foo"), "A subtitle"), + ) + + x = HTMLDocument(ui.panel_title(title)).render()["html"] + assert x == textwrap.dedent( + """\ + + + + + + A title A subtitle + + +

A title

+ + +
+ + A subtitle +
+ + """ + )