Skip to content

Commit a95eed0

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

File tree

1 file changed

+41
-6
lines changed

1 file changed

+41
-6
lines changed

rsconnect/shiny_express.py

Lines changed: 41 additions & 6 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,25 +66,50 @@ 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
6676
elif node.module == "shiny" and any(alias.name == "express" for alias in node.names):
6777
self.found_shiny_express_import = True
6878

6979
# Visit top-level nodes.
70-
def visit_Module(self, node: ast.Module):
80+
def visit_Module(self, node: ast.Module) -> None:
7181
super().generic_visit(node)
7282

7383
# 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):
84+
def generic_visit(self, node: ast.AST) -> None:
7585
pass
7686

7787

88+
def find_magic_comment_mode(content: str) -> Literal["core", "express"] | None:
89+
"""
90+
Look for a magic comment of the form "# shiny_mode: express" or "# shiny_mode:
91+
core".
92+
93+
If a line of the form "# shiny_mode: x" is found, where "x" is not "express" or
94+
"core", then a message will be printed to stderr.
95+
96+
Returns
97+
-------
98+
:
99+
`True` if Shiny Express comment is found, `False` if Shiny Core comment is
100+
found, and `None` if no magic comment is found.
101+
"""
102+
m = re.search(r"^#[ \t]*shiny_mode:[ \t]*(\S*)[ \t]*$", content, re.MULTILINE)
103+
if m is not None:
104+
shiny_mode = cast(str, m.group(1))
105+
if shiny_mode in ("express", "core"):
106+
return shiny_mode
107+
else:
108+
print(f'Invalid shiny_mode: "{shiny_mode}"', file=sys.stderr)
109+
110+
return None
111+
112+
78113
def escape_to_var_name(x: str) -> str:
79114
"""
80115
Given a string, escape it to a valid Python variable name which contains

0 commit comments

Comments
 (0)