From 20f21f3e0db5dcefa6f2927c30b2943d1afd58f4 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Thu, 22 Aug 2024 13:06:36 -0400 Subject: [PATCH 01/22] feat: Support `_template.json` in template repos Used both internally and for external template repos provided via `--github` command line option --- shiny/_main.py | 4 +- shiny/_main_create.py | 213 ++++++++++++++---- .../app-templates/01-basic-app/_template.json | 6 + .../{basic-app => 01-basic-app}/app-core.py | 0 .../app-express.py | 0 .../02-basic-sidebar/_template.json | 6 + .../app-core.py | 0 .../app-express.py | 0 .../penguins.csv | 0 .../requirements.txt | 0 .../shared.py | 0 .../app-templates/03-dashboard/_template.json | 6 + .../{dashboard => 03-dashboard}/app-core.py | 0 .../app-express.py | 0 .../penguins.csv | 0 .../requirements.txt | 0 .../{basic-sidebar => 03-dashboard}/shared.py | 0 .../{dashboard => 03-dashboard}/styles.css | 0 .../README.md | 0 .../04-dashboard-tips/_template.json | 6 + .../app-core.py | 0 .../app-express.py | 0 .../requirements.txt | 0 .../shared.py | 0 .../styles.css | 0 .../tips.csv | 0 .../05-basic-navigation/_template.json | 6 + .../app-core.py | 0 .../app-express.py | 0 .../penguins.csv | 0 .../requirements.txt | 0 .../shared.py | 0 .../package-templates/js-input/_template.json | 6 + .../js-output/_template.json | 6 + .../package-templates/js-react/_template.json | 6 + 35 files changed, 215 insertions(+), 50 deletions(-) create mode 100644 shiny/templates/app-templates/01-basic-app/_template.json rename shiny/templates/app-templates/{basic-app => 01-basic-app}/app-core.py (100%) rename shiny/templates/app-templates/{basic-app => 01-basic-app}/app-express.py (100%) create mode 100644 shiny/templates/app-templates/02-basic-sidebar/_template.json rename shiny/templates/app-templates/{basic-sidebar => 02-basic-sidebar}/app-core.py (100%) rename shiny/templates/app-templates/{basic-sidebar => 02-basic-sidebar}/app-express.py (100%) rename shiny/templates/app-templates/{basic-navigation => 02-basic-sidebar}/penguins.csv (100%) rename shiny/templates/app-templates/{basic-navigation => 02-basic-sidebar}/requirements.txt (100%) rename shiny/templates/app-templates/{basic-navigation => 02-basic-sidebar}/shared.py (100%) create mode 100644 shiny/templates/app-templates/03-dashboard/_template.json rename shiny/templates/app-templates/{dashboard => 03-dashboard}/app-core.py (100%) rename shiny/templates/app-templates/{dashboard => 03-dashboard}/app-express.py (100%) rename shiny/templates/app-templates/{basic-sidebar => 03-dashboard}/penguins.csv (100%) rename shiny/templates/app-templates/{dashboard => 03-dashboard}/requirements.txt (100%) rename shiny/templates/app-templates/{basic-sidebar => 03-dashboard}/shared.py (100%) rename shiny/templates/app-templates/{dashboard => 03-dashboard}/styles.css (100%) rename shiny/templates/app-templates/{dashboard-tips => 04-dashboard-tips}/README.md (100%) create mode 100644 shiny/templates/app-templates/04-dashboard-tips/_template.json rename shiny/templates/app-templates/{dashboard-tips => 04-dashboard-tips}/app-core.py (100%) rename shiny/templates/app-templates/{dashboard-tips => 04-dashboard-tips}/app-express.py (100%) rename shiny/templates/app-templates/{dashboard-tips => 04-dashboard-tips}/requirements.txt (100%) rename shiny/templates/app-templates/{dashboard-tips => 04-dashboard-tips}/shared.py (100%) rename shiny/templates/app-templates/{dashboard-tips => 04-dashboard-tips}/styles.css (100%) rename shiny/templates/app-templates/{dashboard-tips => 04-dashboard-tips}/tips.csv (100%) create mode 100644 shiny/templates/app-templates/05-basic-navigation/_template.json rename shiny/templates/app-templates/{basic-navigation => 05-basic-navigation}/app-core.py (100%) rename shiny/templates/app-templates/{basic-navigation => 05-basic-navigation}/app-express.py (100%) rename shiny/templates/app-templates/{dashboard => 05-basic-navigation}/penguins.csv (100%) rename shiny/templates/app-templates/{basic-sidebar => 05-basic-navigation}/requirements.txt (100%) rename shiny/templates/app-templates/{dashboard => 05-basic-navigation}/shared.py (100%) create mode 100644 shiny/templates/package-templates/js-input/_template.json create mode 100644 shiny/templates/package-templates/js-output/_template.json create mode 100644 shiny/templates/package-templates/js-react/_template.json diff --git a/shiny/_main.py b/shiny/_main.py index 4d2b98cd9..f11f5d22e 100644 --- a/shiny/_main.py +++ b/shiny/_main.py @@ -584,13 +584,11 @@ def create( ) -> None: from ._main_create import use_template_github, use_template_internal - print(f"dir is {dir}") - if dir is not None: dir = Path(dir) if github is not None: - use_template_github(github, template=template, mode=mode, dest_dir=dir) + use_template_github(github, template_name=template, mode=mode, dest_dir=dir) else: use_template_internal(template, mode, dir, package_name) diff --git a/shiny/_main_create.py b/shiny/_main_create.py index bf19157ba..71bbdc605 100644 --- a/shiny/_main_create.py +++ b/shiny/_main_create.py @@ -1,5 +1,6 @@ from __future__ import annotations +import json import os import re import shutil @@ -66,8 +67,69 @@ back_choice: Choice = Choice(title=[("class:secondary", "← Back")], value="back") -def choice_from_dict(choice_dict: dict[str, str]) -> list[Choice]: - return [Choice(title=key, value=value) for key, value in choice_dict.items()] +def choice_from_templates(templates: list[ShinyTemplate]) -> list[Choice]: + return [Choice(title=t.title, value=t.name) for t in templates] + + +@dataclass +class ShinyTemplate: + name: str + path: Path + type: str = "app" + title: str | None = None + description: str | None = None + + +def find_templates(path: Path | str = ".") -> list[ShinyTemplate]: + path = Path(path) + templates: list[ShinyTemplate] = [] + + template_files = sorted(path.glob("**/_template.json")) + for tf in template_files: + with tf.open() as f: + template = json.load(f) + templates.append( + ShinyTemplate( + name=template["name"], + title=template.get("title"), + path=tf.parent.absolute(), + type=template.get("type", "app"), + description=template.get("description"), + ) + ) + + return templates + + +def template_by_name(templates: list[ShinyTemplate], name: str) -> ShinyTemplate | None: + for template in templates: + if template.name == name: + return template + return None + + +class ShinyInternalTemplates: + def __init__(self): + self.templates: list[ShinyTemplate] | None = None + + def _templates(self) -> list[ShinyTemplate]: + if self.templates is not None: + return self.templates + self.templates = find_templates(Path(__file__).parent / "templates") + return self.templates + + @property + def apps(self) -> list[ShinyTemplate]: + templates = self._templates() + return [t for t in templates if t.type == "app"] + + @property + def packages(self) -> list[ShinyTemplate]: + templates = self._templates() + return [t for t in templates if t.type == "package"] + + +shiny_internal_templates = ShinyInternalTemplates() def use_template_internal( @@ -91,27 +153,41 @@ def use_template_internal( "js-component": Start the questions for creating a custom JavaScript component. """ + app_templates = shiny_internal_templates.apps + pkg_templates = shiny_internal_templates.packages + + menu_choices = [ + Choice(title="Custom JavaScript component...", value="js-component"), + Choice( + title="Choose from the Shiny Templates website", value="external-gallery" + ), + cancel_choice, + ] + if question_state is None: - template = questionary.select( + question_state = questionary.select( "Which template would you like to use?:", - choices=[*choice_from_dict(app_template_choices), cancel_choice], + choices=[ + *choice_from_templates(app_templates), + *menu_choices, + ], style=styles_for_questions, ).ask() - else: - template = question_state - - valid_template_choices = {**app_template_choices, **package_template_choices} - if template not in valid_template_choices.values(): - raise click.BadOptionUsage( - "--template", - f"Invalid value for '--template' / '-t': {template} is not one of " - + f"""'{"', '".join(valid_template_choices.values())}'.""", - ) - # Define the control flow for the top level menu - if template is None or template == "cancel": + if question_state is None or question_state == "cancel": sys.exit(1) - elif template == "external-gallery": + + template = template_by_name([*app_templates, *pkg_templates], question_state) + + if template is not None: + if template.type == "app": + return app_template_questions(template, mode, dest_dir=dest_dir) + if template.type == "package": + return js_component_questions( + template, dest_dir=dest_dir, package_name=package_name + ) + + if question_state == "external-gallery": url = cli_url("https://shiny.posit.co/py/templates") click.echo(f"Opening {url} in your browser.") click.echo( @@ -121,13 +197,16 @@ def use_template_internal( webbrowser.open(url) sys.exit(0) - elif template == "js-component": + elif question_state == "js-component": js_component_questions(dest_dir=dest_dir, package_name=package_name) - return - elif template in package_template_choices.values(): - js_component_questions(template, dest_dir=dest_dir, package_name=package_name) else: - app_template_questions(template, mode, dest_dir=dest_dir) + valid_choices = [t.name for t in app_templates + pkg_templates] + if question_state not in valid_choices: + raise click.BadOptionUsage( + "--template", + f"Invalid value for '--template' / '-t': {question_state} is not one of " + + f"""'{"', '".join(valid_choices)}'.""", + ) def download_and_extract_zip(url: str, temp_dir: Path) -> Path: @@ -161,7 +240,7 @@ def download_and_extract_zip(url: str, temp_dir: Path) -> Path: def use_template_github( github: str, - template: str | None = None, + template_name: str | None = None, mode: str | None = None, dest_dir: Path | None = None, ): @@ -208,10 +287,55 @@ def use_template_github( f"Template directory '{cli_input(spec.path)}' does not exist in {cli_field(spec_cli)}." ) + templates = find_templates(template_dir) + + if not templates: + # Legacy: repo doesn't have _template.json files, so we have to rely on + # paths, i.e. template_dir / template_name + if template_name is None: + # warn that we're assuming the repo spec points to the template directly + click.echo( + cli_info( + f"Using {cli_field(spec_cli)} as the template. " + + f"Use {cli_code('--template')} to specify a template otherwise." + ) + ) + template_name = template_dir.name + else: + template_dir = template_dir / template_name + + template = ShinyTemplate( + name=template_name, + title=f"Template from {spec_cli}", + path=template_dir, + ) + elif template_name: + # Repo has templates and the user already picked one + template = template_by_name(templates, template_name) + if not template: + raise click.ClickException( + f"Template '{cli_input(template_name)}' not found in {cli_field(spec_cli)}." + ) + else: + # Has templates, but the user needs to pick one + template_name = questionary.select( + "Which template would you like to use?:", + choices=[*choice_from_templates(templates), cancel_choice], + style=styles_for_questions, + ).ask() + + if template_name is None or template_name == "cancel": + sys.exit(1) + + template = template_by_name(templates, template_name) + if not template: + raise click.ClickException( + f"Template '{cli_input(template_name)}' not found in {cli_field(spec_cli)}." + ) + return app_template_questions( template=template, mode=mode, - template_dir=Path(template_dir), dest_dir=dest_dir, ) @@ -318,27 +442,15 @@ def parse_github_url(x: str) -> GithubRepoLocation: def app_template_questions( - template: Optional[str] = None, + template: ShinyTemplate, mode: Optional[str] = None, - template_dir: Optional[Path] = None, dest_dir: Optional[Path] = None, ): - if template_dir is None: - if template is None: - raise ValueError("You must provide either template or template_dir") - template_dir = Path(__file__).parent / "templates/app-templates" / template - elif template is not None: - template_dir = template_dir / template - - # FIXME: We don't have any special syntax of files to signal a "template", which - # means that we could end up here with `template_dir` being a repo of templates. If - # `template` is missing, we end up copying everything in `template_dir` as if it's - # all part of a single big template. When we introduce a way to signal or coordinate - # templates in a repo, we will add a check here to avoid copying more than one - # template. + template_dir = template.path + click.echo( cli_wait( - f"Creating Shiny app from template {cli_bold(cli_field(template_dir.name))}..." + f"Creating {cli_bold(cli_field(template.title or template.name))} Shiny app..." ) ) @@ -394,7 +506,7 @@ def app_template_questions( def js_component_questions( - component_type: Optional[str] = None, + component_type: Optional[str | ShinyTemplate] = None, dest_dir: Optional[Path] = None, package_name: Optional[str] = None, ): @@ -408,7 +520,7 @@ def js_component_questions( component_type = questionary.select( "What kind of component do you want to build?:", choices=[ - *choice_from_dict(package_template_choices), + *choice_from_templates(shiny_internal_templates.packages), back_choice, cancel_choice, ], @@ -422,6 +534,15 @@ def js_component_questions( if component_type is None or component_type == "cancel": sys.exit(1) + if isinstance(component_type, ShinyTemplate): + template = component_type + else: + template = template_by_name(shiny_internal_templates.packages, component_type) + + if template is None: + # Validation should have happened in `use_template_internal()` + raise ValueError(f"Package template for {component_type} not found.") + # Ask what the user wants the name of their component to be if package_name is None: package_name = questionary.text( @@ -433,15 +554,11 @@ def js_component_questions( if package_name is None: sys.exit(1) - template_dir = ( - Path(__file__).parent / "templates/package-templates" / component_type - ) - dest_dir = directory_prompt(dest_dir, package_name) app_dir = copy_template_files( dest_dir, - template_dir=template_dir, + template_dir=template.path, express_available=False, mode=None, ) @@ -499,6 +616,8 @@ def copy_template_files( for item in template_dir.iterdir(): if item.is_file(): + if item.name == "_template.json": + continue shutil.copy(item, app_dir / item.name) else: if item.name != "__pycache__": diff --git a/shiny/templates/app-templates/01-basic-app/_template.json b/shiny/templates/app-templates/01-basic-app/_template.json new file mode 100644 index 000000000..3f3440616 --- /dev/null +++ b/shiny/templates/app-templates/01-basic-app/_template.json @@ -0,0 +1,6 @@ +{ + "type": "app", + "name": "basic-app", + "title": "Basic app", + "description": "A basic Shiny app template." +} diff --git a/shiny/templates/app-templates/basic-app/app-core.py b/shiny/templates/app-templates/01-basic-app/app-core.py similarity index 100% rename from shiny/templates/app-templates/basic-app/app-core.py rename to shiny/templates/app-templates/01-basic-app/app-core.py diff --git a/shiny/templates/app-templates/basic-app/app-express.py b/shiny/templates/app-templates/01-basic-app/app-express.py similarity index 100% rename from shiny/templates/app-templates/basic-app/app-express.py rename to shiny/templates/app-templates/01-basic-app/app-express.py diff --git a/shiny/templates/app-templates/02-basic-sidebar/_template.json b/shiny/templates/app-templates/02-basic-sidebar/_template.json new file mode 100644 index 000000000..256b3bc81 --- /dev/null +++ b/shiny/templates/app-templates/02-basic-sidebar/_template.json @@ -0,0 +1,6 @@ +{ + "type": "app", + "name": "basic-sidebar", + "title": "Sidebar layout", + "description": "An app with inputs in a sidebar and a plot in the main area." +} diff --git a/shiny/templates/app-templates/basic-sidebar/app-core.py b/shiny/templates/app-templates/02-basic-sidebar/app-core.py similarity index 100% rename from shiny/templates/app-templates/basic-sidebar/app-core.py rename to shiny/templates/app-templates/02-basic-sidebar/app-core.py diff --git a/shiny/templates/app-templates/basic-sidebar/app-express.py b/shiny/templates/app-templates/02-basic-sidebar/app-express.py similarity index 100% rename from shiny/templates/app-templates/basic-sidebar/app-express.py rename to shiny/templates/app-templates/02-basic-sidebar/app-express.py diff --git a/shiny/templates/app-templates/basic-navigation/penguins.csv b/shiny/templates/app-templates/02-basic-sidebar/penguins.csv similarity index 100% rename from shiny/templates/app-templates/basic-navigation/penguins.csv rename to shiny/templates/app-templates/02-basic-sidebar/penguins.csv diff --git a/shiny/templates/app-templates/basic-navigation/requirements.txt b/shiny/templates/app-templates/02-basic-sidebar/requirements.txt similarity index 100% rename from shiny/templates/app-templates/basic-navigation/requirements.txt rename to shiny/templates/app-templates/02-basic-sidebar/requirements.txt diff --git a/shiny/templates/app-templates/basic-navigation/shared.py b/shiny/templates/app-templates/02-basic-sidebar/shared.py similarity index 100% rename from shiny/templates/app-templates/basic-navigation/shared.py rename to shiny/templates/app-templates/02-basic-sidebar/shared.py diff --git a/shiny/templates/app-templates/03-dashboard/_template.json b/shiny/templates/app-templates/03-dashboard/_template.json new file mode 100644 index 000000000..edda8f2a1 --- /dev/null +++ b/shiny/templates/app-templates/03-dashboard/_template.json @@ -0,0 +1,6 @@ +{ + "type": "app", + "name": "dashboard", + "title": "Basic dashboard", + "description": "A basic, single page dashboard with value boxes, two plots in cards and a sidebar." +} diff --git a/shiny/templates/app-templates/dashboard/app-core.py b/shiny/templates/app-templates/03-dashboard/app-core.py similarity index 100% rename from shiny/templates/app-templates/dashboard/app-core.py rename to shiny/templates/app-templates/03-dashboard/app-core.py diff --git a/shiny/templates/app-templates/dashboard/app-express.py b/shiny/templates/app-templates/03-dashboard/app-express.py similarity index 100% rename from shiny/templates/app-templates/dashboard/app-express.py rename to shiny/templates/app-templates/03-dashboard/app-express.py diff --git a/shiny/templates/app-templates/basic-sidebar/penguins.csv b/shiny/templates/app-templates/03-dashboard/penguins.csv similarity index 100% rename from shiny/templates/app-templates/basic-sidebar/penguins.csv rename to shiny/templates/app-templates/03-dashboard/penguins.csv diff --git a/shiny/templates/app-templates/dashboard/requirements.txt b/shiny/templates/app-templates/03-dashboard/requirements.txt similarity index 100% rename from shiny/templates/app-templates/dashboard/requirements.txt rename to shiny/templates/app-templates/03-dashboard/requirements.txt diff --git a/shiny/templates/app-templates/basic-sidebar/shared.py b/shiny/templates/app-templates/03-dashboard/shared.py similarity index 100% rename from shiny/templates/app-templates/basic-sidebar/shared.py rename to shiny/templates/app-templates/03-dashboard/shared.py diff --git a/shiny/templates/app-templates/dashboard/styles.css b/shiny/templates/app-templates/03-dashboard/styles.css similarity index 100% rename from shiny/templates/app-templates/dashboard/styles.css rename to shiny/templates/app-templates/03-dashboard/styles.css diff --git a/shiny/templates/app-templates/dashboard-tips/README.md b/shiny/templates/app-templates/04-dashboard-tips/README.md similarity index 100% rename from shiny/templates/app-templates/dashboard-tips/README.md rename to shiny/templates/app-templates/04-dashboard-tips/README.md diff --git a/shiny/templates/app-templates/04-dashboard-tips/_template.json b/shiny/templates/app-templates/04-dashboard-tips/_template.json new file mode 100644 index 000000000..e97219f98 --- /dev/null +++ b/shiny/templates/app-templates/04-dashboard-tips/_template.json @@ -0,0 +1,6 @@ +{ + "type": "app", + "name": "dashboard-tips", + "title": "Intermediate dashboard", + "description": "An intermediate dashboard with value boxes, several plots in cards and a sidebar." +} diff --git a/shiny/templates/app-templates/dashboard-tips/app-core.py b/shiny/templates/app-templates/04-dashboard-tips/app-core.py similarity index 100% rename from shiny/templates/app-templates/dashboard-tips/app-core.py rename to shiny/templates/app-templates/04-dashboard-tips/app-core.py diff --git a/shiny/templates/app-templates/dashboard-tips/app-express.py b/shiny/templates/app-templates/04-dashboard-tips/app-express.py similarity index 100% rename from shiny/templates/app-templates/dashboard-tips/app-express.py rename to shiny/templates/app-templates/04-dashboard-tips/app-express.py diff --git a/shiny/templates/app-templates/dashboard-tips/requirements.txt b/shiny/templates/app-templates/04-dashboard-tips/requirements.txt similarity index 100% rename from shiny/templates/app-templates/dashboard-tips/requirements.txt rename to shiny/templates/app-templates/04-dashboard-tips/requirements.txt diff --git a/shiny/templates/app-templates/dashboard-tips/shared.py b/shiny/templates/app-templates/04-dashboard-tips/shared.py similarity index 100% rename from shiny/templates/app-templates/dashboard-tips/shared.py rename to shiny/templates/app-templates/04-dashboard-tips/shared.py diff --git a/shiny/templates/app-templates/dashboard-tips/styles.css b/shiny/templates/app-templates/04-dashboard-tips/styles.css similarity index 100% rename from shiny/templates/app-templates/dashboard-tips/styles.css rename to shiny/templates/app-templates/04-dashboard-tips/styles.css diff --git a/shiny/templates/app-templates/dashboard-tips/tips.csv b/shiny/templates/app-templates/04-dashboard-tips/tips.csv similarity index 100% rename from shiny/templates/app-templates/dashboard-tips/tips.csv rename to shiny/templates/app-templates/04-dashboard-tips/tips.csv diff --git a/shiny/templates/app-templates/05-basic-navigation/_template.json b/shiny/templates/app-templates/05-basic-navigation/_template.json new file mode 100644 index 000000000..89aa50790 --- /dev/null +++ b/shiny/templates/app-templates/05-basic-navigation/_template.json @@ -0,0 +1,6 @@ +{ + "type": "app", + "name": "basic-navigation", + "title": "Navigating multiple pages/panels", + "description": "An app with a top navigation bar and two pages." +} diff --git a/shiny/templates/app-templates/basic-navigation/app-core.py b/shiny/templates/app-templates/05-basic-navigation/app-core.py similarity index 100% rename from shiny/templates/app-templates/basic-navigation/app-core.py rename to shiny/templates/app-templates/05-basic-navigation/app-core.py diff --git a/shiny/templates/app-templates/basic-navigation/app-express.py b/shiny/templates/app-templates/05-basic-navigation/app-express.py similarity index 100% rename from shiny/templates/app-templates/basic-navigation/app-express.py rename to shiny/templates/app-templates/05-basic-navigation/app-express.py diff --git a/shiny/templates/app-templates/dashboard/penguins.csv b/shiny/templates/app-templates/05-basic-navigation/penguins.csv similarity index 100% rename from shiny/templates/app-templates/dashboard/penguins.csv rename to shiny/templates/app-templates/05-basic-navigation/penguins.csv diff --git a/shiny/templates/app-templates/basic-sidebar/requirements.txt b/shiny/templates/app-templates/05-basic-navigation/requirements.txt similarity index 100% rename from shiny/templates/app-templates/basic-sidebar/requirements.txt rename to shiny/templates/app-templates/05-basic-navigation/requirements.txt diff --git a/shiny/templates/app-templates/dashboard/shared.py b/shiny/templates/app-templates/05-basic-navigation/shared.py similarity index 100% rename from shiny/templates/app-templates/dashboard/shared.py rename to shiny/templates/app-templates/05-basic-navigation/shared.py diff --git a/shiny/templates/package-templates/js-input/_template.json b/shiny/templates/package-templates/js-input/_template.json new file mode 100644 index 000000000..d6a1e8662 --- /dev/null +++ b/shiny/templates/package-templates/js-input/_template.json @@ -0,0 +1,6 @@ +{ + "type": "package", + "name": "js-input", + "title": "Input component", + "description": "A Python package template providing a custom Shiny input component." +} diff --git a/shiny/templates/package-templates/js-output/_template.json b/shiny/templates/package-templates/js-output/_template.json new file mode 100644 index 000000000..e96b25441 --- /dev/null +++ b/shiny/templates/package-templates/js-output/_template.json @@ -0,0 +1,6 @@ +{ + "type": "package", + "name": "js-output", + "title": "Output component", + "description": "A Python package template providing a custom Shiny output component." +} diff --git a/shiny/templates/package-templates/js-react/_template.json b/shiny/templates/package-templates/js-react/_template.json new file mode 100644 index 000000000..39a713b37 --- /dev/null +++ b/shiny/templates/package-templates/js-react/_template.json @@ -0,0 +1,6 @@ +{ + "type": "package", + "name": "js-react", + "title": "React component", + "description": "A Python package template providing a custom React-based Shiny component." +} From 8706e007e13c171b05f47e99fbddcc442735525d Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Thu, 22 Aug 2024 13:12:35 -0400 Subject: [PATCH 02/22] refactor: Add `express_available` property to `ShinyTemplate` class --- shiny/_main_create.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/shiny/_main_create.py b/shiny/_main_create.py index 71bbdc605..04c6faf50 100644 --- a/shiny/_main_create.py +++ b/shiny/_main_create.py @@ -78,6 +78,13 @@ class ShinyTemplate: type: str = "app" title: str | None = None description: str | None = None + _express_available: bool | None = None + + @property + def express_available(self) -> bool: + if self._express_available is None: + self._express_available = (self.path / "app-express.py").exists() + return self._express_available def find_templates(path: Path | str = ".") -> list[ShinyTemplate]: @@ -447,24 +454,17 @@ def app_template_questions( dest_dir: Optional[Path] = None, ): template_dir = template.path + template_cli_name = cli_bold(cli_field(template.title or template.name)) - click.echo( - cli_wait( - f"Creating {cli_bold(cli_field(template.title or template.name))} Shiny app..." + if mode == "express" and not template.express_available: + raise click.BadParameter( + f"Express mode not available for the {template_cli_name} template." ) - ) - - # Not all apps will be implemented in both express and core so we can - # avoid the questions if it's a core only app. - template_files = [file.name for file in template_dir.iterdir() if file.is_file()] - express_available = "app-express.py" in template_files - - if mode == "express" and not express_available: - raise Exception("Express mode not available for that template.") + click.echo(cli_wait(f"Creating {template_cli_name} Shiny app...")) dest_dir = directory_prompt(dest_dir, template_dir.name) - if mode is None and express_available: + if mode is None and template.express_available: mode = questionary.select( "Would you like to use Shiny Express?", [ @@ -484,7 +484,7 @@ def app_template_questions( app_dir = copy_template_files( dest_dir, template_dir=template_dir, - express_available=express_available, + express_available=template.express_available, mode=mode, ) @@ -563,7 +563,7 @@ def js_component_questions( mode=None, ) - # Print messsage saying we're building the component + # Print message saying we're building the component click.echo(cli_wait(f"Setting up {cli_field(package_name)} component package...")) update_component_name_in_template(app_dir, package_name) From ea1160575403fe450f5481b39ca837a538e58762 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Thu, 22 Aug 2024 13:15:49 -0400 Subject: [PATCH 03/22] refactor: `copy_template_files()` now takes `ShinyTemplate` object --- shiny/_main_create.py | 43 ++++++++++++++++--------------------------- 1 file changed, 16 insertions(+), 27 deletions(-) diff --git a/shiny/_main_create.py b/shiny/_main_create.py index 04c6faf50..4976aed2d 100644 --- a/shiny/_main_create.py +++ b/shiny/_main_create.py @@ -481,12 +481,7 @@ def app_template_questions( use_template_internal() return - app_dir = copy_template_files( - dest_dir, - template_dir=template_dir, - express_available=template.express_available, - mode=mode, - ) + app_dir = copy_template_files(template, dest_dir, mode=mode) click.echo(cli_success(f"Created Shiny app at {cli_field(str(app_dir))}")) click.echo() @@ -556,12 +551,7 @@ def js_component_questions( dest_dir = directory_prompt(dest_dir, package_name) - app_dir = copy_template_files( - dest_dir, - template_dir=template.path, - express_available=False, - mode=None, - ) + app_dir = copy_template_files(template, dest_dir, mode=None) # Print message saying we're building the component click.echo(cli_wait(f"Setting up {cli_field(package_name)} component package...")) @@ -587,19 +577,18 @@ def js_component_questions( def copy_template_files( - app_dir: Path, - template_dir: Path, - express_available: bool, + template: ShinyTemplate, + dest_dir: Path, mode: Optional[str] = None, ): - files_to_check = [file.name for file in template_dir.iterdir()] + files_to_check = [file.name for file in template.path.iterdir()] if "__pycache__" in files_to_check: files_to_check.remove("__pycache__") files_to_check.append("app.py") - duplicate_files = [file for file in files_to_check if (app_dir / file).exists()] + duplicate_files = [file for file in files_to_check if (dest_dir / file).exists()] if any(duplicate_files): err_files = ", ".join([cli_input('"' + file + '"') for file in duplicate_files]) @@ -611,28 +600,28 @@ def copy_template_files( ) sys.exit(1) - if not app_dir.exists(): - app_dir.mkdir() + if not dest_dir.exists(): + dest_dir.mkdir() - for item in template_dir.iterdir(): + for item in template.path.iterdir(): if item.is_file(): if item.name == "_template.json": continue - shutil.copy(item, app_dir / item.name) + shutil.copy(item, dest_dir / item.name) else: if item.name != "__pycache__": - shutil.copytree(item, app_dir / item.name) + shutil.copytree(item, dest_dir / item.name) - def rename_unlink(file_to_rename: str, file_to_delete: str, dir: Path = app_dir): + def rename_unlink(file_to_rename: str, file_to_delete: str, dir: Path = dest_dir): (dir / file_to_rename).rename(dir / "app.py") (dir / file_to_delete).unlink() - if express_available: + if template.express_available: if mode == "express": rename_unlink("app-express.py", "app-core.py") if mode == "core": rename_unlink("app-core.py", "app-express.py") - if (app_dir / "app-core.py").exists(): - (app_dir / "app-core.py").rename(app_dir / "app.py") + if (dest_dir / "app-core.py").exists(): + (dest_dir / "app-core.py").rename(dest_dir / "app.py") - return app_dir + return dest_dir From 447933625a61f4e0f0a3d7d87f15f1ea6f8e67e5 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Thu, 22 Aug 2024 13:23:34 -0400 Subject: [PATCH 04/22] chore: Document internal templates, remove unused objects --- shiny/_main_create.py | 39 ++++++++++++++++----------------------- 1 file changed, 16 insertions(+), 23 deletions(-) diff --git a/shiny/_main_create.py b/shiny/_main_create.py index 4976aed2d..861286711 100644 --- a/shiny/_main_create.py +++ b/shiny/_main_create.py @@ -38,29 +38,6 @@ directory_prompt, ) -# These templates are copied over from the `shiny/templates/app_templates` -# directory. The process for adding new ones is to add your app folder to -# that directory, and then add another entry to this dictionary. -app_template_choices = { - "Basic app": "basic-app", - "Sidebar layout": "basic-sidebar", - "Basic dashboard": "dashboard", - "Intermediate dashboard": "dashboard-tips", - "Navigating multiple pages/panels": "basic-navigation", - "Custom JavaScript component ...": "js-component", - "Choose from the Shiny Templates website": "external-gallery", -} - -# These are templates which produce a Python package and have content filled in at -# various places based on the user input. You can add new ones by following the -# examples in `shiny/templates/package-templates` and then adding entries to this -# dictionary. -package_template_choices = { - "Input component": "js-input", - "Output component": "js-output", - "React component": "js-react", -} - styles_for_questions = questionary.Style([("secondary", "italic")]) # Prebuild some common choices cancel_choice: Choice = Choice(title=[("class:secondary", "[Cancel]")], value="cancel") @@ -116,6 +93,22 @@ def template_by_name(templates: list[ShinyTemplate], name: str) -> ShinyTemplate class ShinyInternalTemplates: + """ + Shiny's Internal Templates + + Internal templates that are built into the shiny package are always available via + `shiny create`. These templates are stored in the `shiny/templates` directory and + are divided into `app-templates` and `package-templates`. + + To add a new template, create the template subfolder in either of the two template + folders and add a `_template.json` file. See `ShinyTemplate` for expected fields. + + * `use_template_internal()` is the initial menu seen, which presents `app-templates` + with additional choices. + * package-templates are also referred to as `js-components` in the code base, these + templates appear as a submenu and are handled by `js_component_questions()`. + """ + def __init__(self): self.templates: list[ShinyTemplate] | None = None From fcfd07e1b8f99fb8d7bcce403e49a8581b24ffd8 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Thu, 22 Aug 2024 13:45:46 -0400 Subject: [PATCH 05/22] feat: external repos could have "package" templates also * Rename `use_internal_template()` (replaces `use_template_internal()`) * Separate `js_component_questions()` into * `use_internal_package_template()`: questions related to picking an internal package template * `package_template_questions()`: questions related to setting up and copying the package template --- shiny/_main.py | 12 +++-- shiny/_main_create.py | 102 +++++++++++++++++++++--------------------- 2 files changed, 61 insertions(+), 53 deletions(-) diff --git a/shiny/_main.py b/shiny/_main.py index f11f5d22e..e004f4312 100644 --- a/shiny/_main.py +++ b/shiny/_main.py @@ -582,15 +582,21 @@ def create( dir: Optional[Path | str] = None, package_name: Optional[str] = None, ) -> None: - from ._main_create import use_template_github, use_template_internal + from ._main_create import use_github_template, use_internal_template if dir is not None: dir = Path(dir) if github is not None: - use_template_github(github, template_name=template, mode=mode, dest_dir=dir) + use_github_template( + github, + template_name=template, + mode=mode, + dest_dir=dir, + package_name=package_name, + ) else: - use_template_internal(template, mode, dir, package_name) + use_internal_template(template, mode, dir, package_name) @main.command( diff --git a/shiny/_main_create.py b/shiny/_main_create.py index 861286711..2ec63b64b 100644 --- a/shiny/_main_create.py +++ b/shiny/_main_create.py @@ -132,7 +132,7 @@ def packages(self) -> list[ShinyTemplate]: shiny_internal_templates = ShinyInternalTemplates() -def use_template_internal( +def use_internal_template( question_state: Optional[str] = None, mode: Optional[str] = None, dest_dir: Optional[Path] = None, @@ -147,7 +147,6 @@ def use_template_internal( were at level 5 of a question chain and wanted to return to level 4. This is not that useful currently because we only have two levels of questions. - :param question_state: The question state you would like to return to. Currently, the options are: "cancel": Cancel the operation and exit. "js-component": Start the questions for creating a custom JavaScript component. @@ -183,7 +182,7 @@ def use_template_internal( if template.type == "app": return app_template_questions(template, mode, dest_dir=dest_dir) if template.type == "package": - return js_component_questions( + return package_template_questions( template, dest_dir=dest_dir, package_name=package_name ) @@ -198,7 +197,7 @@ def use_template_internal( webbrowser.open(url) sys.exit(0) elif question_state == "js-component": - js_component_questions(dest_dir=dest_dir, package_name=package_name) + use_internal_package_template(dest_dir=dest_dir, package_name=package_name) else: valid_choices = [t.name for t in app_templates + pkg_templates] if question_state not in valid_choices: @@ -209,6 +208,37 @@ def use_template_internal( ) +def use_internal_package_template( + dest_dir: Optional[Path] = None, + package_name: Optional[str] = None, +): + input = questionary.select( + "What kind of component do you want to build?", + choices=[ + *choice_from_templates(shiny_internal_templates.packages), + back_choice, + cancel_choice, + ], + style=styles_for_questions, + ).ask() + + if input == "back": + use_internal_template() + return + + if input is None or input == "cancel": + sys.exit(1) + + template = template_by_name(shiny_internal_templates.packages, input) + + if template is None: + # This should be valid because we're selecting from the list of templates + # but just in case and to make type checkers happy + raise ValueError(f"Package template for {input} not found.") + + package_template_questions(template, dest_dir=dest_dir, package_name=package_name) + + def download_and_extract_zip(url: str, temp_dir: Path) -> Path: try: response = urlopen(url) @@ -238,11 +268,12 @@ def download_and_extract_zip(url: str, temp_dir: Path) -> Path: return temp_dir -def use_template_github( +def use_github_template( github: str, template_name: str | None = None, mode: str | None = None, dest_dir: Path | None = None, + package_name: str | None = None, ): # Github requires that we download the whole repository, so we need to # download and unzip the repo, then navigate to the subdirectory. @@ -333,11 +364,14 @@ def use_template_github( f"Template '{cli_input(template_name)}' not found in {cli_field(spec_cli)}." ) - return app_template_questions( - template=template, - mode=mode, - dest_dir=dest_dir, - ) + if template.type == "package": + return package_template_questions( + template, + dest_dir=dest_dir, + package_name=package_name, + ) + else: + return app_template_questions(template, dest_dir=dest_dir, mode=mode) def github_zip_url(spec: GithubRepoLocation) -> Generator[str]: @@ -471,7 +505,7 @@ def app_template_questions( if mode is None or mode == "cancel": sys.exit(1) if mode == "back": - use_template_internal() + use_internal_template() return app_dir = copy_template_files(template, dest_dir, mode=mode) @@ -493,44 +527,11 @@ def app_template_questions( click.echo(f"- Open and edit the app file: {cli_field(str(app_dir / 'app.py'))}") -def js_component_questions( - component_type: Optional[str | ShinyTemplate] = None, +def package_template_questions( + template: ShinyTemplate, dest_dir: Optional[Path] = None, package_name: Optional[str] = None, ): - """ - Hand question branch for the custom js templates. This should handle the entire rest - of the question flow and is responsible for placing files etc. Currently it repeats - a lot of logic from the default flow but as the custom templates get more - complicated the logic will diverge - """ - if component_type is None: - component_type = questionary.select( - "What kind of component do you want to build?:", - choices=[ - *choice_from_templates(shiny_internal_templates.packages), - back_choice, - cancel_choice, - ], - style=styles_for_questions, - ).ask() - - if component_type == "back": - use_template_internal() - return - - if component_type is None or component_type == "cancel": - sys.exit(1) - - if isinstance(component_type, ShinyTemplate): - template = component_type - else: - template = template_by_name(shiny_internal_templates.packages, component_type) - - if template is None: - # Validation should have happened in `use_template_internal()` - raise ValueError(f"Package template for {component_type} not found.") - # Ask what the user wants the name of their component to be if package_name is None: package_name = questionary.text( @@ -542,11 +543,12 @@ def js_component_questions( if package_name is None: sys.exit(1) - dest_dir = directory_prompt(dest_dir, package_name) - - app_dir = copy_template_files(template, dest_dir, mode=None) + app_dir = copy_template_files( + template, + dest_dir=directory_prompt(dest_dir, package_name), + mode=None, + ) - # Print message saying we're building the component click.echo(cli_wait(f"Setting up {cli_field(package_name)} component package...")) update_component_name_in_template(app_dir, package_name) From c1d75898814a31a0e152302ceb10cfa1aab9418f Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Thu, 22 Aug 2024 13:48:47 -0400 Subject: [PATCH 06/22] tests: Update `shiny create` tests --- tests/playwright/examples/test_shiny_create.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/playwright/examples/test_shiny_create.py b/tests/playwright/examples/test_shiny_create.py index 30f7867f4..d2d91f4f6 100644 --- a/tests/playwright/examples/test_shiny_create.py +++ b/tests/playwright/examples/test_shiny_create.py @@ -8,8 +8,8 @@ from shiny._main_create import ( GithubRepoLocation, - app_template_choices, parse_github_arg, + shiny_internal_templates, ) @@ -53,10 +53,10 @@ def test_template_examples(page: Page, ex_app_path: str) -> None: validate_example(page, ex_app_path) -app_templates = list(app_template_choices.values()) -app_templates.remove("external-gallery") # Not actually a template -app_templates.remove("js-component") # Several templates that can't be easily tested +app_templates = [t.name for t in shiny_internal_templates.apps] +pkg_templates = [t.name for t in shiny_internal_templates.packages] assert len(app_templates) > 0 +assert len(pkg_templates) > 0 @pytest.mark.flaky(reruns=reruns, reruns_delay=reruns_delay) @@ -76,7 +76,7 @@ def test_create_express(app_template: str, page: Page): @pytest.mark.flaky(reruns=reruns, reruns_delay=reruns_delay) -@pytest.mark.parametrize("app_template", ["js-input", "js-output", "js-react"]) +@pytest.mark.parametrize("app_template", pkg_templates) def test_create_js(app_template: str): with tempfile.TemporaryDirectory("example_apps") as tmpdir: subprocess_create(app_template, dest_dir=tmpdir, package_name="my_component") From 7a4600cc0c7a7c2616f55a1bc82995a4418d718e Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Thu, 22 Aug 2024 14:51:32 -0400 Subject: [PATCH 07/22] refactor: Add `question_choose_template()` --- shiny/_main_create.py | 44 ++++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/shiny/_main_create.py b/shiny/_main_create.py index 2ec63b64b..ea221ba5e 100644 --- a/shiny/_main_create.py +++ b/shiny/_main_create.py @@ -160,21 +160,10 @@ def use_internal_template( Choice( title="Choose from the Shiny Templates website", value="external-gallery" ), - cancel_choice, ] if question_state is None: - question_state = questionary.select( - "Which template would you like to use?:", - choices=[ - *choice_from_templates(app_templates), - *menu_choices, - ], - style=styles_for_questions, - ).ask() - - if question_state is None or question_state == "cancel": - sys.exit(1) + question_state = question_choose_template(app_templates, *menu_choices) template = template_by_name([*app_templates, *pkg_templates], question_state) @@ -239,6 +228,26 @@ def use_internal_package_template( package_template_questions(template, dest_dir=dest_dir, package_name=package_name) +def question_choose_template( + templates: list[ShinyTemplate], + *extras: Choice, +) -> str: + """ + Ask the user to pick one of the templates. Includes and handles the cancel choice. + """ + + choice = questionary.select( + "Which template would you like to use?", + choices=[*choice_from_templates(templates), *extras, cancel_choice], + style=styles_for_questions, + ).ask() + + if choice is None or choice == "cancel": + sys.exit(1) + + return choice + + def download_and_extract_zip(url: str, temp_dir: Path) -> Path: try: response = urlopen(url) @@ -349,16 +358,9 @@ def use_github_template( ) else: # Has templates, but the user needs to pick one - template_name = questionary.select( - "Which template would you like to use?:", - choices=[*choice_from_templates(templates), cancel_choice], - style=styles_for_questions, - ).ask() - - if template_name is None or template_name == "cancel": - sys.exit(1) - + template_name = question_choose_template(templates) template = template_by_name(templates, template_name) + if not template: raise click.ClickException( f"Template '{cli_input(template_name)}' not found in {cli_field(spec_cli)}." From 8a0d862b8b5d5c632705d9018587f7e976f0ce6b Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Thu, 22 Aug 2024 16:30:52 -0400 Subject: [PATCH 08/22] feat: Add gnerative AI templates to `shiny create` menu Also start marking internal menu choices with a leading underscore so they aren't confused with template choices --- .../aws-bedrock-anthropic/_template.json | 5 + .../enterprise/azure-openai/_template.json | 5 + .../hello-providers/anthropic/_template.json | 5 + .../hello-providers/gemini/_template.json | 5 + .../hello-providers/langchain/_template.json | 5 + .../hello-providers/ollama/_template.json | 5 + .../hello-providers/openai/_template.json | 5 + shiny/_main_create.py | 94 ++++++++++++++++--- 8 files changed, 118 insertions(+), 11 deletions(-) create mode 100644 examples/chat/enterprise/aws-bedrock-anthropic/_template.json create mode 100644 examples/chat/enterprise/azure-openai/_template.json create mode 100644 examples/chat/hello-providers/anthropic/_template.json create mode 100644 examples/chat/hello-providers/gemini/_template.json create mode 100644 examples/chat/hello-providers/langchain/_template.json create mode 100644 examples/chat/hello-providers/ollama/_template.json create mode 100644 examples/chat/hello-providers/openai/_template.json diff --git a/examples/chat/enterprise/aws-bedrock-anthropic/_template.json b/examples/chat/enterprise/aws-bedrock-anthropic/_template.json new file mode 100644 index 000000000..33a8fd7e0 --- /dev/null +++ b/examples/chat/enterprise/aws-bedrock-anthropic/_template.json @@ -0,0 +1,5 @@ +{ + "type": "app", + "name": "chat-ai-anthropic-aws", + "title": "Chat AI using Anthropic via AWS Bedrock" +} diff --git a/examples/chat/enterprise/azure-openai/_template.json b/examples/chat/enterprise/azure-openai/_template.json new file mode 100644 index 000000000..dbe745cb0 --- /dev/null +++ b/examples/chat/enterprise/azure-openai/_template.json @@ -0,0 +1,5 @@ +{ + "type": "app", + "name": "chat-ai-azure-openai", + "title": "Chat AI using OpenAI via Azure" +} diff --git a/examples/chat/hello-providers/anthropic/_template.json b/examples/chat/hello-providers/anthropic/_template.json new file mode 100644 index 000000000..73abc733c --- /dev/null +++ b/examples/chat/hello-providers/anthropic/_template.json @@ -0,0 +1,5 @@ +{ + "type": "app", + "name": "chat-ai-anthropic", + "title": "Chat AI using Anthropic" +} diff --git a/examples/chat/hello-providers/gemini/_template.json b/examples/chat/hello-providers/gemini/_template.json new file mode 100644 index 000000000..d8dd27b9a --- /dev/null +++ b/examples/chat/hello-providers/gemini/_template.json @@ -0,0 +1,5 @@ +{ + "type": "app", + "name": "chat-ai-gemini", + "title": "Chat AI using Google Gemini" +} diff --git a/examples/chat/hello-providers/langchain/_template.json b/examples/chat/hello-providers/langchain/_template.json new file mode 100644 index 000000000..e8175706b --- /dev/null +++ b/examples/chat/hello-providers/langchain/_template.json @@ -0,0 +1,5 @@ +{ + "type": "app", + "name": "chat-ai-langchain", + "title": "Chat AI using LangChain" +} diff --git a/examples/chat/hello-providers/ollama/_template.json b/examples/chat/hello-providers/ollama/_template.json new file mode 100644 index 000000000..47046e8dc --- /dev/null +++ b/examples/chat/hello-providers/ollama/_template.json @@ -0,0 +1,5 @@ +{ + "type": "app", + "name": "chat-ai-ollama", + "title": "Chat AI using Ollama" +} diff --git a/examples/chat/hello-providers/openai/_template.json b/examples/chat/hello-providers/openai/_template.json new file mode 100644 index 000000000..b9ffb4741 --- /dev/null +++ b/examples/chat/hello-providers/openai/_template.json @@ -0,0 +1,5 @@ +{ + "type": "app", + "name": "chat-ai-openai", + "title": "Chat AI using OpenAI" +} diff --git a/shiny/_main_create.py b/shiny/_main_create.py index ea221ba5e..997392b2c 100644 --- a/shiny/_main_create.py +++ b/shiny/_main_create.py @@ -110,13 +110,13 @@ class ShinyInternalTemplates: """ def __init__(self): - self.templates: list[ShinyTemplate] | None = None + self.templates: dict[str, list[ShinyTemplate]] = {} - def _templates(self) -> list[ShinyTemplate]: - if self.templates is not None: - return self.templates - self.templates = find_templates(Path(__file__).parent / "templates") - return self.templates + def _templates(self, dir: str = "templates") -> list[ShinyTemplate]: + if dir in self.templates: + return self.templates[dir] + self.templates[dir] = find_templates(Path(__file__).parent / dir) + return self.templates[dir] @property def apps(self) -> list[ShinyTemplate]: @@ -128,6 +128,14 @@ def packages(self) -> list[ShinyTemplate]: templates = self._templates() return [t for t in templates if t.type == "package"] + @property + def chat_hello_providers(self) -> list[ShinyTemplate]: + return self._templates("../examples/chat/hello-providers") + + @property + def chat_enterprise(self) -> list[ShinyTemplate]: + return self._templates("../examples/chat/enterprise") + shiny_internal_templates = ShinyInternalTemplates() @@ -154,18 +162,25 @@ def use_internal_template( app_templates = shiny_internal_templates.apps pkg_templates = shiny_internal_templates.packages + chat_templates = [ + *shiny_internal_templates.chat_hello_providers, + *shiny_internal_templates.chat_enterprise, + ] menu_choices = [ - Choice(title="Custom JavaScript component...", value="js-component"), + Choice(title="Custom JavaScript component...", value="_js-component"), + Choice(title="Generative AI templates...", value="_chat-ai"), Choice( - title="Choose from the Shiny Templates website", value="external-gallery" + title="Choose from the Shiny Templates website", value="_external-gallery" ), ] if question_state is None: question_state = question_choose_template(app_templates, *menu_choices) - template = template_by_name([*app_templates, *pkg_templates], question_state) + template = template_by_name( + [*app_templates, *pkg_templates, *chat_templates], question_state + ) if template is not None: if template.type == "app": @@ -175,7 +190,7 @@ def use_internal_template( template, dest_dir=dest_dir, package_name=package_name ) - if question_state == "external-gallery": + if question_state == "_external-gallery": url = cli_url("https://shiny.posit.co/py/templates") click.echo(f"Opening {url} in your browser.") click.echo( @@ -185,8 +200,10 @@ def use_internal_template( webbrowser.open(url) sys.exit(0) - elif question_state == "js-component": + elif question_state == "_js-component": use_internal_package_template(dest_dir=dest_dir, package_name=package_name) + elif question_state == "_chat-ai": + use_internal_chat_ai_template(dest_dir=dest_dir, package_name=package_name) else: valid_choices = [t.name for t in app_templates + pkg_templates] if question_state not in valid_choices: @@ -228,6 +245,61 @@ def use_internal_package_template( package_template_questions(template, dest_dir=dest_dir, package_name=package_name) +def use_internal_chat_ai_template( + input: str | None = None, + dest_dir: Optional[Path] = None, + package_name: Optional[str] = None, +): + if input is None: + input = questionary.select( + "Which kind of generative AI template would you like to use?", + choices=[ + Choice(title="By provider...", value="_chat-ai_hello-providers"), + Choice(title="Enterprise providers...", value="_chat-ai_enterprise"), + back_choice, + cancel_choice, + ], + style=styles_for_questions, + ).ask() + + if input is None or input == "cancel": + sys.exit(1) + + if input == "back": + use_internal_template(dest_dir=dest_dir, package_name=package_name) + return + + use_internal_chat_ai_template( + input, dest_dir=dest_dir, package_name=package_name + ) + return + + template_choices = ( + shiny_internal_templates.chat_enterprise + if input == "_chat-ai_enterprise" + else shiny_internal_templates.chat_hello_providers + ) + + choice = question_choose_template(template_choices, back_choice) + + if choice == "back": + use_internal_chat_ai_template(dest_dir=dest_dir, package_name=package_name) + return + + template = template_by_name( + [ + *shiny_internal_templates.chat_hello_providers, + *shiny_internal_templates.chat_enterprise, + ], + choice, + ) + + if template is None: + raise ValueError(f"Chat AI template for {choice} not found.") + + app_template_questions(template, dest_dir=dest_dir, mode=None) + + def question_choose_template( templates: list[ShinyTemplate], *extras: Choice, From 8ed50f20f4a8f2728a0fbaf6417e0915783e4494 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Fri, 23 Aug 2024 13:12:19 -0400 Subject: [PATCH 09/22] feat: Support "next_steps" and "follow_up" in `_template.json` --- shiny/_main_create.py | 60 ++++++++++++++++++- .../app-templates/01-basic-app/_template.json | 12 +++- 2 files changed, 69 insertions(+), 3 deletions(-) diff --git a/shiny/_main_create.py b/shiny/_main_create.py index 997392b2c..bc39e5a13 100644 --- a/shiny/_main_create.py +++ b/shiny/_main_create.py @@ -8,9 +8,9 @@ import tempfile import textwrap import zipfile -from dataclasses import dataclass +from dataclasses import dataclass, field from pathlib import Path -from typing import Generator, Optional, cast +from typing import Generator, Literal, Optional, cast from urllib.error import URLError from urllib.parse import urlparse from urllib.request import urlopen @@ -55,6 +55,8 @@ class ShinyTemplate: type: str = "app" title: str | None = None description: str | None = None + next_steps: list[str] = field(default_factory=list) + follow_up: list[ShinyTemplateFollowUp] = field(default_factory=list) _express_available: bool | None = None @property @@ -64,6 +66,16 @@ def express_available(self) -> bool: return self._express_available +class ShinyTemplateFollowUp: + def __init__( + self, + text: str, + type: Literal["action", "info", "warning", "danger", "text"] | str = "text", + ): + self.text = text + self.type = type + + def find_templates(path: Path | str = ".") -> list[ShinyTemplate]: path = Path(path) templates: list[ShinyTemplate] = [] @@ -72,6 +84,19 @@ def find_templates(path: Path | str = ".") -> list[ShinyTemplate]: for tf in template_files: with tf.open() as f: template = json.load(f) + + # "next_steps" and "follow_up" can be either a string or an array of strings + # or an array of dictionaries (follow_up only) + follow_up_raw: str | list[dict[str, str]] = template.get("follow_up", []) + if isinstance(follow_up_raw, str): + follow_up_raw = [{"text": follow_up_raw}] + + follow_up = [ShinyTemplateFollowUp(**f) for f in follow_up_raw] + + next_steps: str | list[str] = template.get("next_steps", []) + if isinstance(next_steps, str): + next_steps = [next_steps] + templates.append( ShinyTemplate( name=template["name"], @@ -79,6 +104,8 @@ def find_templates(path: Path | str = ".") -> list[ShinyTemplate]: path=tf.parent.absolute(), type=template.get("type", "app"), description=template.get("description"), + follow_up=follow_up, + next_steps=next_steps, ) ) @@ -600,6 +627,8 @@ def app_template_questions( ) click.echo(f"- Open and edit the app file: {cli_field(str(app_dir / 'app.py'))}") + click_echo_next_steps_and_follow_up(template) + def package_template_questions( template: ShinyTemplate, @@ -644,6 +673,8 @@ def package_template_questions( f"- Open and run the example app in the {cli_field('example-app')} directory" ) + click_echo_next_steps_and_follow_up(template) + def copy_template_files( template: ShinyTemplate, @@ -694,3 +725,28 @@ def rename_unlink(file_to_rename: str, file_to_delete: str, dir: Path = dest_dir (dest_dir / "app-core.py").rename(dest_dir / "app.py") return dest_dir + + +def click_echo_next_steps_and_follow_up(template: ShinyTemplate): + for next_step in template.next_steps: + click.echo(f"- {next_step}") + + if len(template.follow_up) > 0: + click.echo() + for follow_up in template.follow_up: + click.echo(cli_follow_up(follow_up)) + + +def cli_follow_up(follow_up: ShinyTemplateFollowUp): + if follow_up.type == "text": + return follow_up.text + if follow_up.type == "action": + return cli_action(follow_up.text) + if follow_up.type == "info": + return cli_info(follow_up.text) + if follow_up.type == "warning": + return cli_danger(follow_up.text) + if follow_up.type == "danger": + return cli_danger(follow_up.text) + + return follow_up.text diff --git a/shiny/templates/app-templates/01-basic-app/_template.json b/shiny/templates/app-templates/01-basic-app/_template.json index 3f3440616..e44eae127 100644 --- a/shiny/templates/app-templates/01-basic-app/_template.json +++ b/shiny/templates/app-templates/01-basic-app/_template.json @@ -2,5 +2,15 @@ "type": "app", "name": "basic-app", "title": "Basic app", - "description": "A basic Shiny app template." + "description": "A basic Shiny app template.", + "follow_up": [ + { + "type": "info", + "text": "Just getting started with Shiny?" + }, + { + "type": "action", + "text": "Learn more at https://shiny.posit.co/py/docs/overview.html" + } + ] } From ca39ab08b1a826aab56f09ece69c6fc63ae5f7e6 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Fri, 23 Aug 2024 14:25:48 -0400 Subject: [PATCH 10/22] feat: Add next steps for the `basic-app` example --- shiny/templates/app-templates/01-basic-app/_template.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/shiny/templates/app-templates/01-basic-app/_template.json b/shiny/templates/app-templates/01-basic-app/_template.json index e44eae127..c46685263 100644 --- a/shiny/templates/app-templates/01-basic-app/_template.json +++ b/shiny/templates/app-templates/01-basic-app/_template.json @@ -3,6 +3,9 @@ "name": "basic-app", "title": "Basic app", "description": "A basic Shiny app template.", + "next_steps": [ + "Run the app with `shiny run app.py`." + ], "follow_up": [ { "type": "info", From c7742896f41f41b29e794c8bf495bc929f4921d3 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Fri, 23 Aug 2024 14:26:17 -0400 Subject: [PATCH 11/22] feat: improve documentation of ShinyTemplate --- shiny/_main_create.py | 41 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/shiny/_main_create.py b/shiny/_main_create.py index bc39e5a13..2492aa9c9 100644 --- a/shiny/_main_create.py +++ b/shiny/_main_create.py @@ -50,6 +50,36 @@ def choice_from_templates(templates: list[ShinyTemplate]) -> list[Choice]: @dataclass class ShinyTemplate: + """ + Shiny Template + + This class receives metadata for a Shiny template from a `_template.json` file. + (Alternatively, a template can be created from just `name` and `path` for legacy + reasons.) + + Attributes + ---------- + name + The name of the Shiny template. + path + The path to the `_template.json` file or the root directory of the template. + type + The type of the template (e.g. "app", "package"), default: 'app'. + title + A brief title for the template, if provided. + description + A longer description of the template, if provided. + next_steps + A list of next steps or instructions related to this template, shown after the + default instructions are displayed. In the `_template.json` file, this field + can be a single string or an array of strings. + follow_up + A list of follow-up actions or information related to this template. In the + `_template.json` file, this field can be a single string, an array of strings, + or an object with a `text` field and an optional `type` field. The `type` field + can be "action", "info", "warning", "danger", or "text". + """ + name: str path: Path type: str = "app" @@ -61,6 +91,11 @@ class ShinyTemplate: @property def express_available(self) -> bool: + """ + Does the template include an Express variant, denoted by the presence of an + `app-express.py` file? + """ + if self._express_available is None: self._express_available = (self.path / "app-express.py").exists() return self._express_available @@ -70,10 +105,12 @@ class ShinyTemplateFollowUp: def __init__( self, text: str, - type: Literal["action", "info", "warning", "danger", "text"] | str = "text", + type: str = "text", ): self.text = text - self.type = type + self.type: Literal["action", "info", "warning", "danger", "text"] = "text" + if type in ("action", "info", "warning", "danger"): + self.type = type def find_templates(path: Path | str = ".") -> list[ShinyTemplate]: From 2fd80c4cde1fdb0be5833ae26c7bf11b380e101d Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Mon, 26 Aug 2024 16:25:01 -0400 Subject: [PATCH 12/22] chore: add next steps and follow up to remaining app-templates --- .../app-templates/02-basic-sidebar/_template.json | 15 ++++++++++++++- .../app-templates/03-dashboard/_template.json | 15 ++++++++++++++- .../04-dashboard-tips/_template.json | 15 ++++++++++++++- .../05-basic-navigation/_template.json | 15 ++++++++++++++- 4 files changed, 56 insertions(+), 4 deletions(-) diff --git a/shiny/templates/app-templates/02-basic-sidebar/_template.json b/shiny/templates/app-templates/02-basic-sidebar/_template.json index 256b3bc81..7117f643b 100644 --- a/shiny/templates/app-templates/02-basic-sidebar/_template.json +++ b/shiny/templates/app-templates/02-basic-sidebar/_template.json @@ -2,5 +2,18 @@ "type": "app", "name": "basic-sidebar", "title": "Sidebar layout", - "description": "An app with inputs in a sidebar and a plot in the main area." + "description": "An app with inputs in a sidebar and a plot in the main area.", + "next_steps": [ + "Run the app with `shiny run app.py`." + ], + "follow_up": [ + { + "type": "info", + "text": "Just getting started with Shiny?" + }, + { + "type": "action", + "text": "Learn more at https://shiny.posit.co/py/docs/overview.html" + } + ] } diff --git a/shiny/templates/app-templates/03-dashboard/_template.json b/shiny/templates/app-templates/03-dashboard/_template.json index edda8f2a1..de079ccf3 100644 --- a/shiny/templates/app-templates/03-dashboard/_template.json +++ b/shiny/templates/app-templates/03-dashboard/_template.json @@ -2,5 +2,18 @@ "type": "app", "name": "dashboard", "title": "Basic dashboard", - "description": "A basic, single page dashboard with value boxes, two plots in cards and a sidebar." + "description": "A basic, single page dashboard with value boxes, two plots in cards and a sidebar.", + "next_steps": [ + "Run the app with `shiny run app.py`." + ], + "follow_up": [ + { + "type": "info", + "text": "Just getting started with Shiny?" + }, + { + "type": "action", + "text": "Learn more at https://shiny.posit.co/py/docs/user-interfaces.html" + } + ] } diff --git a/shiny/templates/app-templates/04-dashboard-tips/_template.json b/shiny/templates/app-templates/04-dashboard-tips/_template.json index e97219f98..9be2c2bf9 100644 --- a/shiny/templates/app-templates/04-dashboard-tips/_template.json +++ b/shiny/templates/app-templates/04-dashboard-tips/_template.json @@ -2,5 +2,18 @@ "type": "app", "name": "dashboard-tips", "title": "Intermediate dashboard", - "description": "An intermediate dashboard with value boxes, several plots in cards and a sidebar." + "description": "An intermediate dashboard with value boxes, several plots in cards and a sidebar.", + "next_steps": [ + "Run the app with `shiny run app.py`." + ], + "follow_up": [ + { + "type": "info", + "text": "Just getting started with Shiny?" + }, + { + "type": "action", + "text": "Learn more at https://shiny.posit.co/py/docs/user-interfaces.html" + } + ] } diff --git a/shiny/templates/app-templates/05-basic-navigation/_template.json b/shiny/templates/app-templates/05-basic-navigation/_template.json index 89aa50790..51c99a99b 100644 --- a/shiny/templates/app-templates/05-basic-navigation/_template.json +++ b/shiny/templates/app-templates/05-basic-navigation/_template.json @@ -2,5 +2,18 @@ "type": "app", "name": "basic-navigation", "title": "Navigating multiple pages/panels", - "description": "An app with a top navigation bar and two pages." + "description": "An app with a top navigation bar and two pages.", + "next_steps": [ + "Run the app with `shiny run app.py`." + ], + "follow_up": [ + { + "type": "info", + "text": "Just getting started with Shiny?" + }, + { + "type": "action", + "text": "Learn more at https://shiny.posit.co/py/docs/user-interfaces.html" + } + ] } From 8cc2c2c8a1c90b939b156d843f72ef041d4d6191 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Mon, 26 Aug 2024 16:27:29 -0400 Subject: [PATCH 13/22] feat: Add follow up actions to package templates --- shiny/templates/package-templates/js-input/_template.json | 8 +++++++- .../templates/package-templates/js-output/_template.json | 8 +++++++- shiny/templates/package-templates/js-react/_template.json | 8 +++++++- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/shiny/templates/package-templates/js-input/_template.json b/shiny/templates/package-templates/js-input/_template.json index d6a1e8662..292931a44 100644 --- a/shiny/templates/package-templates/js-input/_template.json +++ b/shiny/templates/package-templates/js-input/_template.json @@ -2,5 +2,11 @@ "type": "package", "name": "js-input", "title": "Input component", - "description": "A Python package template providing a custom Shiny input component." + "description": "A Python package template providing a custom Shiny input component.", + "follow_up": [ + { + "type": "action", + "text": "Learn more about custom components for Shiny at https://shiny.posit.co/py/docs/custom-components-pkg.html" + } + ] } diff --git a/shiny/templates/package-templates/js-output/_template.json b/shiny/templates/package-templates/js-output/_template.json index e96b25441..d1018d9ef 100644 --- a/shiny/templates/package-templates/js-output/_template.json +++ b/shiny/templates/package-templates/js-output/_template.json @@ -2,5 +2,11 @@ "type": "package", "name": "js-output", "title": "Output component", - "description": "A Python package template providing a custom Shiny output component." + "description": "A Python package template providing a custom Shiny output component.", + "follow_up": [ + { + "type": "action", + "text": "Learn more about custom components for Shiny at https://shiny.posit.co/py/docs/custom-components-pkg.html" + } + ] } diff --git a/shiny/templates/package-templates/js-react/_template.json b/shiny/templates/package-templates/js-react/_template.json index 39a713b37..d73d2b012 100644 --- a/shiny/templates/package-templates/js-react/_template.json +++ b/shiny/templates/package-templates/js-react/_template.json @@ -2,5 +2,11 @@ "type": "package", "name": "js-react", "title": "React component", - "description": "A Python package template providing a custom React-based Shiny component." + "description": "A Python package template providing a custom React-based Shiny component.", + "follow_up": [ + { + "type": "action", + "text": "Learn more about custom components for Shiny at https://shiny.posit.co/py/docs/custom-components-pkg.html" + } + ] } From ca291481499220584fdb69da6bfe8c2289b7d6cd Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Mon, 26 Aug 2024 21:01:09 -0400 Subject: [PATCH 14/22] chore: Apply suggestions from code review --- shiny/_main_create.py | 14 ++++++++++---- .../app-templates/04-dashboard-tips/_template.json | 2 +- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/shiny/_main_create.py b/shiny/_main_create.py index 2492aa9c9..c42852f46 100644 --- a/shiny/_main_create.py +++ b/shiny/_main_create.py @@ -120,7 +120,10 @@ def find_templates(path: Path | str = ".") -> list[ShinyTemplate]: template_files = sorted(path.glob("**/_template.json")) for tf in template_files: with tf.open() as f: - template = json.load(f) + try: + template = json.load(f) + except json.JSONDecodeError as err: + raise ValueError(f"Error parsing {tf}: {err}") # "next_steps" and "follow_up" can be either a string or an array of strings # or an array of dictionaries (follow_up only) @@ -134,11 +137,14 @@ def find_templates(path: Path | str = ".") -> list[ShinyTemplate]: if isinstance(next_steps, str): next_steps = [next_steps] + if "name" not in template: + raise ValueError(f"Template in {tf} is missing a 'name' field.") + templates.append( ShinyTemplate( name=template["name"], - title=template.get("title"), path=tf.parent.absolute(), + title=template.get("title"), type=template.get("type", "app"), description=template.get("description"), follow_up=follow_up, @@ -255,8 +261,8 @@ def use_internal_template( ) if question_state == "_external-gallery": - url = cli_url("https://shiny.posit.co/py/templates") - click.echo(f"Opening {url} in your browser.") + url = "https://shiny.posit.co/py/templates" + click.echo(f"Opening {cli_url(url)} in your browser.") click.echo( f"Choose a template and copy the {cli_code('shiny create')} command to use it." ) diff --git a/shiny/templates/app-templates/04-dashboard-tips/_template.json b/shiny/templates/app-templates/04-dashboard-tips/_template.json index 9be2c2bf9..53556ffab 100644 --- a/shiny/templates/app-templates/04-dashboard-tips/_template.json +++ b/shiny/templates/app-templates/04-dashboard-tips/_template.json @@ -4,7 +4,7 @@ "title": "Intermediate dashboard", "description": "An intermediate dashboard with value boxes, several plots in cards and a sidebar.", "next_steps": [ - "Run the app with `shiny run app.py`." + "Run the app with `shiny run app.py` from the app directory." ], "follow_up": [ { From deff20932d5008aea8b6f5db711d1ccf11957bc9 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Mon, 26 Aug 2024 21:05:28 -0400 Subject: [PATCH 15/22] chore: move chat templates into shiny/templates/chat --- shiny/_main_create.py | 4 ++-- .../chat/enterprise/aws-bedrock-anthropic/_template.json | 0 .../templates}/chat/enterprise/aws-bedrock-anthropic/app.py | 0 .../chat/enterprise/aws-bedrock-anthropic/app_utils.py | 0 .../chat/enterprise/aws-bedrock-anthropic/requirements.txt | 0 .../templates}/chat/enterprise/azure-openai/_template.json | 0 .../templates}/chat/enterprise/azure-openai/app.py | 0 .../templates}/chat/enterprise/azure-openai/app_utils.py | 0 .../templates}/chat/enterprise/azure-openai/requirements.txt | 0 .../templates}/chat/hello-providers/anthropic/_template.json | 0 .../templates}/chat/hello-providers/anthropic/app.py | 0 .../templates}/chat/hello-providers/anthropic/app_utils.py | 0 .../chat/hello-providers/anthropic/requirements.txt | 0 .../templates}/chat/hello-providers/gemini/_template.json | 0 .../templates}/chat/hello-providers/gemini/app.py | 0 .../templates}/chat/hello-providers/gemini/app_utils.py | 0 .../templates}/chat/hello-providers/gemini/requirements.txt | 0 .../templates}/chat/hello-providers/langchain/_template.json | 0 .../templates}/chat/hello-providers/langchain/app.py | 0 .../templates}/chat/hello-providers/langchain/app_utils.py | 0 .../chat/hello-providers/langchain/requirements.txt | 0 .../templates}/chat/hello-providers/ollama/_template.json | 0 .../templates}/chat/hello-providers/ollama/app.py | 0 .../templates}/chat/hello-providers/ollama/requirements.txt | 0 .../templates}/chat/hello-providers/openai/_template.json | 0 .../templates}/chat/hello-providers/openai/app.py | 0 .../templates}/chat/hello-providers/openai/app_utils.py | 0 .../templates}/chat/hello-providers/openai/requirements.txt | 0 28 files changed, 2 insertions(+), 2 deletions(-) rename {examples => shiny/templates}/chat/enterprise/aws-bedrock-anthropic/_template.json (100%) rename {examples => shiny/templates}/chat/enterprise/aws-bedrock-anthropic/app.py (100%) rename {examples => shiny/templates}/chat/enterprise/aws-bedrock-anthropic/app_utils.py (100%) rename {examples => shiny/templates}/chat/enterprise/aws-bedrock-anthropic/requirements.txt (100%) rename {examples => shiny/templates}/chat/enterprise/azure-openai/_template.json (100%) rename {examples => shiny/templates}/chat/enterprise/azure-openai/app.py (100%) rename {examples => shiny/templates}/chat/enterprise/azure-openai/app_utils.py (100%) rename {examples => shiny/templates}/chat/enterprise/azure-openai/requirements.txt (100%) rename {examples => shiny/templates}/chat/hello-providers/anthropic/_template.json (100%) rename {examples => shiny/templates}/chat/hello-providers/anthropic/app.py (100%) rename {examples => shiny/templates}/chat/hello-providers/anthropic/app_utils.py (100%) rename {examples => shiny/templates}/chat/hello-providers/anthropic/requirements.txt (100%) rename {examples => shiny/templates}/chat/hello-providers/gemini/_template.json (100%) rename {examples => shiny/templates}/chat/hello-providers/gemini/app.py (100%) rename {examples => shiny/templates}/chat/hello-providers/gemini/app_utils.py (100%) rename {examples => shiny/templates}/chat/hello-providers/gemini/requirements.txt (100%) rename {examples => shiny/templates}/chat/hello-providers/langchain/_template.json (100%) rename {examples => shiny/templates}/chat/hello-providers/langchain/app.py (100%) rename {examples => shiny/templates}/chat/hello-providers/langchain/app_utils.py (100%) rename {examples => shiny/templates}/chat/hello-providers/langchain/requirements.txt (100%) rename {examples => shiny/templates}/chat/hello-providers/ollama/_template.json (100%) rename {examples => shiny/templates}/chat/hello-providers/ollama/app.py (100%) rename {examples => shiny/templates}/chat/hello-providers/ollama/requirements.txt (100%) rename {examples => shiny/templates}/chat/hello-providers/openai/_template.json (100%) rename {examples => shiny/templates}/chat/hello-providers/openai/app.py (100%) rename {examples => shiny/templates}/chat/hello-providers/openai/app_utils.py (100%) rename {examples => shiny/templates}/chat/hello-providers/openai/requirements.txt (100%) diff --git a/shiny/_main_create.py b/shiny/_main_create.py index c42852f46..e8efb3591 100644 --- a/shiny/_main_create.py +++ b/shiny/_main_create.py @@ -200,11 +200,11 @@ def packages(self) -> list[ShinyTemplate]: @property def chat_hello_providers(self) -> list[ShinyTemplate]: - return self._templates("../examples/chat/hello-providers") + return self._templates("templates/chat/hello-providers") @property def chat_enterprise(self) -> list[ShinyTemplate]: - return self._templates("../examples/chat/enterprise") + return self._templates("templates/chat/enterprise") shiny_internal_templates = ShinyInternalTemplates() diff --git a/examples/chat/enterprise/aws-bedrock-anthropic/_template.json b/shiny/templates/chat/enterprise/aws-bedrock-anthropic/_template.json similarity index 100% rename from examples/chat/enterprise/aws-bedrock-anthropic/_template.json rename to shiny/templates/chat/enterprise/aws-bedrock-anthropic/_template.json diff --git a/examples/chat/enterprise/aws-bedrock-anthropic/app.py b/shiny/templates/chat/enterprise/aws-bedrock-anthropic/app.py similarity index 100% rename from examples/chat/enterprise/aws-bedrock-anthropic/app.py rename to shiny/templates/chat/enterprise/aws-bedrock-anthropic/app.py diff --git a/examples/chat/enterprise/aws-bedrock-anthropic/app_utils.py b/shiny/templates/chat/enterprise/aws-bedrock-anthropic/app_utils.py similarity index 100% rename from examples/chat/enterprise/aws-bedrock-anthropic/app_utils.py rename to shiny/templates/chat/enterprise/aws-bedrock-anthropic/app_utils.py diff --git a/examples/chat/enterprise/aws-bedrock-anthropic/requirements.txt b/shiny/templates/chat/enterprise/aws-bedrock-anthropic/requirements.txt similarity index 100% rename from examples/chat/enterprise/aws-bedrock-anthropic/requirements.txt rename to shiny/templates/chat/enterprise/aws-bedrock-anthropic/requirements.txt diff --git a/examples/chat/enterprise/azure-openai/_template.json b/shiny/templates/chat/enterprise/azure-openai/_template.json similarity index 100% rename from examples/chat/enterprise/azure-openai/_template.json rename to shiny/templates/chat/enterprise/azure-openai/_template.json diff --git a/examples/chat/enterprise/azure-openai/app.py b/shiny/templates/chat/enterprise/azure-openai/app.py similarity index 100% rename from examples/chat/enterprise/azure-openai/app.py rename to shiny/templates/chat/enterprise/azure-openai/app.py diff --git a/examples/chat/enterprise/azure-openai/app_utils.py b/shiny/templates/chat/enterprise/azure-openai/app_utils.py similarity index 100% rename from examples/chat/enterprise/azure-openai/app_utils.py rename to shiny/templates/chat/enterprise/azure-openai/app_utils.py diff --git a/examples/chat/enterprise/azure-openai/requirements.txt b/shiny/templates/chat/enterprise/azure-openai/requirements.txt similarity index 100% rename from examples/chat/enterprise/azure-openai/requirements.txt rename to shiny/templates/chat/enterprise/azure-openai/requirements.txt diff --git a/examples/chat/hello-providers/anthropic/_template.json b/shiny/templates/chat/hello-providers/anthropic/_template.json similarity index 100% rename from examples/chat/hello-providers/anthropic/_template.json rename to shiny/templates/chat/hello-providers/anthropic/_template.json diff --git a/examples/chat/hello-providers/anthropic/app.py b/shiny/templates/chat/hello-providers/anthropic/app.py similarity index 100% rename from examples/chat/hello-providers/anthropic/app.py rename to shiny/templates/chat/hello-providers/anthropic/app.py diff --git a/examples/chat/hello-providers/anthropic/app_utils.py b/shiny/templates/chat/hello-providers/anthropic/app_utils.py similarity index 100% rename from examples/chat/hello-providers/anthropic/app_utils.py rename to shiny/templates/chat/hello-providers/anthropic/app_utils.py diff --git a/examples/chat/hello-providers/anthropic/requirements.txt b/shiny/templates/chat/hello-providers/anthropic/requirements.txt similarity index 100% rename from examples/chat/hello-providers/anthropic/requirements.txt rename to shiny/templates/chat/hello-providers/anthropic/requirements.txt diff --git a/examples/chat/hello-providers/gemini/_template.json b/shiny/templates/chat/hello-providers/gemini/_template.json similarity index 100% rename from examples/chat/hello-providers/gemini/_template.json rename to shiny/templates/chat/hello-providers/gemini/_template.json diff --git a/examples/chat/hello-providers/gemini/app.py b/shiny/templates/chat/hello-providers/gemini/app.py similarity index 100% rename from examples/chat/hello-providers/gemini/app.py rename to shiny/templates/chat/hello-providers/gemini/app.py diff --git a/examples/chat/hello-providers/gemini/app_utils.py b/shiny/templates/chat/hello-providers/gemini/app_utils.py similarity index 100% rename from examples/chat/hello-providers/gemini/app_utils.py rename to shiny/templates/chat/hello-providers/gemini/app_utils.py diff --git a/examples/chat/hello-providers/gemini/requirements.txt b/shiny/templates/chat/hello-providers/gemini/requirements.txt similarity index 100% rename from examples/chat/hello-providers/gemini/requirements.txt rename to shiny/templates/chat/hello-providers/gemini/requirements.txt diff --git a/examples/chat/hello-providers/langchain/_template.json b/shiny/templates/chat/hello-providers/langchain/_template.json similarity index 100% rename from examples/chat/hello-providers/langchain/_template.json rename to shiny/templates/chat/hello-providers/langchain/_template.json diff --git a/examples/chat/hello-providers/langchain/app.py b/shiny/templates/chat/hello-providers/langchain/app.py similarity index 100% rename from examples/chat/hello-providers/langchain/app.py rename to shiny/templates/chat/hello-providers/langchain/app.py diff --git a/examples/chat/hello-providers/langchain/app_utils.py b/shiny/templates/chat/hello-providers/langchain/app_utils.py similarity index 100% rename from examples/chat/hello-providers/langchain/app_utils.py rename to shiny/templates/chat/hello-providers/langchain/app_utils.py diff --git a/examples/chat/hello-providers/langchain/requirements.txt b/shiny/templates/chat/hello-providers/langchain/requirements.txt similarity index 100% rename from examples/chat/hello-providers/langchain/requirements.txt rename to shiny/templates/chat/hello-providers/langchain/requirements.txt diff --git a/examples/chat/hello-providers/ollama/_template.json b/shiny/templates/chat/hello-providers/ollama/_template.json similarity index 100% rename from examples/chat/hello-providers/ollama/_template.json rename to shiny/templates/chat/hello-providers/ollama/_template.json diff --git a/examples/chat/hello-providers/ollama/app.py b/shiny/templates/chat/hello-providers/ollama/app.py similarity index 100% rename from examples/chat/hello-providers/ollama/app.py rename to shiny/templates/chat/hello-providers/ollama/app.py diff --git a/examples/chat/hello-providers/ollama/requirements.txt b/shiny/templates/chat/hello-providers/ollama/requirements.txt similarity index 100% rename from examples/chat/hello-providers/ollama/requirements.txt rename to shiny/templates/chat/hello-providers/ollama/requirements.txt diff --git a/examples/chat/hello-providers/openai/_template.json b/shiny/templates/chat/hello-providers/openai/_template.json similarity index 100% rename from examples/chat/hello-providers/openai/_template.json rename to shiny/templates/chat/hello-providers/openai/_template.json diff --git a/examples/chat/hello-providers/openai/app.py b/shiny/templates/chat/hello-providers/openai/app.py similarity index 100% rename from examples/chat/hello-providers/openai/app.py rename to shiny/templates/chat/hello-providers/openai/app.py diff --git a/examples/chat/hello-providers/openai/app_utils.py b/shiny/templates/chat/hello-providers/openai/app_utils.py similarity index 100% rename from examples/chat/hello-providers/openai/app_utils.py rename to shiny/templates/chat/hello-providers/openai/app_utils.py diff --git a/examples/chat/hello-providers/openai/requirements.txt b/shiny/templates/chat/hello-providers/openai/requirements.txt similarity index 100% rename from examples/chat/hello-providers/openai/requirements.txt rename to shiny/templates/chat/hello-providers/openai/requirements.txt From cf431493dbe82d98e6e244c9420fad23f46ac539 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Mon, 26 Aug 2024 21:06:29 -0400 Subject: [PATCH 16/22] chore: remove `-templates` suffix from `templates/{app,package}` --- shiny/_main_create.py | 6 +- .../01-basic-app/_template.json | 0 .../01-basic-app/app-core.py | 0 .../01-basic-app/app-express.py | 0 .../02-basic-sidebar/_template.json | 0 .../02-basic-sidebar/app-core.py | 0 .../02-basic-sidebar/app-express.py | 0 .../02-basic-sidebar/penguins.csv | 0 .../02-basic-sidebar/requirements.txt | 0 .../02-basic-sidebar/shared.py | 0 .../03-dashboard/_template.json | 0 .../03-dashboard/app-core.py | 0 .../03-dashboard/app-express.py | 0 .../03-dashboard/penguins.csv | 0 .../03-dashboard/requirements.txt | 0 .../03-dashboard/shared.py | 0 .../03-dashboard/styles.css | 0 .../04-dashboard-tips/README.md | 0 .../04-dashboard-tips/_template.json | 0 .../04-dashboard-tips/app-core.py | 0 .../04-dashboard-tips/app-express.py | 0 .../04-dashboard-tips/requirements.txt | 0 .../04-dashboard-tips/shared.py | 0 .../04-dashboard-tips/styles.css | 0 .../04-dashboard-tips/tips.csv | 0 .../05-basic-navigation/_template.json | 0 .../05-basic-navigation/app-core.py | 0 .../05-basic-navigation/app-express.py | 0 .../05-basic-navigation/penguins.csv | 0 .../05-basic-navigation/requirements.txt | 0 .../05-basic-navigation/shared.py | 0 .../js-input/custom_component/distjs/index.js | 814 ------------------ .../{package-templates => package}/.gitignore | 0 .../js-input/.gitignore | 0 .../js-input/README.md | 0 .../js-input/_template.json | 0 .../js-input/custom_component/__init__.py | 0 .../custom_component/custom_component.py | 0 .../js-input/example-app/app.py | 0 .../js-input/package-lock.json | 0 .../js-input/package.json | 0 .../js-input/pyproject.toml | 0 .../js-input/srcts/index.ts | 0 .../js-input/tsconfig.json | 0 .../js-output/.gitignore | 0 .../js-output/README.md | 0 .../js-output/_template.json | 0 .../js-output/custom_component/__init__.py | 0 .../custom_component/custom_component.py | 0 .../js-output/example-app/app.py | 0 .../js-output/package-lock.json | 0 .../js-output/package.json | 0 .../js-output/pyproject.toml | 0 .../js-output/srcts/index.ts | 0 .../js-output/tsconfig.json | 0 .../js-react/.gitignore | 0 .../js-react/README.md | 0 .../js-react/_template.json | 0 .../js-react/custom_component/__init__.py | 0 .../custom_component/custom_component.py | 0 .../js-react/example-app/app.py | 0 .../js-react/package-lock.json | 0 .../js-react/package.json | 0 .../js-react/pyproject.toml | 0 .../js-react/srcts/index.tsx | 0 .../js-react/tsconfig.json | 0 66 files changed, 2 insertions(+), 818 deletions(-) rename shiny/templates/{app-templates => app}/01-basic-app/_template.json (100%) rename shiny/templates/{app-templates => app}/01-basic-app/app-core.py (100%) rename shiny/templates/{app-templates => app}/01-basic-app/app-express.py (100%) rename shiny/templates/{app-templates => app}/02-basic-sidebar/_template.json (100%) rename shiny/templates/{app-templates => app}/02-basic-sidebar/app-core.py (100%) rename shiny/templates/{app-templates => app}/02-basic-sidebar/app-express.py (100%) rename shiny/templates/{app-templates => app}/02-basic-sidebar/penguins.csv (100%) rename shiny/templates/{app-templates => app}/02-basic-sidebar/requirements.txt (100%) rename shiny/templates/{app-templates => app}/02-basic-sidebar/shared.py (100%) rename shiny/templates/{app-templates => app}/03-dashboard/_template.json (100%) rename shiny/templates/{app-templates => app}/03-dashboard/app-core.py (100%) rename shiny/templates/{app-templates => app}/03-dashboard/app-express.py (100%) rename shiny/templates/{app-templates => app}/03-dashboard/penguins.csv (100%) rename shiny/templates/{app-templates => app}/03-dashboard/requirements.txt (100%) rename shiny/templates/{app-templates => app}/03-dashboard/shared.py (100%) rename shiny/templates/{app-templates => app}/03-dashboard/styles.css (100%) rename shiny/templates/{app-templates => app}/04-dashboard-tips/README.md (100%) rename shiny/templates/{app-templates => app}/04-dashboard-tips/_template.json (100%) rename shiny/templates/{app-templates => app}/04-dashboard-tips/app-core.py (100%) rename shiny/templates/{app-templates => app}/04-dashboard-tips/app-express.py (100%) rename shiny/templates/{app-templates => app}/04-dashboard-tips/requirements.txt (100%) rename shiny/templates/{app-templates => app}/04-dashboard-tips/shared.py (100%) rename shiny/templates/{app-templates => app}/04-dashboard-tips/styles.css (100%) rename shiny/templates/{app-templates => app}/04-dashboard-tips/tips.csv (100%) rename shiny/templates/{app-templates => app}/05-basic-navigation/_template.json (100%) rename shiny/templates/{app-templates => app}/05-basic-navigation/app-core.py (100%) rename shiny/templates/{app-templates => app}/05-basic-navigation/app-express.py (100%) rename shiny/templates/{app-templates => app}/05-basic-navigation/penguins.csv (100%) rename shiny/templates/{app-templates => app}/05-basic-navigation/requirements.txt (100%) rename shiny/templates/{app-templates => app}/05-basic-navigation/shared.py (100%) delete mode 100644 shiny/templates/package-templates/js-input/custom_component/distjs/index.js rename shiny/templates/{package-templates => package}/.gitignore (100%) rename shiny/templates/{package-templates => package}/js-input/.gitignore (100%) rename shiny/templates/{package-templates => package}/js-input/README.md (100%) rename shiny/templates/{package-templates => package}/js-input/_template.json (100%) rename shiny/templates/{package-templates => package}/js-input/custom_component/__init__.py (100%) rename shiny/templates/{package-templates => package}/js-input/custom_component/custom_component.py (100%) rename shiny/templates/{package-templates => package}/js-input/example-app/app.py (100%) rename shiny/templates/{package-templates => package}/js-input/package-lock.json (100%) rename shiny/templates/{package-templates => package}/js-input/package.json (100%) rename shiny/templates/{package-templates => package}/js-input/pyproject.toml (100%) rename shiny/templates/{package-templates => package}/js-input/srcts/index.ts (100%) rename shiny/templates/{package-templates => package}/js-input/tsconfig.json (100%) rename shiny/templates/{package-templates => package}/js-output/.gitignore (100%) rename shiny/templates/{package-templates => package}/js-output/README.md (100%) rename shiny/templates/{package-templates => package}/js-output/_template.json (100%) rename shiny/templates/{package-templates => package}/js-output/custom_component/__init__.py (100%) rename shiny/templates/{package-templates => package}/js-output/custom_component/custom_component.py (100%) rename shiny/templates/{package-templates => package}/js-output/example-app/app.py (100%) rename shiny/templates/{package-templates => package}/js-output/package-lock.json (100%) rename shiny/templates/{package-templates => package}/js-output/package.json (100%) rename shiny/templates/{package-templates => package}/js-output/pyproject.toml (100%) rename shiny/templates/{package-templates => package}/js-output/srcts/index.ts (100%) rename shiny/templates/{package-templates => package}/js-output/tsconfig.json (100%) rename shiny/templates/{package-templates => package}/js-react/.gitignore (100%) rename shiny/templates/{package-templates => package}/js-react/README.md (100%) rename shiny/templates/{package-templates => package}/js-react/_template.json (100%) rename shiny/templates/{package-templates => package}/js-react/custom_component/__init__.py (100%) rename shiny/templates/{package-templates => package}/js-react/custom_component/custom_component.py (100%) rename shiny/templates/{package-templates => package}/js-react/example-app/app.py (100%) rename shiny/templates/{package-templates => package}/js-react/package-lock.json (100%) rename shiny/templates/{package-templates => package}/js-react/package.json (100%) rename shiny/templates/{package-templates => package}/js-react/pyproject.toml (100%) rename shiny/templates/{package-templates => package}/js-react/srcts/index.tsx (100%) rename shiny/templates/{package-templates => package}/js-react/tsconfig.json (100%) diff --git a/shiny/_main_create.py b/shiny/_main_create.py index e8efb3591..bcb778dfe 100644 --- a/shiny/_main_create.py +++ b/shiny/_main_create.py @@ -190,13 +190,11 @@ def _templates(self, dir: str = "templates") -> list[ShinyTemplate]: @property def apps(self) -> list[ShinyTemplate]: - templates = self._templates() - return [t for t in templates if t.type == "app"] + return self._templates("templates/app") @property def packages(self) -> list[ShinyTemplate]: - templates = self._templates() - return [t for t in templates if t.type == "package"] + return self._templates("templates/package") @property def chat_hello_providers(self) -> list[ShinyTemplate]: diff --git a/shiny/templates/app-templates/01-basic-app/_template.json b/shiny/templates/app/01-basic-app/_template.json similarity index 100% rename from shiny/templates/app-templates/01-basic-app/_template.json rename to shiny/templates/app/01-basic-app/_template.json diff --git a/shiny/templates/app-templates/01-basic-app/app-core.py b/shiny/templates/app/01-basic-app/app-core.py similarity index 100% rename from shiny/templates/app-templates/01-basic-app/app-core.py rename to shiny/templates/app/01-basic-app/app-core.py diff --git a/shiny/templates/app-templates/01-basic-app/app-express.py b/shiny/templates/app/01-basic-app/app-express.py similarity index 100% rename from shiny/templates/app-templates/01-basic-app/app-express.py rename to shiny/templates/app/01-basic-app/app-express.py diff --git a/shiny/templates/app-templates/02-basic-sidebar/_template.json b/shiny/templates/app/02-basic-sidebar/_template.json similarity index 100% rename from shiny/templates/app-templates/02-basic-sidebar/_template.json rename to shiny/templates/app/02-basic-sidebar/_template.json diff --git a/shiny/templates/app-templates/02-basic-sidebar/app-core.py b/shiny/templates/app/02-basic-sidebar/app-core.py similarity index 100% rename from shiny/templates/app-templates/02-basic-sidebar/app-core.py rename to shiny/templates/app/02-basic-sidebar/app-core.py diff --git a/shiny/templates/app-templates/02-basic-sidebar/app-express.py b/shiny/templates/app/02-basic-sidebar/app-express.py similarity index 100% rename from shiny/templates/app-templates/02-basic-sidebar/app-express.py rename to shiny/templates/app/02-basic-sidebar/app-express.py diff --git a/shiny/templates/app-templates/02-basic-sidebar/penguins.csv b/shiny/templates/app/02-basic-sidebar/penguins.csv similarity index 100% rename from shiny/templates/app-templates/02-basic-sidebar/penguins.csv rename to shiny/templates/app/02-basic-sidebar/penguins.csv diff --git a/shiny/templates/app-templates/02-basic-sidebar/requirements.txt b/shiny/templates/app/02-basic-sidebar/requirements.txt similarity index 100% rename from shiny/templates/app-templates/02-basic-sidebar/requirements.txt rename to shiny/templates/app/02-basic-sidebar/requirements.txt diff --git a/shiny/templates/app-templates/02-basic-sidebar/shared.py b/shiny/templates/app/02-basic-sidebar/shared.py similarity index 100% rename from shiny/templates/app-templates/02-basic-sidebar/shared.py rename to shiny/templates/app/02-basic-sidebar/shared.py diff --git a/shiny/templates/app-templates/03-dashboard/_template.json b/shiny/templates/app/03-dashboard/_template.json similarity index 100% rename from shiny/templates/app-templates/03-dashboard/_template.json rename to shiny/templates/app/03-dashboard/_template.json diff --git a/shiny/templates/app-templates/03-dashboard/app-core.py b/shiny/templates/app/03-dashboard/app-core.py similarity index 100% rename from shiny/templates/app-templates/03-dashboard/app-core.py rename to shiny/templates/app/03-dashboard/app-core.py diff --git a/shiny/templates/app-templates/03-dashboard/app-express.py b/shiny/templates/app/03-dashboard/app-express.py similarity index 100% rename from shiny/templates/app-templates/03-dashboard/app-express.py rename to shiny/templates/app/03-dashboard/app-express.py diff --git a/shiny/templates/app-templates/03-dashboard/penguins.csv b/shiny/templates/app/03-dashboard/penguins.csv similarity index 100% rename from shiny/templates/app-templates/03-dashboard/penguins.csv rename to shiny/templates/app/03-dashboard/penguins.csv diff --git a/shiny/templates/app-templates/03-dashboard/requirements.txt b/shiny/templates/app/03-dashboard/requirements.txt similarity index 100% rename from shiny/templates/app-templates/03-dashboard/requirements.txt rename to shiny/templates/app/03-dashboard/requirements.txt diff --git a/shiny/templates/app-templates/03-dashboard/shared.py b/shiny/templates/app/03-dashboard/shared.py similarity index 100% rename from shiny/templates/app-templates/03-dashboard/shared.py rename to shiny/templates/app/03-dashboard/shared.py diff --git a/shiny/templates/app-templates/03-dashboard/styles.css b/shiny/templates/app/03-dashboard/styles.css similarity index 100% rename from shiny/templates/app-templates/03-dashboard/styles.css rename to shiny/templates/app/03-dashboard/styles.css diff --git a/shiny/templates/app-templates/04-dashboard-tips/README.md b/shiny/templates/app/04-dashboard-tips/README.md similarity index 100% rename from shiny/templates/app-templates/04-dashboard-tips/README.md rename to shiny/templates/app/04-dashboard-tips/README.md diff --git a/shiny/templates/app-templates/04-dashboard-tips/_template.json b/shiny/templates/app/04-dashboard-tips/_template.json similarity index 100% rename from shiny/templates/app-templates/04-dashboard-tips/_template.json rename to shiny/templates/app/04-dashboard-tips/_template.json diff --git a/shiny/templates/app-templates/04-dashboard-tips/app-core.py b/shiny/templates/app/04-dashboard-tips/app-core.py similarity index 100% rename from shiny/templates/app-templates/04-dashboard-tips/app-core.py rename to shiny/templates/app/04-dashboard-tips/app-core.py diff --git a/shiny/templates/app-templates/04-dashboard-tips/app-express.py b/shiny/templates/app/04-dashboard-tips/app-express.py similarity index 100% rename from shiny/templates/app-templates/04-dashboard-tips/app-express.py rename to shiny/templates/app/04-dashboard-tips/app-express.py diff --git a/shiny/templates/app-templates/04-dashboard-tips/requirements.txt b/shiny/templates/app/04-dashboard-tips/requirements.txt similarity index 100% rename from shiny/templates/app-templates/04-dashboard-tips/requirements.txt rename to shiny/templates/app/04-dashboard-tips/requirements.txt diff --git a/shiny/templates/app-templates/04-dashboard-tips/shared.py b/shiny/templates/app/04-dashboard-tips/shared.py similarity index 100% rename from shiny/templates/app-templates/04-dashboard-tips/shared.py rename to shiny/templates/app/04-dashboard-tips/shared.py diff --git a/shiny/templates/app-templates/04-dashboard-tips/styles.css b/shiny/templates/app/04-dashboard-tips/styles.css similarity index 100% rename from shiny/templates/app-templates/04-dashboard-tips/styles.css rename to shiny/templates/app/04-dashboard-tips/styles.css diff --git a/shiny/templates/app-templates/04-dashboard-tips/tips.csv b/shiny/templates/app/04-dashboard-tips/tips.csv similarity index 100% rename from shiny/templates/app-templates/04-dashboard-tips/tips.csv rename to shiny/templates/app/04-dashboard-tips/tips.csv diff --git a/shiny/templates/app-templates/05-basic-navigation/_template.json b/shiny/templates/app/05-basic-navigation/_template.json similarity index 100% rename from shiny/templates/app-templates/05-basic-navigation/_template.json rename to shiny/templates/app/05-basic-navigation/_template.json diff --git a/shiny/templates/app-templates/05-basic-navigation/app-core.py b/shiny/templates/app/05-basic-navigation/app-core.py similarity index 100% rename from shiny/templates/app-templates/05-basic-navigation/app-core.py rename to shiny/templates/app/05-basic-navigation/app-core.py diff --git a/shiny/templates/app-templates/05-basic-navigation/app-express.py b/shiny/templates/app/05-basic-navigation/app-express.py similarity index 100% rename from shiny/templates/app-templates/05-basic-navigation/app-express.py rename to shiny/templates/app/05-basic-navigation/app-express.py diff --git a/shiny/templates/app-templates/05-basic-navigation/penguins.csv b/shiny/templates/app/05-basic-navigation/penguins.csv similarity index 100% rename from shiny/templates/app-templates/05-basic-navigation/penguins.csv rename to shiny/templates/app/05-basic-navigation/penguins.csv diff --git a/shiny/templates/app-templates/05-basic-navigation/requirements.txt b/shiny/templates/app/05-basic-navigation/requirements.txt similarity index 100% rename from shiny/templates/app-templates/05-basic-navigation/requirements.txt rename to shiny/templates/app/05-basic-navigation/requirements.txt diff --git a/shiny/templates/app-templates/05-basic-navigation/shared.py b/shiny/templates/app/05-basic-navigation/shared.py similarity index 100% rename from shiny/templates/app-templates/05-basic-navigation/shared.py rename to shiny/templates/app/05-basic-navigation/shared.py diff --git a/shiny/templates/package-templates/js-input/custom_component/distjs/index.js b/shiny/templates/package-templates/js-input/custom_component/distjs/index.js deleted file mode 100644 index 620c5999a..000000000 --- a/shiny/templates/package-templates/js-input/custom_component/distjs/index.js +++ /dev/null @@ -1,814 +0,0 @@ -"use strict"; -(() => { - var __defProp = Object.defineProperty; - var __getOwnPropDesc = Object.getOwnPropertyDescriptor; - var __decorateClass = (decorators, target, key, kind) => { - var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target; - for (var i4 = decorators.length - 1, decorator; i4 >= 0; i4--) - if (decorator = decorators[i4]) - result = (kind ? decorator(target, key, result) : decorator(result)) || result; - if (kind && result) - __defProp(target, key, result); - return result; - }; - - // node_modules/@lit/reactive-element/css-tag.js - var t = globalThis; - var e = t.ShadowRoot && (void 0 === t.ShadyCSS || t.ShadyCSS.nativeShadow) && "adoptedStyleSheets" in Document.prototype && "replace" in CSSStyleSheet.prototype; - var s = Symbol(); - var o = /* @__PURE__ */ new WeakMap(); - var n = class { - constructor(t4, e5, o5) { - if (this._$cssResult$ = true, o5 !== s) - throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead."); - this.cssText = t4, this.t = e5; - } - get styleSheet() { - let t4 = this.o; - const s4 = this.t; - if (e && void 0 === t4) { - const e5 = void 0 !== s4 && 1 === s4.length; - e5 && (t4 = o.get(s4)), void 0 === t4 && ((this.o = t4 = new CSSStyleSheet()).replaceSync(this.cssText), e5 && o.set(s4, t4)); - } - return t4; - } - toString() { - return this.cssText; - } - }; - var r = (t4) => new n("string" == typeof t4 ? t4 : t4 + "", void 0, s); - var i = (t4, ...e5) => { - const o5 = 1 === t4.length ? t4[0] : e5.reduce((e6, s4, o6) => e6 + ((t5) => { - if (true === t5._$cssResult$) - return t5.cssText; - if ("number" == typeof t5) - return t5; - throw Error("Value passed to 'css' function must be a 'css' function result: " + t5 + ". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security."); - })(s4) + t4[o6 + 1], t4[0]); - return new n(o5, t4, s); - }; - var S = (s4, o5) => { - if (e) - s4.adoptedStyleSheets = o5.map((t4) => t4 instanceof CSSStyleSheet ? t4 : t4.styleSheet); - else - for (const e5 of o5) { - const o6 = document.createElement("style"), n5 = t.litNonce; - void 0 !== n5 && o6.setAttribute("nonce", n5), o6.textContent = e5.cssText, s4.appendChild(o6); - } - }; - var c = e ? (t4) => t4 : (t4) => t4 instanceof CSSStyleSheet ? ((t5) => { - let e5 = ""; - for (const s4 of t5.cssRules) - e5 += s4.cssText; - return r(e5); - })(t4) : t4; - - // node_modules/@lit/reactive-element/reactive-element.js - var { is: i2, defineProperty: e2, getOwnPropertyDescriptor: r2, getOwnPropertyNames: h, getOwnPropertySymbols: o2, getPrototypeOf: n2 } = Object; - var a = globalThis; - var c2 = a.trustedTypes; - var l = c2 ? c2.emptyScript : ""; - var p = a.reactiveElementPolyfillSupport; - var d = (t4, s4) => t4; - var u = { toAttribute(t4, s4) { - switch (s4) { - case Boolean: - t4 = t4 ? l : null; - break; - case Object: - case Array: - t4 = null == t4 ? t4 : JSON.stringify(t4); - } - return t4; - }, fromAttribute(t4, s4) { - let i4 = t4; - switch (s4) { - case Boolean: - i4 = null !== t4; - break; - case Number: - i4 = null === t4 ? null : Number(t4); - break; - case Object: - case Array: - try { - i4 = JSON.parse(t4); - } catch (t5) { - i4 = null; - } - } - return i4; - } }; - var f = (t4, s4) => !i2(t4, s4); - var y = { attribute: true, type: String, converter: u, reflect: false, hasChanged: f }; - Symbol.metadata ??= Symbol("metadata"), a.litPropertyMetadata ??= /* @__PURE__ */ new WeakMap(); - var b = class extends HTMLElement { - static addInitializer(t4) { - this._$Ei(), (this.l ??= []).push(t4); - } - static get observedAttributes() { - return this.finalize(), this._$Eh && [...this._$Eh.keys()]; - } - static createProperty(t4, s4 = y) { - if (s4.state && (s4.attribute = false), this._$Ei(), this.elementProperties.set(t4, s4), !s4.noAccessor) { - const i4 = Symbol(), r6 = this.getPropertyDescriptor(t4, i4, s4); - void 0 !== r6 && e2(this.prototype, t4, r6); - } - } - static getPropertyDescriptor(t4, s4, i4) { - const { get: e5, set: h3 } = r2(this.prototype, t4) ?? { get() { - return this[s4]; - }, set(t5) { - this[s4] = t5; - } }; - return { get() { - return e5?.call(this); - }, set(s5) { - const r6 = e5?.call(this); - h3.call(this, s5), this.requestUpdate(t4, r6, i4); - }, configurable: true, enumerable: true }; - } - static getPropertyOptions(t4) { - return this.elementProperties.get(t4) ?? y; - } - static _$Ei() { - if (this.hasOwnProperty(d("elementProperties"))) - return; - const t4 = n2(this); - t4.finalize(), void 0 !== t4.l && (this.l = [...t4.l]), this.elementProperties = new Map(t4.elementProperties); - } - static finalize() { - if (this.hasOwnProperty(d("finalized"))) - return; - if (this.finalized = true, this._$Ei(), this.hasOwnProperty(d("properties"))) { - const t5 = this.properties, s4 = [...h(t5), ...o2(t5)]; - for (const i4 of s4) - this.createProperty(i4, t5[i4]); - } - const t4 = this[Symbol.metadata]; - if (null !== t4) { - const s4 = litPropertyMetadata.get(t4); - if (void 0 !== s4) - for (const [t5, i4] of s4) - this.elementProperties.set(t5, i4); - } - this._$Eh = /* @__PURE__ */ new Map(); - for (const [t5, s4] of this.elementProperties) { - const i4 = this._$Eu(t5, s4); - void 0 !== i4 && this._$Eh.set(i4, t5); - } - this.elementStyles = this.finalizeStyles(this.styles); - } - static finalizeStyles(s4) { - const i4 = []; - if (Array.isArray(s4)) { - const e5 = new Set(s4.flat(1 / 0).reverse()); - for (const s5 of e5) - i4.unshift(c(s5)); - } else - void 0 !== s4 && i4.push(c(s4)); - return i4; - } - static _$Eu(t4, s4) { - const i4 = s4.attribute; - return false === i4 ? void 0 : "string" == typeof i4 ? i4 : "string" == typeof t4 ? t4.toLowerCase() : void 0; - } - constructor() { - super(), this._$Ep = void 0, this.isUpdatePending = false, this.hasUpdated = false, this._$Em = null, this._$Ev(); - } - _$Ev() { - this._$Eg = new Promise((t4) => this.enableUpdating = t4), this._$AL = /* @__PURE__ */ new Map(), this._$ES(), this.requestUpdate(), this.constructor.l?.forEach((t4) => t4(this)); - } - addController(t4) { - (this._$E_ ??= /* @__PURE__ */ new Set()).add(t4), void 0 !== this.renderRoot && this.isConnected && t4.hostConnected?.(); - } - removeController(t4) { - this._$E_?.delete(t4); - } - _$ES() { - const t4 = /* @__PURE__ */ new Map(), s4 = this.constructor.elementProperties; - for (const i4 of s4.keys()) - this.hasOwnProperty(i4) && (t4.set(i4, this[i4]), delete this[i4]); - t4.size > 0 && (this._$Ep = t4); - } - createRenderRoot() { - const t4 = this.shadowRoot ?? this.attachShadow(this.constructor.shadowRootOptions); - return S(t4, this.constructor.elementStyles), t4; - } - connectedCallback() { - this.renderRoot ??= this.createRenderRoot(), this.enableUpdating(true), this._$E_?.forEach((t4) => t4.hostConnected?.()); - } - enableUpdating(t4) { - } - disconnectedCallback() { - this._$E_?.forEach((t4) => t4.hostDisconnected?.()); - } - attributeChangedCallback(t4, s4, i4) { - this._$AK(t4, i4); - } - _$EO(t4, s4) { - const i4 = this.constructor.elementProperties.get(t4), e5 = this.constructor._$Eu(t4, i4); - if (void 0 !== e5 && true === i4.reflect) { - const r6 = (void 0 !== i4.converter?.toAttribute ? i4.converter : u).toAttribute(s4, i4.type); - this._$Em = t4, null == r6 ? this.removeAttribute(e5) : this.setAttribute(e5, r6), this._$Em = null; - } - } - _$AK(t4, s4) { - const i4 = this.constructor, e5 = i4._$Eh.get(t4); - if (void 0 !== e5 && this._$Em !== e5) { - const t5 = i4.getPropertyOptions(e5), r6 = "function" == typeof t5.converter ? { fromAttribute: t5.converter } : void 0 !== t5.converter?.fromAttribute ? t5.converter : u; - this._$Em = e5, this[e5] = r6.fromAttribute(s4, t5.type), this._$Em = null; - } - } - requestUpdate(t4, s4, i4, e5 = false, r6) { - if (void 0 !== t4) { - if (i4 ??= this.constructor.getPropertyOptions(t4), !(i4.hasChanged ?? f)(e5 ? r6 : this[t4], s4)) - return; - this.C(t4, s4, i4); - } - false === this.isUpdatePending && (this._$Eg = this._$EP()); - } - C(t4, s4, i4) { - this._$AL.has(t4) || this._$AL.set(t4, s4), true === i4.reflect && this._$Em !== t4 && (this._$Ej ??= /* @__PURE__ */ new Set()).add(t4); - } - async _$EP() { - this.isUpdatePending = true; - try { - await this._$Eg; - } catch (t5) { - Promise.reject(t5); - } - const t4 = this.scheduleUpdate(); - return null != t4 && await t4, !this.isUpdatePending; - } - scheduleUpdate() { - return this.performUpdate(); - } - performUpdate() { - if (!this.isUpdatePending) - return; - if (!this.hasUpdated) { - if (this.renderRoot ??= this.createRenderRoot(), this._$Ep) { - for (const [t6, s5] of this._$Ep) - this[t6] = s5; - this._$Ep = void 0; - } - const t5 = this.constructor.elementProperties; - if (t5.size > 0) - for (const [s5, i4] of t5) - true !== i4.wrapped || this._$AL.has(s5) || void 0 === this[s5] || this.C(s5, this[s5], i4); - } - let t4 = false; - const s4 = this._$AL; - try { - t4 = this.shouldUpdate(s4), t4 ? (this.willUpdate(s4), this._$E_?.forEach((t5) => t5.hostUpdate?.()), this.update(s4)) : this._$ET(); - } catch (s5) { - throw t4 = false, this._$ET(), s5; - } - t4 && this._$AE(s4); - } - willUpdate(t4) { - } - _$AE(t4) { - this._$E_?.forEach((t5) => t5.hostUpdated?.()), this.hasUpdated || (this.hasUpdated = true, this.firstUpdated(t4)), this.updated(t4); - } - _$ET() { - this._$AL = /* @__PURE__ */ new Map(), this.isUpdatePending = false; - } - get updateComplete() { - return this.getUpdateComplete(); - } - getUpdateComplete() { - return this._$Eg; - } - shouldUpdate(t4) { - return true; - } - update(t4) { - this._$Ej &&= this._$Ej.forEach((t5) => this._$EO(t5, this[t5])), this._$ET(); - } - updated(t4) { - } - firstUpdated(t4) { - } - }; - b.elementStyles = [], b.shadowRootOptions = { mode: "open" }, b[d("elementProperties")] = /* @__PURE__ */ new Map(), b[d("finalized")] = /* @__PURE__ */ new Map(), p?.({ ReactiveElement: b }), (a.reactiveElementVersions ??= []).push("2.0.2"); - - // node_modules/lit-html/lit-html.js - var t2 = globalThis; - var i3 = t2.trustedTypes; - var s2 = i3 ? i3.createPolicy("lit-html", { createHTML: (t4) => t4 }) : void 0; - var e3 = "$lit$"; - var h2 = `lit$${(Math.random() + "").slice(9)}$`; - var o3 = "?" + h2; - var n3 = `<${o3}>`; - var r3 = document; - var l2 = () => r3.createComment(""); - var c3 = (t4) => null === t4 || "object" != typeof t4 && "function" != typeof t4; - var a2 = Array.isArray; - var u2 = (t4) => a2(t4) || "function" == typeof t4?.[Symbol.iterator]; - var d2 = "[ \n\f\r]"; - var f2 = /<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g; - var v = /-->/g; - var _ = />/g; - var m = RegExp(`>|${d2}(?:([^\\s"'>=/]+)(${d2}*=${d2}*(?:[^ -\f\r"'\`<>=]|("|')|))|$)`, "g"); - var p2 = /'/g; - var g = /"/g; - var $2 = /^(?:script|style|textarea|title)$/i; - var y2 = (t4) => (i4, ...s4) => ({ _$litType$: t4, strings: i4, values: s4 }); - var x = y2(1); - var b2 = y2(2); - var w = Symbol.for("lit-noChange"); - var T = Symbol.for("lit-nothing"); - var A = /* @__PURE__ */ new WeakMap(); - var E = r3.createTreeWalker(r3, 129); - function C(t4, i4) { - if (!Array.isArray(t4) || !t4.hasOwnProperty("raw")) - throw Error("invalid template strings array"); - return void 0 !== s2 ? s2.createHTML(i4) : i4; - } - var P = (t4, i4) => { - const s4 = t4.length - 1, o5 = []; - let r6, l3 = 2 === i4 ? "" : "", c4 = f2; - for (let i5 = 0; i5 < s4; i5++) { - const s5 = t4[i5]; - let a3, u3, d3 = -1, y3 = 0; - for (; y3 < s5.length && (c4.lastIndex = y3, u3 = c4.exec(s5), null !== u3); ) - y3 = c4.lastIndex, c4 === f2 ? "!--" === u3[1] ? c4 = v : void 0 !== u3[1] ? c4 = _ : void 0 !== u3[2] ? ($2.test(u3[2]) && (r6 = RegExp("" === u3[0] ? (c4 = r6 ?? f2, d3 = -1) : void 0 === u3[1] ? d3 = -2 : (d3 = c4.lastIndex - u3[2].length, a3 = u3[1], c4 = void 0 === u3[3] ? m : '"' === u3[3] ? g : p2) : c4 === g || c4 === p2 ? c4 = m : c4 === v || c4 === _ ? c4 = f2 : (c4 = m, r6 = void 0); - const x2 = c4 === m && t4[i5 + 1].startsWith("/>") ? " " : ""; - l3 += c4 === f2 ? s5 + n3 : d3 >= 0 ? (o5.push(a3), s5.slice(0, d3) + e3 + s5.slice(d3) + h2 + x2) : s5 + h2 + (-2 === d3 ? i5 : x2); - } - return [C(t4, l3 + (t4[s4] || "") + (2 === i4 ? "" : "")), o5]; - }; - var V = class _V { - constructor({ strings: t4, _$litType$: s4 }, n5) { - let r6; - this.parts = []; - let c4 = 0, a3 = 0; - const u3 = t4.length - 1, d3 = this.parts, [f3, v2] = P(t4, s4); - if (this.el = _V.createElement(f3, n5), E.currentNode = this.el.content, 2 === s4) { - const t5 = this.el.content.firstChild; - t5.replaceWith(...t5.childNodes); - } - for (; null !== (r6 = E.nextNode()) && d3.length < u3; ) { - if (1 === r6.nodeType) { - if (r6.hasAttributes()) - for (const t5 of r6.getAttributeNames()) - if (t5.endsWith(e3)) { - const i4 = v2[a3++], s5 = r6.getAttribute(t5).split(h2), e5 = /([.?@])?(.*)/.exec(i4); - d3.push({ type: 1, index: c4, name: e5[2], strings: s5, ctor: "." === e5[1] ? k : "?" === e5[1] ? H : "@" === e5[1] ? I : R }), r6.removeAttribute(t5); - } else - t5.startsWith(h2) && (d3.push({ type: 6, index: c4 }), r6.removeAttribute(t5)); - if ($2.test(r6.tagName)) { - const t5 = r6.textContent.split(h2), s5 = t5.length - 1; - if (s5 > 0) { - r6.textContent = i3 ? i3.emptyScript : ""; - for (let i4 = 0; i4 < s5; i4++) - r6.append(t5[i4], l2()), E.nextNode(), d3.push({ type: 2, index: ++c4 }); - r6.append(t5[s5], l2()); - } - } - } else if (8 === r6.nodeType) - if (r6.data === o3) - d3.push({ type: 2, index: c4 }); - else { - let t5 = -1; - for (; -1 !== (t5 = r6.data.indexOf(h2, t5 + 1)); ) - d3.push({ type: 7, index: c4 }), t5 += h2.length - 1; - } - c4++; - } - } - static createElement(t4, i4) { - const s4 = r3.createElement("template"); - return s4.innerHTML = t4, s4; - } - }; - function N(t4, i4, s4 = t4, e5) { - if (i4 === w) - return i4; - let h3 = void 0 !== e5 ? s4._$Co?.[e5] : s4._$Cl; - const o5 = c3(i4) ? void 0 : i4._$litDirective$; - return h3?.constructor !== o5 && (h3?._$AO?.(false), void 0 === o5 ? h3 = void 0 : (h3 = new o5(t4), h3._$AT(t4, s4, e5)), void 0 !== e5 ? (s4._$Co ??= [])[e5] = h3 : s4._$Cl = h3), void 0 !== h3 && (i4 = N(t4, h3._$AS(t4, i4.values), h3, e5)), i4; - } - var S2 = class { - constructor(t4, i4) { - this._$AV = [], this._$AN = void 0, this._$AD = t4, this._$AM = i4; - } - get parentNode() { - return this._$AM.parentNode; - } - get _$AU() { - return this._$AM._$AU; - } - u(t4) { - const { el: { content: i4 }, parts: s4 } = this._$AD, e5 = (t4?.creationScope ?? r3).importNode(i4, true); - E.currentNode = e5; - let h3 = E.nextNode(), o5 = 0, n5 = 0, l3 = s4[0]; - for (; void 0 !== l3; ) { - if (o5 === l3.index) { - let i5; - 2 === l3.type ? i5 = new M(h3, h3.nextSibling, this, t4) : 1 === l3.type ? i5 = new l3.ctor(h3, l3.name, l3.strings, this, t4) : 6 === l3.type && (i5 = new L(h3, this, t4)), this._$AV.push(i5), l3 = s4[++n5]; - } - o5 !== l3?.index && (h3 = E.nextNode(), o5++); - } - return E.currentNode = r3, e5; - } - p(t4) { - let i4 = 0; - for (const s4 of this._$AV) - void 0 !== s4 && (void 0 !== s4.strings ? (s4._$AI(t4, s4, i4), i4 += s4.strings.length - 2) : s4._$AI(t4[i4])), i4++; - } - }; - var M = class _M { - get _$AU() { - return this._$AM?._$AU ?? this._$Cv; - } - constructor(t4, i4, s4, e5) { - this.type = 2, this._$AH = T, this._$AN = void 0, this._$AA = t4, this._$AB = i4, this._$AM = s4, this.options = e5, this._$Cv = e5?.isConnected ?? true; - } - get parentNode() { - let t4 = this._$AA.parentNode; - const i4 = this._$AM; - return void 0 !== i4 && 11 === t4?.nodeType && (t4 = i4.parentNode), t4; - } - get startNode() { - return this._$AA; - } - get endNode() { - return this._$AB; - } - _$AI(t4, i4 = this) { - t4 = N(this, t4, i4), c3(t4) ? t4 === T || null == t4 || "" === t4 ? (this._$AH !== T && this._$AR(), this._$AH = T) : t4 !== this._$AH && t4 !== w && this._(t4) : void 0 !== t4._$litType$ ? this.g(t4) : void 0 !== t4.nodeType ? this.$(t4) : u2(t4) ? this.T(t4) : this._(t4); - } - k(t4) { - return this._$AA.parentNode.insertBefore(t4, this._$AB); - } - $(t4) { - this._$AH !== t4 && (this._$AR(), this._$AH = this.k(t4)); - } - _(t4) { - this._$AH !== T && c3(this._$AH) ? this._$AA.nextSibling.data = t4 : this.$(r3.createTextNode(t4)), this._$AH = t4; - } - g(t4) { - const { values: i4, _$litType$: s4 } = t4, e5 = "number" == typeof s4 ? this._$AC(t4) : (void 0 === s4.el && (s4.el = V.createElement(C(s4.h, s4.h[0]), this.options)), s4); - if (this._$AH?._$AD === e5) - this._$AH.p(i4); - else { - const t5 = new S2(e5, this), s5 = t5.u(this.options); - t5.p(i4), this.$(s5), this._$AH = t5; - } - } - _$AC(t4) { - let i4 = A.get(t4.strings); - return void 0 === i4 && A.set(t4.strings, i4 = new V(t4)), i4; - } - T(t4) { - a2(this._$AH) || (this._$AH = [], this._$AR()); - const i4 = this._$AH; - let s4, e5 = 0; - for (const h3 of t4) - e5 === i4.length ? i4.push(s4 = new _M(this.k(l2()), this.k(l2()), this, this.options)) : s4 = i4[e5], s4._$AI(h3), e5++; - e5 < i4.length && (this._$AR(s4 && s4._$AB.nextSibling, e5), i4.length = e5); - } - _$AR(t4 = this._$AA.nextSibling, i4) { - for (this._$AP?.(false, true, i4); t4 && t4 !== this._$AB; ) { - const i5 = t4.nextSibling; - t4.remove(), t4 = i5; - } - } - setConnected(t4) { - void 0 === this._$AM && (this._$Cv = t4, this._$AP?.(t4)); - } - }; - var R = class { - get tagName() { - return this.element.tagName; - } - get _$AU() { - return this._$AM._$AU; - } - constructor(t4, i4, s4, e5, h3) { - this.type = 1, this._$AH = T, this._$AN = void 0, this.element = t4, this.name = i4, this._$AM = e5, this.options = h3, s4.length > 2 || "" !== s4[0] || "" !== s4[1] ? (this._$AH = Array(s4.length - 1).fill(new String()), this.strings = s4) : this._$AH = T; - } - _$AI(t4, i4 = this, s4, e5) { - const h3 = this.strings; - let o5 = false; - if (void 0 === h3) - t4 = N(this, t4, i4, 0), o5 = !c3(t4) || t4 !== this._$AH && t4 !== w, o5 && (this._$AH = t4); - else { - const e6 = t4; - let n5, r6; - for (t4 = h3[0], n5 = 0; n5 < h3.length - 1; n5++) - r6 = N(this, e6[s4 + n5], i4, n5), r6 === w && (r6 = this._$AH[n5]), o5 ||= !c3(r6) || r6 !== this._$AH[n5], r6 === T ? t4 = T : t4 !== T && (t4 += (r6 ?? "") + h3[n5 + 1]), this._$AH[n5] = r6; - } - o5 && !e5 && this.O(t4); - } - O(t4) { - t4 === T ? this.element.removeAttribute(this.name) : this.element.setAttribute(this.name, t4 ?? ""); - } - }; - var k = class extends R { - constructor() { - super(...arguments), this.type = 3; - } - O(t4) { - this.element[this.name] = t4 === T ? void 0 : t4; - } - }; - var H = class extends R { - constructor() { - super(...arguments), this.type = 4; - } - O(t4) { - this.element.toggleAttribute(this.name, !!t4 && t4 !== T); - } - }; - var I = class extends R { - constructor(t4, i4, s4, e5, h3) { - super(t4, i4, s4, e5, h3), this.type = 5; - } - _$AI(t4, i4 = this) { - if ((t4 = N(this, t4, i4, 0) ?? T) === w) - return; - const s4 = this._$AH, e5 = t4 === T && s4 !== T || t4.capture !== s4.capture || t4.once !== s4.once || t4.passive !== s4.passive, h3 = t4 !== T && (s4 === T || e5); - e5 && this.element.removeEventListener(this.name, this, s4), h3 && this.element.addEventListener(this.name, this, t4), this._$AH = t4; - } - handleEvent(t4) { - "function" == typeof this._$AH ? this._$AH.call(this.options?.host ?? this.element, t4) : this._$AH.handleEvent(t4); - } - }; - var L = class { - constructor(t4, i4, s4) { - this.element = t4, this.type = 6, this._$AN = void 0, this._$AM = i4, this.options = s4; - } - get _$AU() { - return this._$AM._$AU; - } - _$AI(t4) { - N(this, t4); - } - }; - var Z = t2.litHtmlPolyfillSupport; - Z?.(V, M), (t2.litHtmlVersions ??= []).push("3.1.0"); - var j = (t4, i4, s4) => { - const e5 = s4?.renderBefore ?? i4; - let h3 = e5._$litPart$; - if (void 0 === h3) { - const t5 = s4?.renderBefore ?? null; - e5._$litPart$ = h3 = new M(i4.insertBefore(l2(), t5), t5, void 0, s4 ?? {}); - } - return h3._$AI(t4), h3; - }; - - // node_modules/lit-element/lit-element.js - var s3 = class extends b { - constructor() { - super(...arguments), this.renderOptions = { host: this }, this._$Do = void 0; - } - createRenderRoot() { - const t4 = super.createRenderRoot(); - return this.renderOptions.renderBefore ??= t4.firstChild, t4; - } - update(t4) { - const i4 = this.render(); - this.hasUpdated || (this.renderOptions.isConnected = this.isConnected), super.update(t4), this._$Do = j(i4, this.renderRoot, this.renderOptions); - } - connectedCallback() { - super.connectedCallback(), this._$Do?.setConnected(true); - } - disconnectedCallback() { - super.disconnectedCallback(), this._$Do?.setConnected(false); - } - render() { - return w; - } - }; - s3._$litElement$ = true, s3["finalized", "finalized"] = true, globalThis.litElementHydrateSupport?.({ LitElement: s3 }); - var r4 = globalThis.litElementPolyfillSupport; - r4?.({ LitElement: s3 }); - (globalThis.litElementVersions ??= []).push("4.0.2"); - - // node_modules/@lit/reactive-element/decorators/custom-element.js - var t3 = (t4) => (e5, o5) => { - void 0 !== o5 ? o5.addInitializer(() => { - customElements.define(t4, e5); - }) : customElements.define(t4, e5); - }; - - // node_modules/@lit/reactive-element/decorators/property.js - var o4 = { attribute: true, type: String, converter: u, reflect: false, hasChanged: f }; - var r5 = (t4 = o4, e5, r6) => { - const { kind: n5, metadata: i4 } = r6; - let s4 = globalThis.litPropertyMetadata.get(i4); - if (void 0 === s4 && globalThis.litPropertyMetadata.set(i4, s4 = /* @__PURE__ */ new Map()), s4.set(r6.name, t4), "accessor" === n5) { - const { name: o5 } = r6; - return { set(r7) { - const n6 = e5.get.call(this); - e5.set.call(this, r7), this.requestUpdate(o5, n6, t4); - }, init(e6) { - return void 0 !== e6 && this.C(o5, void 0, t4), e6; - } }; - } - if ("setter" === n5) { - const { name: o5 } = r6; - return function(r7) { - const n6 = this[o5]; - e5.call(this, r7), this.requestUpdate(o5, n6, t4); - }; - } - throw Error("Unsupported decorator location: " + n5); - }; - function n4(t4) { - return (e5, o5) => "object" == typeof o5 ? r5(t4, e5, o5) : ((t5, e6, o6) => { - const r6 = e6.hasOwnProperty(o6); - return e6.constructor.createProperty(o6, r6 ? { ...t5, wrapped: true } : t5), r6 ? Object.getOwnPropertyDescriptor(e6, o6) : void 0; - })(t4, e5, o5); - } - - // node_modules/@posit-dev/shiny-bindings-core/dist/OptionalShiny.js - var Shiny = window.Shiny; - - // node_modules/@posit-dev/shiny-bindings-core/dist/makeInputBinding.js - function makeInputBinding(tagName, { type = null } = {}) { - if (!Shiny) { - return; - } - class NewCustomBinding extends Shiny["InputBinding"] { - constructor() { - super(); - } - find(scope) { - return $(scope).find(tagName); - } - getValue(el) { - return el.value; - } - getType(_2) { - return type; - } - subscribe(el, callback) { - el.notifyBindingOfChange = (ad) => callback(ad ?? false); - } - unsubscribe(el) { - el.notifyBindingOfChange = (_2) => { - }; - } - } - Shiny.inputBindings.register(new NewCustomBinding(), `${tagName}-Binding`); - } - - // srcts/index.ts - var customInputTag = "custom-component"; - var CustomComponentEl = class extends s3 { - constructor() { - super(...arguments); - this.value = 0; - /* - * The callback function that is called when the value of the input changes. - * This alerts Shiny that the value has changed and it should check for the - * latest value. This is set by the input binding. - */ - this.notifyBindingOfChange = () => { - }; - } - /** - * Function to run when the increment button is clicked. - */ - onIncrement() { - this.value++; - this.notifyBindingOfChange(true); - } - render() { - return x` - - Value: ${this.value} - - `; - } - }; - CustomComponentEl.styles = i` - :host { - display: block; - border: solid 1px gray; - padding: 16px; - max-width: 800px; - width: fit-content; - } - `; - __decorateClass([ - n4({ type: Number }) - ], CustomComponentEl.prototype, "value", 2); - CustomComponentEl = __decorateClass([ - t3(customInputTag) - ], CustomComponentEl); - makeInputBinding(customInputTag); -})(); -/*! Bundled license information: - -@lit/reactive-element/css-tag.js: - (** - * @license - * Copyright 2019 Google LLC - * SPDX-License-Identifier: BSD-3-Clause - *) - -@lit/reactive-element/reactive-element.js: - (** - * @license - * Copyright 2017 Google LLC - * SPDX-License-Identifier: BSD-3-Clause - *) - -lit-html/lit-html.js: - (** - * @license - * Copyright 2017 Google LLC - * SPDX-License-Identifier: BSD-3-Clause - *) - -lit-element/lit-element.js: - (** - * @license - * Copyright 2017 Google LLC - * SPDX-License-Identifier: BSD-3-Clause - *) - -lit-html/is-server.js: - (** - * @license - * Copyright 2022 Google LLC - * SPDX-License-Identifier: BSD-3-Clause - *) - -@lit/reactive-element/decorators/custom-element.js: - (** - * @license - * Copyright 2017 Google LLC - * SPDX-License-Identifier: BSD-3-Clause - *) - -@lit/reactive-element/decorators/property.js: - (** - * @license - * Copyright 2017 Google LLC - * SPDX-License-Identifier: BSD-3-Clause - *) - -@lit/reactive-element/decorators/state.js: - (** - * @license - * Copyright 2017 Google LLC - * SPDX-License-Identifier: BSD-3-Clause - *) - -@lit/reactive-element/decorators/event-options.js: - (** - * @license - * Copyright 2017 Google LLC - * SPDX-License-Identifier: BSD-3-Clause - *) - -@lit/reactive-element/decorators/base.js: - (** - * @license - * Copyright 2017 Google LLC - * SPDX-License-Identifier: BSD-3-Clause - *) - -@lit/reactive-element/decorators/query.js: - (** - * @license - * Copyright 2017 Google LLC - * SPDX-License-Identifier: BSD-3-Clause - *) - -@lit/reactive-element/decorators/query-all.js: - (** - * @license - * Copyright 2017 Google LLC - * SPDX-License-Identifier: BSD-3-Clause - *) - -@lit/reactive-element/decorators/query-async.js: - (** - * @license - * Copyright 2017 Google LLC - * SPDX-License-Identifier: BSD-3-Clause - *) - -@lit/reactive-element/decorators/query-assigned-elements.js: - (** - * @license - * Copyright 2021 Google LLC - * SPDX-License-Identifier: BSD-3-Clause - *) - -@lit/reactive-element/decorators/query-assigned-nodes.js: - (** - * @license - * Copyright 2017 Google LLC - * SPDX-License-Identifier: BSD-3-Clause - *) -*/ diff --git a/shiny/templates/package-templates/.gitignore b/shiny/templates/package/.gitignore similarity index 100% rename from shiny/templates/package-templates/.gitignore rename to shiny/templates/package/.gitignore diff --git a/shiny/templates/package-templates/js-input/.gitignore b/shiny/templates/package/js-input/.gitignore similarity index 100% rename from shiny/templates/package-templates/js-input/.gitignore rename to shiny/templates/package/js-input/.gitignore diff --git a/shiny/templates/package-templates/js-input/README.md b/shiny/templates/package/js-input/README.md similarity index 100% rename from shiny/templates/package-templates/js-input/README.md rename to shiny/templates/package/js-input/README.md diff --git a/shiny/templates/package-templates/js-input/_template.json b/shiny/templates/package/js-input/_template.json similarity index 100% rename from shiny/templates/package-templates/js-input/_template.json rename to shiny/templates/package/js-input/_template.json diff --git a/shiny/templates/package-templates/js-input/custom_component/__init__.py b/shiny/templates/package/js-input/custom_component/__init__.py similarity index 100% rename from shiny/templates/package-templates/js-input/custom_component/__init__.py rename to shiny/templates/package/js-input/custom_component/__init__.py diff --git a/shiny/templates/package-templates/js-input/custom_component/custom_component.py b/shiny/templates/package/js-input/custom_component/custom_component.py similarity index 100% rename from shiny/templates/package-templates/js-input/custom_component/custom_component.py rename to shiny/templates/package/js-input/custom_component/custom_component.py diff --git a/shiny/templates/package-templates/js-input/example-app/app.py b/shiny/templates/package/js-input/example-app/app.py similarity index 100% rename from shiny/templates/package-templates/js-input/example-app/app.py rename to shiny/templates/package/js-input/example-app/app.py diff --git a/shiny/templates/package-templates/js-input/package-lock.json b/shiny/templates/package/js-input/package-lock.json similarity index 100% rename from shiny/templates/package-templates/js-input/package-lock.json rename to shiny/templates/package/js-input/package-lock.json diff --git a/shiny/templates/package-templates/js-input/package.json b/shiny/templates/package/js-input/package.json similarity index 100% rename from shiny/templates/package-templates/js-input/package.json rename to shiny/templates/package/js-input/package.json diff --git a/shiny/templates/package-templates/js-input/pyproject.toml b/shiny/templates/package/js-input/pyproject.toml similarity index 100% rename from shiny/templates/package-templates/js-input/pyproject.toml rename to shiny/templates/package/js-input/pyproject.toml diff --git a/shiny/templates/package-templates/js-input/srcts/index.ts b/shiny/templates/package/js-input/srcts/index.ts similarity index 100% rename from shiny/templates/package-templates/js-input/srcts/index.ts rename to shiny/templates/package/js-input/srcts/index.ts diff --git a/shiny/templates/package-templates/js-input/tsconfig.json b/shiny/templates/package/js-input/tsconfig.json similarity index 100% rename from shiny/templates/package-templates/js-input/tsconfig.json rename to shiny/templates/package/js-input/tsconfig.json diff --git a/shiny/templates/package-templates/js-output/.gitignore b/shiny/templates/package/js-output/.gitignore similarity index 100% rename from shiny/templates/package-templates/js-output/.gitignore rename to shiny/templates/package/js-output/.gitignore diff --git a/shiny/templates/package-templates/js-output/README.md b/shiny/templates/package/js-output/README.md similarity index 100% rename from shiny/templates/package-templates/js-output/README.md rename to shiny/templates/package/js-output/README.md diff --git a/shiny/templates/package-templates/js-output/_template.json b/shiny/templates/package/js-output/_template.json similarity index 100% rename from shiny/templates/package-templates/js-output/_template.json rename to shiny/templates/package/js-output/_template.json diff --git a/shiny/templates/package-templates/js-output/custom_component/__init__.py b/shiny/templates/package/js-output/custom_component/__init__.py similarity index 100% rename from shiny/templates/package-templates/js-output/custom_component/__init__.py rename to shiny/templates/package/js-output/custom_component/__init__.py diff --git a/shiny/templates/package-templates/js-output/custom_component/custom_component.py b/shiny/templates/package/js-output/custom_component/custom_component.py similarity index 100% rename from shiny/templates/package-templates/js-output/custom_component/custom_component.py rename to shiny/templates/package/js-output/custom_component/custom_component.py diff --git a/shiny/templates/package-templates/js-output/example-app/app.py b/shiny/templates/package/js-output/example-app/app.py similarity index 100% rename from shiny/templates/package-templates/js-output/example-app/app.py rename to shiny/templates/package/js-output/example-app/app.py diff --git a/shiny/templates/package-templates/js-output/package-lock.json b/shiny/templates/package/js-output/package-lock.json similarity index 100% rename from shiny/templates/package-templates/js-output/package-lock.json rename to shiny/templates/package/js-output/package-lock.json diff --git a/shiny/templates/package-templates/js-output/package.json b/shiny/templates/package/js-output/package.json similarity index 100% rename from shiny/templates/package-templates/js-output/package.json rename to shiny/templates/package/js-output/package.json diff --git a/shiny/templates/package-templates/js-output/pyproject.toml b/shiny/templates/package/js-output/pyproject.toml similarity index 100% rename from shiny/templates/package-templates/js-output/pyproject.toml rename to shiny/templates/package/js-output/pyproject.toml diff --git a/shiny/templates/package-templates/js-output/srcts/index.ts b/shiny/templates/package/js-output/srcts/index.ts similarity index 100% rename from shiny/templates/package-templates/js-output/srcts/index.ts rename to shiny/templates/package/js-output/srcts/index.ts diff --git a/shiny/templates/package-templates/js-output/tsconfig.json b/shiny/templates/package/js-output/tsconfig.json similarity index 100% rename from shiny/templates/package-templates/js-output/tsconfig.json rename to shiny/templates/package/js-output/tsconfig.json diff --git a/shiny/templates/package-templates/js-react/.gitignore b/shiny/templates/package/js-react/.gitignore similarity index 100% rename from shiny/templates/package-templates/js-react/.gitignore rename to shiny/templates/package/js-react/.gitignore diff --git a/shiny/templates/package-templates/js-react/README.md b/shiny/templates/package/js-react/README.md similarity index 100% rename from shiny/templates/package-templates/js-react/README.md rename to shiny/templates/package/js-react/README.md diff --git a/shiny/templates/package-templates/js-react/_template.json b/shiny/templates/package/js-react/_template.json similarity index 100% rename from shiny/templates/package-templates/js-react/_template.json rename to shiny/templates/package/js-react/_template.json diff --git a/shiny/templates/package-templates/js-react/custom_component/__init__.py b/shiny/templates/package/js-react/custom_component/__init__.py similarity index 100% rename from shiny/templates/package-templates/js-react/custom_component/__init__.py rename to shiny/templates/package/js-react/custom_component/__init__.py diff --git a/shiny/templates/package-templates/js-react/custom_component/custom_component.py b/shiny/templates/package/js-react/custom_component/custom_component.py similarity index 100% rename from shiny/templates/package-templates/js-react/custom_component/custom_component.py rename to shiny/templates/package/js-react/custom_component/custom_component.py diff --git a/shiny/templates/package-templates/js-react/example-app/app.py b/shiny/templates/package/js-react/example-app/app.py similarity index 100% rename from shiny/templates/package-templates/js-react/example-app/app.py rename to shiny/templates/package/js-react/example-app/app.py diff --git a/shiny/templates/package-templates/js-react/package-lock.json b/shiny/templates/package/js-react/package-lock.json similarity index 100% rename from shiny/templates/package-templates/js-react/package-lock.json rename to shiny/templates/package/js-react/package-lock.json diff --git a/shiny/templates/package-templates/js-react/package.json b/shiny/templates/package/js-react/package.json similarity index 100% rename from shiny/templates/package-templates/js-react/package.json rename to shiny/templates/package/js-react/package.json diff --git a/shiny/templates/package-templates/js-react/pyproject.toml b/shiny/templates/package/js-react/pyproject.toml similarity index 100% rename from shiny/templates/package-templates/js-react/pyproject.toml rename to shiny/templates/package/js-react/pyproject.toml diff --git a/shiny/templates/package-templates/js-react/srcts/index.tsx b/shiny/templates/package/js-react/srcts/index.tsx similarity index 100% rename from shiny/templates/package-templates/js-react/srcts/index.tsx rename to shiny/templates/package/js-react/srcts/index.tsx diff --git a/shiny/templates/package-templates/js-react/tsconfig.json b/shiny/templates/package/js-react/tsconfig.json similarity index 100% rename from shiny/templates/package-templates/js-react/tsconfig.json rename to shiny/templates/package/js-react/tsconfig.json From d3fa89c5d0d95fe88532d60a383ad8895f097a51 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Mon, 26 Aug 2024 21:29:49 -0400 Subject: [PATCH 17/22] tests: fix `templates/app` path in tests --- .../playwright/examples/test_shiny_create.py | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/playwright/examples/test_shiny_create.py b/tests/playwright/examples/test_shiny_create.py index d2d91f4f6..775051f83 100644 --- a/tests/playwright/examples/test_shiny_create.py +++ b/tests/playwright/examples/test_shiny_create.py @@ -48,7 +48,7 @@ def subprocess_create( @pytest.mark.flaky(reruns=reruns, reruns_delay=reruns_delay) -@pytest.mark.parametrize("ex_app_path", get_apps("shiny/templates/app-templates")) +@pytest.mark.parametrize("ex_app_path", get_apps("shiny/templates/app")) def test_template_examples(page: Page, ex_app_path: str) -> None: validate_example(page, ex_app_path) @@ -88,40 +88,40 @@ def test_parse_github_arg(): repo_owner="posit-dev", repo_name="py-shiny", ref="main", - path="shiny/templates/app-templates/basic-app", + path="shiny/templates/app/basic-app", ) # * {repo_owner}/{repo_name}@{ref}:{path} actual_ref_path = parse_github_arg( - "posit-dev/py-shiny@main:shiny/templates/app-templates/basic-app" + "posit-dev/py-shiny@main:shiny/templates/app/basic-app" ) assert actual_ref_path == expected # * {repo_owner}/{repo_name}:{path}@{ref} actual_path_ref = parse_github_arg( - "posit-dev/py-shiny:shiny/templates/app-templates/basic-app@main" + "posit-dev/py-shiny:shiny/templates/app/basic-app@main" ) assert actual_path_ref == expected # * {repo_owner}/{repo_name}/{path}@{ref} actual_path_slash_ref = parse_github_arg( - "posit-dev/py-shiny/shiny/templates/app-templates/basic-app@main" + "posit-dev/py-shiny/shiny/templates/app/basic-app@main" ) assert actual_path_slash_ref == expected # * {repo_owner}/{repo_name}/{path}?ref={ref} actual_path_slash_query = parse_github_arg( - "posit-dev/py-shiny/shiny/templates/app-templates/basic-app?ref=main" + "posit-dev/py-shiny/shiny/templates/app/basic-app?ref=main" ) assert actual_path_slash_query == expected actual_path_full = parse_github_arg( - "https://github.com/posit-dev/py-shiny/tree/main/shiny/templates/app-templates/basic-app" + "https://github.com/posit-dev/py-shiny/tree/main/shiny/templates/app/basic-app" ) assert actual_path_full == expected actual_path_part = parse_github_arg( - "github.com/posit-dev/py-shiny/tree/main/shiny/templates/app-templates/basic-app" + "github.com/posit-dev/py-shiny/tree/main/shiny/templates/app/basic-app" ) assert actual_path_part == expected @@ -130,25 +130,25 @@ def test_parse_github_arg(): # * {repo_owner}/{repo_name}:{path} actual_path_colon = parse_github_arg( - "posit-dev/py-shiny:shiny/templates/app-templates/basic-app" + "posit-dev/py-shiny:shiny/templates/app/basic-app" ) assert actual_path_colon == expected # * {repo_owner}/{repo_name}/{path} actual_path_slash = parse_github_arg( - "posit-dev/py-shiny/shiny/templates/app-templates/basic-app" + "posit-dev/py-shiny/shiny/templates/app/basic-app" ) assert actual_path_slash == expected # complicated ref actual_ref_tag = parse_github_arg( - "posit-dev/py-shiny@v0.1.0:shiny/templates/app-templates/basic-app" + "posit-dev/py-shiny@v0.1.0:shiny/templates/app/basic-app" ) expected.ref = "v0.1.0" assert actual_ref_tag == expected actual_ref_branch = parse_github_arg( - "posit-dev/py-shiny@feat/new-template:shiny/templates/app-templates/basic-app" + "posit-dev/py-shiny@feat/new-template:shiny/templates/app/basic-app" ) expected.ref = "feat/new-template" assert actual_ref_branch == expected From 9c2e47183b6be21bb9b2dd49027e81cf5d240f9a Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Tue, 27 Aug 2024 11:23:48 -0400 Subject: [PATCH 18/22] chore: rename `"name"` field to `"id"` --- shiny/_main_create.py | 46 ++++++++++++++----- .../templates/app/01-basic-app/_template.json | 2 +- .../app/02-basic-sidebar/_template.json | 2 +- .../templates/app/03-dashboard/_template.json | 2 +- .../app/04-dashboard-tips/_template.json | 2 +- .../app/05-basic-navigation/_template.json | 2 +- .../aws-bedrock-anthropic/_template.json | 2 +- .../enterprise/azure-openai/_template.json | 2 +- .../hello-providers/anthropic/_template.json | 2 +- .../hello-providers/gemini/_template.json | 2 +- .../hello-providers/langchain/_template.json | 2 +- .../hello-providers/ollama/_template.json | 2 +- .../hello-providers/openai/_template.json | 2 +- .../templates/package/js-input/_template.json | 2 +- .../package/js-output/_template.json | 2 +- .../templates/package/js-react/_template.json | 2 +- .../playwright/examples/test_shiny_create.py | 4 +- 17 files changed, 52 insertions(+), 28 deletions(-) diff --git a/shiny/_main_create.py b/shiny/_main_create.py index bcb778dfe..a9f5e2a64 100644 --- a/shiny/_main_create.py +++ b/shiny/_main_create.py @@ -35,6 +35,7 @@ cli_url, cli_verbatim, cli_wait, + cli_warning, directory_prompt, ) @@ -45,7 +46,7 @@ def choice_from_templates(templates: list[ShinyTemplate]) -> list[Choice]: - return [Choice(title=t.title, value=t.name) for t in templates] + return [Choice(title=t.title, value=t.id) for t in templates] @dataclass @@ -59,8 +60,9 @@ class ShinyTemplate: Attributes ---------- - name - The name of the Shiny template. + id + The identifier of the Shiny template. This `id` should be unique within a + repository of templates. path The path to the `_template.json` file or the root directory of the template. type @@ -80,7 +82,7 @@ class ShinyTemplate: can be "action", "info", "warning", "danger", or "text". """ - name: str + id: str path: Path type: str = "app" title: str | None = None @@ -116,6 +118,7 @@ def __init__( def find_templates(path: Path | str = ".") -> list[ShinyTemplate]: path = Path(path) templates: list[ShinyTemplate] = [] + duplicated_ids: set[str] = set() template_files = sorted(path.glob("**/_template.json")) for tf in template_files: @@ -137,12 +140,16 @@ def find_templates(path: Path | str = ".") -> list[ShinyTemplate]: if isinstance(next_steps, str): next_steps = [next_steps] - if "name" not in template: - raise ValueError(f"Template in {tf} is missing a 'name' field.") + if "id" not in template: + raise ValueError(f"Template in {tf} is missing a 'id' field.") + + id = template["id"] + if id in [t.id for t in templates]: + duplicated_ids.add(id) templates.append( ShinyTemplate( - name=template["name"], + id=id, path=tf.parent.absolute(), title=template.get("title"), type=template.get("type", "app"), @@ -152,12 +159,29 @@ def find_templates(path: Path | str = ".") -> list[ShinyTemplate]: ) ) + if duplicated_ids: + click.echo( + cli_danger( + "Warning: The following templates contain duplicate IDs. " + + "Only the first occurrence will be used." + ) + ) + for id in duplicated_ids: + paths = [t.path.relative_to(path) for t in templates if t.id == id] + click.echo( + cli_warning( + cli_code(f'"id": "{id}"') + + " used by: " + + ", ".join([cli_field(str(p)) for p in paths]) + ) + ) + return templates def template_by_name(templates: list[ShinyTemplate], name: str) -> ShinyTemplate | None: for template in templates: - if template.name == name: + if template.id == name: return template return None @@ -273,7 +297,7 @@ def use_internal_template( elif question_state == "_chat-ai": use_internal_chat_ai_template(dest_dir=dest_dir, package_name=package_name) else: - valid_choices = [t.name for t in app_templates + pkg_templates] + valid_choices = [t.id for t in app_templates + pkg_templates] if question_state not in valid_choices: raise click.BadOptionUsage( "--template", @@ -485,7 +509,7 @@ def use_github_template( template_dir = template_dir / template_name template = ShinyTemplate( - name=template_name, + id=template_name, title=f"Template from {spec_cli}", path=template_dir, ) @@ -623,7 +647,7 @@ def app_template_questions( dest_dir: Optional[Path] = None, ): template_dir = template.path - template_cli_name = cli_bold(cli_field(template.title or template.name)) + template_cli_name = cli_bold(cli_field(template.title or template.id)) if mode == "express" and not template.express_available: raise click.BadParameter( diff --git a/shiny/templates/app/01-basic-app/_template.json b/shiny/templates/app/01-basic-app/_template.json index c46685263..c2bdc26f1 100644 --- a/shiny/templates/app/01-basic-app/_template.json +++ b/shiny/templates/app/01-basic-app/_template.json @@ -1,6 +1,6 @@ { "type": "app", - "name": "basic-app", + "id": "basic-app", "title": "Basic app", "description": "A basic Shiny app template.", "next_steps": [ diff --git a/shiny/templates/app/02-basic-sidebar/_template.json b/shiny/templates/app/02-basic-sidebar/_template.json index 7117f643b..148a07102 100644 --- a/shiny/templates/app/02-basic-sidebar/_template.json +++ b/shiny/templates/app/02-basic-sidebar/_template.json @@ -1,6 +1,6 @@ { "type": "app", - "name": "basic-sidebar", + "id": "basic-app", "title": "Sidebar layout", "description": "An app with inputs in a sidebar and a plot in the main area.", "next_steps": [ diff --git a/shiny/templates/app/03-dashboard/_template.json b/shiny/templates/app/03-dashboard/_template.json index de079ccf3..09ad15e60 100644 --- a/shiny/templates/app/03-dashboard/_template.json +++ b/shiny/templates/app/03-dashboard/_template.json @@ -1,6 +1,6 @@ { "type": "app", - "name": "dashboard", + "id": "dashboard", "title": "Basic dashboard", "description": "A basic, single page dashboard with value boxes, two plots in cards and a sidebar.", "next_steps": [ diff --git a/shiny/templates/app/04-dashboard-tips/_template.json b/shiny/templates/app/04-dashboard-tips/_template.json index 53556ffab..a3998731c 100644 --- a/shiny/templates/app/04-dashboard-tips/_template.json +++ b/shiny/templates/app/04-dashboard-tips/_template.json @@ -1,6 +1,6 @@ { "type": "app", - "name": "dashboard-tips", + "id": "dashboard-tips", "title": "Intermediate dashboard", "description": "An intermediate dashboard with value boxes, several plots in cards and a sidebar.", "next_steps": [ diff --git a/shiny/templates/app/05-basic-navigation/_template.json b/shiny/templates/app/05-basic-navigation/_template.json index 51c99a99b..9d530fcca 100644 --- a/shiny/templates/app/05-basic-navigation/_template.json +++ b/shiny/templates/app/05-basic-navigation/_template.json @@ -1,6 +1,6 @@ { "type": "app", - "name": "basic-navigation", + "id": "basic-navigation", "title": "Navigating multiple pages/panels", "description": "An app with a top navigation bar and two pages.", "next_steps": [ diff --git a/shiny/templates/chat/enterprise/aws-bedrock-anthropic/_template.json b/shiny/templates/chat/enterprise/aws-bedrock-anthropic/_template.json index 33a8fd7e0..39712d0f7 100644 --- a/shiny/templates/chat/enterprise/aws-bedrock-anthropic/_template.json +++ b/shiny/templates/chat/enterprise/aws-bedrock-anthropic/_template.json @@ -1,5 +1,5 @@ { "type": "app", - "name": "chat-ai-anthropic-aws", + "id": "chat-ai-anthropic-aws", "title": "Chat AI using Anthropic via AWS Bedrock" } diff --git a/shiny/templates/chat/enterprise/azure-openai/_template.json b/shiny/templates/chat/enterprise/azure-openai/_template.json index dbe745cb0..14702f9c7 100644 --- a/shiny/templates/chat/enterprise/azure-openai/_template.json +++ b/shiny/templates/chat/enterprise/azure-openai/_template.json @@ -1,5 +1,5 @@ { "type": "app", - "name": "chat-ai-azure-openai", + "id": "chat-ai-azure-openai", "title": "Chat AI using OpenAI via Azure" } diff --git a/shiny/templates/chat/hello-providers/anthropic/_template.json b/shiny/templates/chat/hello-providers/anthropic/_template.json index 73abc733c..79e2bf257 100644 --- a/shiny/templates/chat/hello-providers/anthropic/_template.json +++ b/shiny/templates/chat/hello-providers/anthropic/_template.json @@ -1,5 +1,5 @@ { "type": "app", - "name": "chat-ai-anthropic", + "id": "chat-ai-anthropic", "title": "Chat AI using Anthropic" } diff --git a/shiny/templates/chat/hello-providers/gemini/_template.json b/shiny/templates/chat/hello-providers/gemini/_template.json index d8dd27b9a..baf30e7cd 100644 --- a/shiny/templates/chat/hello-providers/gemini/_template.json +++ b/shiny/templates/chat/hello-providers/gemini/_template.json @@ -1,5 +1,5 @@ { "type": "app", - "name": "chat-ai-gemini", + "id": "chat-ai-gemini", "title": "Chat AI using Google Gemini" } diff --git a/shiny/templates/chat/hello-providers/langchain/_template.json b/shiny/templates/chat/hello-providers/langchain/_template.json index e8175706b..3ac04a285 100644 --- a/shiny/templates/chat/hello-providers/langchain/_template.json +++ b/shiny/templates/chat/hello-providers/langchain/_template.json @@ -1,5 +1,5 @@ { "type": "app", - "name": "chat-ai-langchain", + "id": "chat-ai-langchain", "title": "Chat AI using LangChain" } diff --git a/shiny/templates/chat/hello-providers/ollama/_template.json b/shiny/templates/chat/hello-providers/ollama/_template.json index 47046e8dc..9a1c53ccc 100644 --- a/shiny/templates/chat/hello-providers/ollama/_template.json +++ b/shiny/templates/chat/hello-providers/ollama/_template.json @@ -1,5 +1,5 @@ { "type": "app", - "name": "chat-ai-ollama", + "id": "chat-ai-ollama", "title": "Chat AI using Ollama" } diff --git a/shiny/templates/chat/hello-providers/openai/_template.json b/shiny/templates/chat/hello-providers/openai/_template.json index b9ffb4741..89bfb15d3 100644 --- a/shiny/templates/chat/hello-providers/openai/_template.json +++ b/shiny/templates/chat/hello-providers/openai/_template.json @@ -1,5 +1,5 @@ { "type": "app", - "name": "chat-ai-openai", + "id": "chat-ai-openai", "title": "Chat AI using OpenAI" } diff --git a/shiny/templates/package/js-input/_template.json b/shiny/templates/package/js-input/_template.json index 292931a44..f5bc5f1f2 100644 --- a/shiny/templates/package/js-input/_template.json +++ b/shiny/templates/package/js-input/_template.json @@ -1,6 +1,6 @@ { "type": "package", - "name": "js-input", + "id": "js-input", "title": "Input component", "description": "A Python package template providing a custom Shiny input component.", "follow_up": [ diff --git a/shiny/templates/package/js-output/_template.json b/shiny/templates/package/js-output/_template.json index d1018d9ef..440eafa5e 100644 --- a/shiny/templates/package/js-output/_template.json +++ b/shiny/templates/package/js-output/_template.json @@ -1,6 +1,6 @@ { "type": "package", - "name": "js-output", + "id": "js-output", "title": "Output component", "description": "A Python package template providing a custom Shiny output component.", "follow_up": [ diff --git a/shiny/templates/package/js-react/_template.json b/shiny/templates/package/js-react/_template.json index d73d2b012..1b26e6ecc 100644 --- a/shiny/templates/package/js-react/_template.json +++ b/shiny/templates/package/js-react/_template.json @@ -1,6 +1,6 @@ { "type": "package", - "name": "js-react", + "id": "js-react", "title": "React component", "description": "A Python package template providing a custom React-based Shiny component.", "follow_up": [ diff --git a/tests/playwright/examples/test_shiny_create.py b/tests/playwright/examples/test_shiny_create.py index 775051f83..662ae9214 100644 --- a/tests/playwright/examples/test_shiny_create.py +++ b/tests/playwright/examples/test_shiny_create.py @@ -53,8 +53,8 @@ def test_template_examples(page: Page, ex_app_path: str) -> None: validate_example(page, ex_app_path) -app_templates = [t.name for t in shiny_internal_templates.apps] -pkg_templates = [t.name for t in shiny_internal_templates.packages] +app_templates = [t.id for t in shiny_internal_templates.apps] +pkg_templates = [t.id for t in shiny_internal_templates.packages] assert len(app_templates) > 0 assert len(pkg_templates) > 0 From 86ac10a692b21a2869334b7b19acf23011a4095a Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Tue, 27 Aug 2024 11:24:06 -0400 Subject: [PATCH 19/22] docs: update ShinyInternalTemplates comments --- shiny/_main_create.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/shiny/_main_create.py b/shiny/_main_create.py index a9f5e2a64..0230fac4b 100644 --- a/shiny/_main_create.py +++ b/shiny/_main_create.py @@ -197,10 +197,13 @@ class ShinyInternalTemplates: To add a new template, create the template subfolder in either of the two template folders and add a `_template.json` file. See `ShinyTemplate` for expected fields. - * `use_template_internal()` is the initial menu seen, which presents `app-templates` - with additional choices. - * package-templates are also referred to as `js-components` in the code base, these - templates appear as a submenu and are handled by `js_component_questions()`. + * `use_template_internal()` is the initial menu seen, which presents `templates/app` + templates with additional choices. + * `templates/package` templates are also referred to as `js-components` in the code + base, these templates appear as a submenu and are handled by + `use_internal_package_template()`. + * `templates/chat` templates are generative AI templates and are handled by + `use_internal_chat_ai_template()`, """ def __init__(self): From c1564d0c9401d3b2f5195505eb2decbf847ea9a7 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Tue, 27 Aug 2024 11:26:06 -0400 Subject: [PATCH 20/22] fix: un-duplicate template id --- shiny/templates/app/02-basic-sidebar/_template.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shiny/templates/app/02-basic-sidebar/_template.json b/shiny/templates/app/02-basic-sidebar/_template.json index 148a07102..1fdbf7efb 100644 --- a/shiny/templates/app/02-basic-sidebar/_template.json +++ b/shiny/templates/app/02-basic-sidebar/_template.json @@ -1,6 +1,6 @@ { "type": "app", - "id": "basic-app", + "id": "basic-sidebar", "title": "Sidebar layout", "description": "An app with inputs in a sidebar and a plot in the main area.", "next_steps": [ From 88d4c9f64f86b18427dfe629f31cfab286eded39 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Tue, 27 Aug 2024 11:26:28 -0400 Subject: [PATCH 21/22] chore: only `basic-app` needs "in app directory" instruction --- shiny/templates/app/01-basic-app/_template.json | 2 +- shiny/templates/app/04-dashboard-tips/_template.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/shiny/templates/app/01-basic-app/_template.json b/shiny/templates/app/01-basic-app/_template.json index c2bdc26f1..0ea6216f1 100644 --- a/shiny/templates/app/01-basic-app/_template.json +++ b/shiny/templates/app/01-basic-app/_template.json @@ -4,7 +4,7 @@ "title": "Basic app", "description": "A basic Shiny app template.", "next_steps": [ - "Run the app with `shiny run app.py`." + "Run the app with `shiny run app.py` from the app directory." ], "follow_up": [ { diff --git a/shiny/templates/app/04-dashboard-tips/_template.json b/shiny/templates/app/04-dashboard-tips/_template.json index a3998731c..0c514ac0d 100644 --- a/shiny/templates/app/04-dashboard-tips/_template.json +++ b/shiny/templates/app/04-dashboard-tips/_template.json @@ -4,7 +4,7 @@ "title": "Intermediate dashboard", "description": "An intermediate dashboard with value boxes, several plots in cards and a sidebar.", "next_steps": [ - "Run the app with `shiny run app.py` from the app directory." + "Run the app with `shiny run app.py`." ], "follow_up": [ { From 7ba15cc940df12605feb0b6edcf5d7efd8602a06 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Tue, 27 Aug 2024 14:00:36 -0400 Subject: [PATCH 22/22] docs: add changelog item --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 092eaa051..31b5fdea8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * `shiny create` now supports a succinct format for specifying the GitHub repository via the `--github` flag, e.g. `--github posit-dev/py-shiny-templates`. You can now also use `--github` and `--template` together, in which case `--github` should point to a repository containing a directory matching the name provided in `--template`. (#1623) +* `shiny create` now identifies templates in external repositories using a `_template.json` metadata file. This file should contain at an `"id"` and optionally a `"title"` and `"description"`. When `shiny create` is called with the `--github` flag but without a `--template` flag, it will offer a menu listing all available templates in the repository. (#1631) + ### Other changes ### Bug fixes