1+ # ' Detect the type of Shiny application (R or Python)
2+ # '
3+ # ' @param app_dir Character. Path to the Shiny application directory.
4+ # '
5+ # ' @return Character. Either "r" or "python".
6+ # '
7+ # ' @keywords internal
8+ detect_app_type <- function (app_dir ) {
9+ # Look for R files
10+ r_files <- list.files(app_dir , pattern = " \\ .R$|\\ .r$" , recursive = TRUE )
11+
12+ # Look for Python files
13+ py_files <- list.files(app_dir , pattern = " \\ .py$" , recursive = TRUE )
14+
15+ # Check for Python's app.py or server.py
16+ has_py_app <- any(grepl(" app\\ .py$|server\\ .py$" , py_files ))
17+
18+ # Check for R's app.R, server.R, or ui.R
19+ has_r_app <- any(grepl(" app\\ .R$|server\\ .R$|ui\\ .R$" , r_files , ignore.case = TRUE ))
20+
21+ if (has_py_app && ! has_r_app ) {
22+ return (" python" )
23+ } else if (has_r_app || length(r_files ) > 0 ) {
24+ return (" r" )
25+ } else if (length(py_files ) > 0 ) {
26+ return (" python" )
27+ } else {
28+ # If we can't determine, default to R
29+ warning(" Could not determine app type, defaulting to R. Specify app_type manually if needed." )
30+ return (" r" )
31+ }
32+ }
33+
34+ # ' Check if directory contains Shiny app files
35+ # '
36+ # ' @param app_dir Character. Path to the Shiny application directory.
37+ # '
38+ # ' @return Logical. TRUE if the directory contains valid Shiny app files.
39+ # '
40+ # ' @keywords internal
41+ has_shiny_app_files <- function (app_dir ) {
42+ # Check for R Shiny app files
43+ has_r_app <- file.exists(file.path(app_dir , " app.R" )) ||
44+ (file.exists(file.path(app_dir , " ui.R" )) &&
45+ file.exists(file.path(app_dir , " server.R" )))
46+
47+ # Check for Python Shiny app files
48+ has_py_app <- file.exists(file.path(app_dir , " app.py" )) ||
49+ (file.exists(file.path(app_dir , " ui.py" )) &&
50+ file.exists(file.path(app_dir , " server.py" )))
51+
52+ return (has_r_app || has_py_app )
53+ }
54+
55+ # ' Check if directory contains valid Shiny app files and abort if not
56+ # '
57+ # ' @param app_dir Character. Path to the Shiny application directory.
58+ # '
59+ # ' @return Invisibly returns TRUE if valid, otherwise aborts with an error message.
60+ # '
61+ # ' @keywords internal
62+ check_shiny_app_files <- function (app_dir ) {
63+ if (! has_shiny_app_files(app_dir )) {
64+ cli :: cli_abort(c(
65+ " Directory does not appear to contain a valid Shiny application." ,
66+ " i" = " Expected files for R apps: {.file app.R}, or {.file ui.R} and {.file server.R}" ,
67+ " i" = " Expected files for Python apps: {.file app.py}, or {.file ui.py} and {.file server.py}"
68+ ))
69+ }
70+
71+ invisible (TRUE )
72+ }
73+
74+ # ' Detect dependencies for a Shiny application
75+ # '
76+ # ' @param app_dir Character. Path to the Shiny application directory.
77+ # ' @param app_type Character. Either "r" or "python".
78+ # '
79+ # ' @return Character vector with detected dependencies.
80+ # '
81+ # ' @keywords internal
82+ detect_dependencies <- function (app_dir , app_type ) {
83+ if (app_type == " r" ) {
84+ return (detect_r_dependencies(app_dir ))
85+ } else if (app_type == " python" ) {
86+ return (detect_python_dependencies(app_dir ))
87+ }
88+ }
89+ # ' Extract package names from library() and require() calls
90+ # '
91+ # ' @param r_code Character vector. R code to analyze.
92+ # '
93+ # ' @return Character vector of package names.
94+ # '
95+ # ' @keywords internal
96+ extract_library_packages <- function (r_code ) {
97+ lib_pattern <- " library\\ (([^,)]+)\\ )|require\\ (([^,)]+)\\ )"
98+ lib_matches <- gregexpr(lib_pattern , r_code , perl = TRUE )
99+
100+ packages <- character (0 )
101+ for (i in seq_along(r_code )) {
102+ if (lib_matches [[i ]][1 ] != - 1 ) {
103+ matches <- regmatches(r_code [i ], lib_matches [[i ]])
104+ for (match in matches ) {
105+ # Extract the package name from library()/require() call
106+ pkg_name <- gsub(" library\\ (([^,)]+)\\ )|require\\ (([^,)]+)\\ )" , " \\ 1\\ 2" , match , perl = TRUE )
107+ # Remove quotes if present
108+ pkg_name <- gsub(" ^['\" ]|['\" ]$" , " " , pkg_name )
109+ packages <- c(packages , pkg_name )
110+ }
111+ }
112+ }
113+
114+ return (packages )
115+ }
116+
117+ # ' Extract package names from namespace calls (package::function)
118+ # '
119+ # ' @param r_code Character vector. R code to analyze.
120+ # '
121+ # ' @return Character vector of package names.
122+ # '
123+ # ' @keywords internal
124+ extract_namespace_packages <- function (r_code ) {
125+ pkg_pattern <- " ([[:alnum:].]+)::"
126+ pkg_matches <- gregexpr(pkg_pattern , r_code , perl = TRUE )
127+
128+ packages <- character (0 )
129+ for (i in seq_along(r_code )) {
130+ if (pkg_matches [[i ]][1 ] != - 1 ) {
131+ matches <- regmatches(r_code [i ], pkg_matches [[i ]])
132+ for (match in matches ) {
133+ # Extract the package name from package:: call
134+ pkg_name <- gsub(" ([[:alnum:].]+)::" , " \\ 1" , match , perl = TRUE )
135+ packages <- c(packages , pkg_name )
136+ }
137+ }
138+ }
139+
140+ return (packages )
141+ }
142+
143+ # ' Detect R package dependencies
144+ # '
145+ # ' @param app_dir Character. Path to the Shiny application directory.
146+ # '
147+ # ' @return Character vector of detected R package dependencies.
148+ # '
149+ # ' @keywords internal
150+ detect_r_dependencies <- function (app_dir ) {
151+ # Look for R files
152+ r_files <- list.files(app_dir , pattern = " \\ .R$|\\ .r$" , full.names = TRUE , recursive = TRUE )
153+
154+ if (length(r_files ) == 0 ) {
155+ warning(" No R files found in the application directory." )
156+ return (c(" shiny" ))
157+ }
158+
159+ # Read all R files
160+ r_code <- unlist(lapply(r_files , readLines ))
161+
162+ # Extract packages from different patterns
163+ library_packages <- extract_library_packages(r_code )
164+ namespace_packages <- extract_namespace_packages(r_code )
165+
166+ # Combine and ensure shiny is included
167+ packages <- unique(c(" shiny" , library_packages , namespace_packages ))
168+ return (sort(packages ))
169+ }
170+
171+ # ' Detect Python package dependencies
172+ # '
173+ # ' @param app_dir Character. Path to the Shiny application directory.
174+ # '
175+ # ' @return Character vector of detected Python package dependencies.
176+ # '
177+ # ' @keywords internal
178+ detect_python_dependencies <- function (app_dir ) {
179+ # Look for requirements.txt
180+ req_file <- file.path(app_dir , " requirements.txt" )
181+
182+ if (file.exists(req_file )) {
183+ # Read requirements.txt
184+ packages <- readLines(req_file )
185+ # Remove comments and empty lines
186+ packages <- packages [! grepl(" ^\\ s*#" , packages ) & nzchar(trimws(packages ))]
187+ # Remove version specifications
188+ packages <- gsub(" ([^<>=~!]+)[<>=~!].*" , " \\ 1" , packages )
189+ # Trim whitespace
190+ packages <- trimws(packages )
191+ return (packages )
192+ }
193+
194+ # If no requirements.txt, look for import statements in Python files
195+ py_files <- list.files(app_dir , pattern = " \\ .py$" , full.names = TRUE , recursive = TRUE )
196+
197+ if (length(py_files ) == 0 ) {
198+ warning(" No Python files found in the application directory." )
199+ return (c(" shiny" ))
200+ }
201+
202+ # Read all Python files
203+ py_code <- unlist(lapply(py_files , readLines ))
204+
205+ # Look for import statements
206+ import_pattern <- " ^\\ s*import\\ s+([^\\ s.]+)|^\\ s*from\\ s+([^\\ s.]+)"
207+ import_matches <- gregexpr(import_pattern , py_code , perl = TRUE )
208+
209+ # Extract package names
210+ packages <- character (0 )
211+ for (i in seq_along(py_code )) {
212+ if (import_matches [[i ]][1 ] != - 1 ) {
213+ matches <- regmatches(py_code [i ], import_matches [[i ]])
214+ for (match in matches ) {
215+ # Extract the package name from import or from statement
216+ pkg_name <- gsub(" ^\\ s*import\\ s+([^\\ s.]+)|^\\ s*from\\ s+([^\\ s.]+)" , " \\ 1\\ 2" , match , perl = TRUE )
217+ packages <- c(packages , pkg_name )
218+ }
219+ }
220+ }
221+
222+ # Remove duplicates and sort
223+ packages <- sort(unique(c(" shiny" , packages )))
224+ return (packages )
225+ }
0 commit comments