Skip to content

Commit 373f639

Browse files
committed
separate bundle creation from URL encoding
1 parent 6d5c22a commit 373f639

File tree

3 files changed

+107
-40
lines changed

3 files changed

+107
-40
lines changed

shinylive/__init__.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
"""A package for packaging Shiny applications that run on Python in the browser."""
22

33
from . import _version
4-
from ._url import decode_shinylive_url, encode_shinylive_url
4+
from ._url import (
5+
create_shinylive_bundle_file,
6+
create_shinylive_bundle_text,
7+
create_shinylive_url,
8+
decode_shinylive_url,
9+
)
510

611
__version__ = _version.SHINYLIVE_PACKAGE_VERSION
712

813
__all__ = (
914
"decode_shinylive_url",
10-
"encode_shinylive_url",
15+
"create_shinylive_url",
16+
"create_shinylive_bundle_text",
17+
"create_shinylive_bundle_file",
1118
)

shinylive/_main.py

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,12 @@
88
import click
99

1010
from . import _assets, _deps, _export, _version
11-
from ._url import decode_shinylive_url, encode_shinylive_url
11+
from ._url import (
12+
create_shinylive_bundle_file,
13+
create_shinylive_bundle_text,
14+
create_shinylive_url,
15+
decode_shinylive_url,
16+
)
1217
from ._utils import print_as_json
1318

1419

@@ -519,6 +524,9 @@ def url() -> None:
519524
@click.option(
520525
"-v", "--view", is_flag=True, default=False, help="Open the link in a browser."
521526
)
527+
@click.option(
528+
"--json", is_flag=True, default=False, help="Print the bundle as JSON to stdout."
529+
)
522530
@click.option(
523531
"--no-header", is_flag=True, default=False, help="Hide the Shinylive header."
524532
)
@@ -529,6 +537,7 @@ def encode(
529537
files: Optional[tuple[str, ...]] = None,
530538
mode: Literal["editor", "app"] = "editor",
531539
language: Optional[str] = None,
540+
json: bool = False,
532541
no_header: bool = False,
533542
view: bool = False,
534543
) -> None:
@@ -549,15 +558,24 @@ def encode(
549558
else:
550559
lang = None
551560

552-
url = encode_shinylive_url(
553-
app=app_in,
554-
files=files,
561+
if "\n" in app_in:
562+
bundle = create_shinylive_bundle_text(app_in, files, lang)
563+
else:
564+
bundle = create_shinylive_bundle_file(app_in, files, lang)
565+
566+
if json:
567+
print_as_json(bundle["files"])
568+
if not view:
569+
return
570+
571+
url = create_shinylive_url(
572+
bundle,
555573
mode=mode,
556-
language=lang,
557574
header=not no_header,
558575
)
559576

560-
print(url)
577+
if not json:
578+
print(url)
561579

562580
if view:
563581
import webbrowser

shinylive/_url.py

Lines changed: 74 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,55 @@ class FileContentJson(TypedDict):
2323
type: NotRequired[Literal["text", "binary"]]
2424

2525

26-
def encode_shinylive_url(
27-
app: str | Path,
28-
files: Optional[str | Path | Sequence[str | Path]] = None,
26+
class AppBundle(TypedDict):
27+
language: Literal["py", "r"]
28+
files: list[FileContentJson]
29+
30+
31+
def create_shinylive_url(
32+
bundle: AppBundle,
2933
mode: Literal["editor", "app"] = "editor",
30-
language: Optional[Literal["py", "r"]] = None,
3134
header: bool = True,
3235
) -> str:
36+
""" """
37+
38+
file_lz = lzstring_file_bundle(bundle["files"])
39+
40+
base = "https://shinylive.io"
41+
h = "h=0&" if not header and mode == "app" else ""
42+
43+
return f"{base}/{bundle['language']}/{mode}/#{h}code={file_lz}"
44+
45+
46+
def create_shinylive_bundle_text(
47+
app: str,
48+
files: Optional[str | Path | Sequence[str | Path]] = None,
49+
language: Optional[Literal["py", "r"]] = None,
50+
root_dir: str | Path = ".",
51+
) -> AppBundle:
52+
if language is None:
53+
language = detect_app_language(app)
54+
elif language not in ["py", "r"]:
55+
raise ValueError(
56+
f"Language '{language}' is not supported. Please specify one of 'py' or 'r'."
57+
)
58+
59+
app_fc: FileContentJson = {
60+
"name": f"app.{'py' if language == 'py' else 'R'}",
61+
"content": app,
62+
}
63+
64+
return {
65+
"language": language,
66+
"files": add_supporting_files_to_bundle(app_fc, files, root_dir),
67+
}
68+
69+
70+
def create_shinylive_bundle_file(
71+
app: str | Path,
72+
files: Optional[str | Path | Sequence[str | Path]] = None,
73+
language: Optional[Literal["py", "r"]] = None,
74+
) -> AppBundle:
3375
"""
3476
Generate a URL for a [ShinyLive application](https://shinylive.io).
3577
@@ -57,20 +99,34 @@ def encode_shinylive_url(
5799

58100
if language is None:
59101
language = detect_app_language(app)
102+
elif language not in ["py", "r"]:
103+
raise ValueError(
104+
f"Language '{language}' is not supported. Please specify one of 'py' or 'r'."
105+
)
60106

61-
# if app has a newline, then it's app content, not a path
62-
if isinstance(app, str) and "\n" in app:
63-
app_path = ""
64-
root_dir = Path(".")
65-
app_fc: FileContentJson = {
66-
"name": f"app.{'py' if language == 'py' else 'R'}",
67-
"content": app,
68-
}
69-
file_bundle = [app_fc]
70-
else:
71-
app_path = Path(app)
72-
root_dir = app_path.parent
73-
file_bundle = [read_file(app, root_dir)]
107+
app_path = Path(app)
108+
root_dir = app_path.parent
109+
app_fc = read_file(app, root_dir)
110+
111+
# if the app is not named either `ui.R` or `server.R`, then make it app.py or app.R
112+
if app_fc["name"] not in ["ui.R", "server.R"]:
113+
app_fc["name"] = f"app.{'py' if language == 'py' else 'R'}"
114+
115+
return {
116+
"language": language,
117+
"files": add_supporting_files_to_bundle(app_fc, files, root_dir, app_path),
118+
}
119+
120+
121+
def add_supporting_files_to_bundle(
122+
app: FileContentJson,
123+
files: Optional[str | Path | Sequence[str | Path]] = None,
124+
root_dir: str | Path = ".",
125+
app_path: str | Path = "",
126+
) -> list[FileContentJson]:
127+
app_path = Path(app_path)
128+
129+
file_bundle = [app]
74130

75131
if isinstance(files, (str, Path)):
76132
files = [files]
@@ -88,21 +144,7 @@ def encode_shinylive_url(
88144
read_file(file, root_dir) for file in file_list if Path(file) != app_path
89145
]
90146

91-
if language not in ["py", "r"]:
92-
raise ValueError(
93-
f"Language '{language}' is not supported. Please specify one of 'py' or 'r'."
94-
)
95-
96-
# if first file is not named either `ui.R` or `server.R`, then make it app.{language}
97-
if file_bundle[0]["name"] not in ["ui.R", "server.R"]:
98-
file_bundle[0]["name"] = f"app.{'py' if language == 'py' else 'R'}"
99-
100-
file_lz = lzstring_file_bundle(file_bundle)
101-
102-
base = "https://shinylive.io"
103-
h = "h=0&" if not header and mode == "app" else ""
104-
105-
return f"{base}/{language}/{mode}/#{h}code={file_lz}"
147+
return file_bundle
106148

107149

108150
def detect_app_language(app: str | Path) -> Literal["py", "r"]:

0 commit comments

Comments
 (0)