44from __future__ import annotations
55
66import ast
7- from pathlib import Path
87import 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