Skip to content

Commit 0945a23

Browse files
committed
Add support for shiny mode magic comment
1 parent 18bfc5a commit 0945a23

File tree

1 file changed

+44
-7
lines changed

1 file changed

+44
-7
lines changed

rsconnect/shiny_express.py

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44
from __future__ import annotations
55

66
import ast
7-
from pathlib import Path
87
import re
8+
import sys
9+
from pathlib import Path
10+
from typing import Literal, cast
911

1012
__all__ = ("is_express_app",)
1113

@@ -39,8 +41,16 @@ def is_express_app(app: str, app_dir: str | None) -> bool:
3941

4042
try:
4143
# Read the file, parse it, and look for any imports of shiny.express.
42-
with open(app_path) as f:
44+
with open(app_path, encoding="utf-8") as f:
4345
content = f.read()
46+
47+
# Check for magic comment in the first 1000 characters
48+
forced_mode = find_magic_comment_mode(content[:1000])
49+
if forced_mode == "express":
50+
return True
51+
elif forced_mode == "core":
52+
return False
53+
4454
tree = ast.parse(content, app_path)
4555
detector = DetectShinyExpressVisitor()
4656
detector.visit(tree)
@@ -56,22 +66,24 @@ def __init__(self):
5666
super().__init__()
5767
self.found_shiny_express_import = False
5868

59-
def visit_Import(self, node: ast.Import):
69+
def visit_Import(self, node: ast.Import) -> None:
6070
if any(alias.name == "shiny.express" for alias in node.names):
6171
self.found_shiny_express_import = True
6272

63-
def visit_ImportFrom(self, node: ast.ImportFrom):
73+
def visit_ImportFrom(self, node: ast.ImportFrom) -> None:
6474
if node.module == "shiny.express":
6575
self.found_shiny_express_import = True
66-
elif node.module == "shiny" and any(alias.name == "express" for alias in node.names):
76+
elif node.module == "shiny" and any(
77+
alias.name == "express" for alias in node.names
78+
):
6779
self.found_shiny_express_import = True
6880

6981
# Visit top-level nodes.
70-
def visit_Module(self, node: ast.Module):
82+
def visit_Module(self, node: ast.Module) -> None:
7183
super().generic_visit(node)
7284

7385
# Don't recurse into any nodes, so the we'll only ever look at top-level nodes.
74-
def generic_visit(self, node: ast.AST):
86+
def generic_visit(self, node: ast.AST) -> None:
7587
pass
7688

7789

@@ -97,3 +109,28 @@ def escape_to_var_name(x: str) -> str:
97109
is_first = False
98110

99111
return encoded
112+
113+
114+
def find_magic_comment_mode(content: str) -> Literal["core", "express"] | None:
115+
"""
116+
Look for a magic comment of the form "# shiny_mode: express" or "# shiny_mode:
117+
core".
118+
119+
If a line of the form "# shiny_mode: x" is found, where "x" is not "express" or
120+
"core", then a message will be printed to stderr.
121+
122+
Returns
123+
-------
124+
:
125+
`True` if Shiny Express comment is found, `False` if Shiny Core comment is
126+
found, and `None` if no magic comment is found.
127+
"""
128+
m = re.search(r"^#[ \t]*shiny_mode:[ \t]*(\S*)[ \t]*$", content, re.MULTILINE)
129+
if m is not None:
130+
shiny_mode = cast(str, m.group(1))
131+
if shiny_mode in ("express", "core"):
132+
return shiny_mode
133+
else:
134+
print(f'Invalid shiny_mode: "{shiny_mode}"', file=sys.stderr)
135+
136+
return None

0 commit comments

Comments
 (0)