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