Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add shinylive url command #20

Merged
merged 41 commits into from
Jan 12, 2024
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
313debf
feat(url): Add `shinylive url`command and `make_shinylive_url()` func…
gadenbuie Jan 4, 2024
d570bfe
export `make_shinylive_url()`
gadenbuie Jan 4, 2024
a231982
chore: app and files could be Paths
gadenbuie Jan 4, 2024
13a4d9d
require lzstring
gadenbuie Jan 4, 2024
b6944fd
remove `__all__` from `__init__.py`
gadenbuie Jan 4, 2024
18cf274
noqa and pyright ingore
gadenbuie Jan 4, 2024
163f452
feat: shinylive url (encode,decode)
gadenbuie Jan 4, 2024
cccffbc
allow piping into `shinylive url decode`
gadenbuie Jan 5, 2024
281e47a
allow piping into `shinylive url encode` and detect app source code
gadenbuie Jan 5, 2024
637725d
negotiate aggressively with the type checker
gadenbuie Jan 5, 2024
e36fbd7
demote unused f string
gadenbuie Jan 5, 2024
7b02ac9
add comment
gadenbuie Jan 5, 2024
dd3d6f9
document --out option
gadenbuie Jan 5, 2024
294341a
require `--help` so that piping into url encode works
gadenbuie Jan 5, 2024
0dea7a4
include files, recursively
gadenbuie Jan 5, 2024
d725e57
less aggressive type check convincing
gadenbuie Jan 5, 2024
a7cc038
rename --out to --dir
gadenbuie Jan 5, 2024
331f958
automatically detect app language when app is the text content
gadenbuie Jan 5, 2024
2b9d3ce
Apply suggestions from code review
gadenbuie Jan 6, 2024
479f55c
import Literal
gadenbuie Jan 6, 2024
b5da6d8
type narrow language from encode CLI -> internal
gadenbuie Jan 6, 2024
f635f55
don't need to import os
gadenbuie Jan 6, 2024
ef8218f
add note about decode result wrt --dir
gadenbuie Jan 6, 2024
2da9c8a
write base64-decoded binary files
gadenbuie Jan 6, 2024
07c952f
detect_app_language() returns "py" or "r"
gadenbuie Jan 6, 2024
8f9e75a
make FileContentJson.type not required
gadenbuie Jan 6, 2024
e1b0b81
only add header param in app mode
gadenbuie Jan 6, 2024
b87490b
if file is str|Path, promote to list
gadenbuie Jan 6, 2024
246cdad
improve FileContentJson typing throughout
gadenbuie Jan 6, 2024
4a96487
exclude _dev folder from checks
gadenbuie Jan 6, 2024
89f4d27
fix syntax for creating FileContenJson objects
gadenbuie Jan 6, 2024
6d5c22a
require typing-extensions
gadenbuie Jan 6, 2024
373f639
separate bundle creation from URL encoding
gadenbuie Jan 6, 2024
d3a7665
add `encode_shinylive_url()` and make only encode/decode public
gadenbuie Jan 8, 2024
dd75236
simplify types and remove need for AppBundle
gadenbuie Jan 8, 2024
6674c6b
move package version into a subpackage
gadenbuie Jan 8, 2024
a9199b7
wrap decode outputs in helper functions, too
gadenbuie Jan 8, 2024
b6bf76f
rename version to _version
gadenbuie Jan 12, 2024
c80e99c
docs: describe feature in changelog
gadenbuie Jan 12, 2024
5a73146
fix one more _version import
gadenbuie Jan 12, 2024
bc0fad7
bump package version to 0.1.3.9000
gadenbuie Jan 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ install_requires =
shiny
click>=8.1.7
appdirs>=1.4.4
lzstring>=1.0.4
gadenbuie marked this conversation as resolved.
Show resolved Hide resolved
tests_require =
pytest>=3
zip_safe = False
Expand Down
6 changes: 6 additions & 0 deletions shinylive/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
"""A package for packaging Shiny applications that run on Python in the browser."""

from . import _version
from ._url import decode_shinylive_url, encode_shinylive_url

__version__ = _version.SHINYLIVE_PACKAGE_VERSION

__all__ = (
"decode_shinylive_url",
"encode_shinylive_url",
)
149 changes: 149 additions & 0 deletions shinylive/_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import click

from . import _assets, _deps, _export, _version
from ._url import decode_shinylive_url, encode_shinylive_url
from ._utils import print_as_json


Expand Down Expand Up @@ -59,6 +60,7 @@ def list_commands(self, ctx: click.Context) -> list[str]:
# * language-resources
# * app-resources
# * Options: --json-file / stdin (required)
# * url


# #############################################################################
Expand Down Expand Up @@ -469,6 +471,153 @@ def defunct_help(cmd: str) -> str:
"""


# #############################################################################
# ## shinylive.io url
# #############################################################################


@main.group(
short_help="Create or decode a shinylive.io URL.",
help="Create or decode a shinylive.io URL.",
no_args_is_help=True,
cls=OrderedGroup,
)
def url() -> None:
pass


@url.command(
short_help="Create a shinylive.io URL from local files.",
help="""
Create a shinylive.io URL for a Shiny app from local files.

APP is the path to the primary Shiny app file.

FILES are additional supporting files or directories for the app.

On macOS, you can copy the URL to the clipboard with:

shinylive url encode app.py | pbcopy
""",
)
@click.option(
"-m",
"--mode",
type=click.Choice(["editor", "app"]),
required=True,
default="editor",
help="The shinylive mode: include the editor or show only the app.",
)
@click.option(
"-l",
"--language",
type=click.Choice(["python", "py", "R", "r"]),
required=False,
default=None,
help="The primary language used to run the app, by default inferred from the app file.",
)
@click.option(
"-v", "--view", is_flag=True, default=False, help="Open the link in a browser."
)
@click.option(
"--no-header", is_flag=True, default=False, help="Hide the Shinylive header."
)
@click.argument("app", type=str, nargs=1, required=True, default="-")
@click.argument("files", type=str, nargs=-1, required=False)
def encode(
gadenbuie marked this conversation as resolved.
Show resolved Hide resolved
app: str,
files: Optional[tuple[str, ...]] = None,
mode: str = "editor",
gadenbuie marked this conversation as resolved.
Show resolved Hide resolved
language: Optional[str] = None,
no_header: bool = False,
view: bool = False,
) -> None:
if app == "-":
app_in = sys.stdin.read()
else:
app_in = app

url = encode_shinylive_url(
app=app_in,
files=files,
mode=mode,
language=language,
header=not no_header,
)

print(url)

if view:
import webbrowser

webbrowser.open(url)
return
gadenbuie marked this conversation as resolved.
Show resolved Hide resolved


@url.command(
short_help="Decode a shinylive.io URL.",
help="""
Decode a shinylive.io URL.

URL is the shinylive editor or app URL. If not specified, the URL will be read from
stdin, allowing you to read the URL from a file or the clipboard.

On macOS, you can read the URL from the clipboard with:

pbpaste | shinylive url decode
""",
)
@click.option(
"--dir",
type=str,
default=None,
help="Output directory into which the app's files will be written. The directory is created if it does not exist. ",
gadenbuie marked this conversation as resolved.
Show resolved Hide resolved
)
@click.option(
"--json",
is_flag=True,
default=False,
help="Prints the decoded shinylive bundle as JSON to stdout, ignoring --out.",
gadenbuie marked this conversation as resolved.
Show resolved Hide resolved
)
@click.argument("url", type=str, nargs=1, default="-")
def decode(url: str, dir: Optional[str] = None, json: bool = False) -> None:
if url == "-":
url_in = sys.stdin.read()
else:
url_in = url
bundle = decode_shinylive_url(str(url_in))

if json:
print_as_json(bundle)
return

if dir is not None:
import os

out_dir = Path(dir)
os.makedirs(out_dir, exist_ok=True)
gadenbuie marked this conversation as resolved.
Show resolved Hide resolved
for file in bundle:
with open(out_dir / file["name"], "w") as f_out:
f_out.write(
file["content"].encode("utf-8", errors="ignore").decode("utf-8")
)
gadenbuie marked this conversation as resolved.
Show resolved Hide resolved
else:
print("")
for file in bundle:
print(f"## file: {file['name']}")
if "type" in file and file["type"] == "binary":
print("## type: binary")
print("")
print(file["content"].encode("utf-8", errors="ignore").decode("utf-8"))
gadenbuie marked this conversation as resolved.
Show resolved Hide resolved

return
gadenbuie marked this conversation as resolved.
Show resolved Hide resolved


# #############################################################################
# ## Deprecated commands
# #############################################################################


def defunct_error_txt(cmd: str) -> str:
return f"Error: { defunct_help(cmd) }"

Expand Down
Loading