|
| 1 | +#' Find and Validate Shinylive app.json |
| 2 | +#' |
| 3 | +#' Attempts to locate and validate a Shinylive app.json file from a given base URL. |
| 4 | +#' The function tries multiple possible paths and validates both the HTTP response |
| 5 | +#' and JSON structure. |
| 6 | +#' |
| 7 | +#' @param base_url Character string. The base URL to search for app.json. |
| 8 | +#' |
| 9 | +#' @return |
| 10 | +#' A list with three components: |
| 11 | +#' |
| 12 | +#' - `valid` Logical indicating if a valid app.json was found |
| 13 | +#' - `url` Character string of the successful URL, or NULL if not found |
| 14 | +#' - `data` List containing the parsed JSON data if valid, NULL otherwise |
| 15 | +#' |
| 16 | +#' @details |
| 17 | +#' The function performs the following steps: |
| 18 | +#' |
| 19 | +#' 1. Generates three possible paths to check: |
| 20 | +#' - The base URL as provided |
| 21 | +#' - base URL + `"/app.json"`` |
| 22 | +#' - Parent directory + `"/app.json"` |
| 23 | +#' 2. For each path: |
| 24 | +#' - Attempts an HTTP GET request |
| 25 | +#' - Verifies the content type is JSON |
| 26 | +#' - Parses and validates the JSON structure |
| 27 | +#' - Returns immediately if valid app.json is found |
| 28 | +#' |
| 29 | +#' @keywords internal |
| 30 | +#' |
| 31 | +#' @examples |
| 32 | +#' \dontrun{ |
| 33 | +#' # Direct app.json URL |
| 34 | +#' result <- find_shinylive_app_json("https://example.com/app.json") |
| 35 | +#' |
| 36 | +#' # Directory containing app.json |
| 37 | +#' result <- find_shinylive_app_json("https://example.com/myapp/") |
| 38 | +#' |
| 39 | +#' # Check if valid |
| 40 | +#' if (result$valid) { |
| 41 | +#' cat("Found app.json at:", result$url) |
| 42 | +#' } |
| 43 | +#' } |
| 44 | +find_shinylive_app_json <- function(base_url) { |
| 45 | + # List of possible paths to try |
| 46 | + possible_paths <- c( |
| 47 | + base_url, |
| 48 | + file.path(base_url, "app.json"), |
| 49 | + file.path(dirname(base_url), "app.json") |
| 50 | + ) |
| 51 | + |
| 52 | + # Try each path |
| 53 | + for (path in possible_paths) { |
| 54 | + tryCatch({ |
| 55 | + resp <- httr::GET(path) |
| 56 | + |
| 57 | + # If this is already JSON content, verify it's a valid app.json |
| 58 | + if (grepl("application/json", httr::headers(resp)[["content-type"]], fixed = TRUE)) { |
| 59 | + content <- httr::content(resp, "text") |
| 60 | + # Try to parse as JSON and validate structure |
| 61 | + json_data <- jsonlite::fromJSON(content, simplifyDataFrame = FALSE) |
| 62 | + if (validate_app_json(json_data)) { |
| 63 | + return(list( |
| 64 | + valid = TRUE, |
| 65 | + url = path, |
| 66 | + data = json_data # Return parsed data instead of raw content |
| 67 | + )) |
| 68 | + } |
| 69 | + } |
| 70 | + }, error = function(e) NULL) |
| 71 | + } |
| 72 | + return(list(valid = FALSE, url = NULL, data = NULL)) |
| 73 | +} |
| 74 | + |
| 75 | +#' Find Shinylive Code Blocks in Quarto HTML |
| 76 | +#' |
| 77 | +#' Parses HTML content to extract and process Shinylive code blocks for both R and Python |
| 78 | +#' applications. This function identifies code blocks with class `'shinylive-r'` or |
| 79 | +#' `'shinylive-python'` and processes their content into structured application data. |
| 80 | +#' |
| 81 | +#' @param html Character string containing HTML content. The HTML should contain |
| 82 | +#' code blocks with class `'shinylive-r'` or `'shinylive-python'` to be processed. |
| 83 | +#' |
| 84 | +#' @return |
| 85 | +#' A list of parsed Shinylive applications. Each list element contains: |
| 86 | +#' |
| 87 | +#' - `engine`: Character string indicating the application type (`"r"` or `"python"`) |
| 88 | +#' - `options`: List of parsed YAML-style options from the code block |
| 89 | +#' - `files`: List of file definitions, where each file contains: |
| 90 | +#' - `name`: Character string of the file name |
| 91 | +#' - `content`: Character string of the file content |
| 92 | +#' - `type`: Character string indicating the file type |
| 93 | +#' |
| 94 | +#' @details |
| 95 | +#' |
| 96 | +#' The function performs the following steps: |
| 97 | +#' |
| 98 | +#' 1. Parses the HTML content using `rvest` |
| 99 | +#' 2. Extracts code blocks with classes `'shinylive-r'` or `'shinylive-python'` |
| 100 | +#' 3. For each code block: |
| 101 | +#' - Determines the engine type from the 'data-engine' attribute |
| 102 | +#' - Extracts the code text content |
| 103 | +#' - Parses the code block structure using `parse_code_block()` |
| 104 | +#' |
| 105 | +#' Code blocks should follow the Shinylive format with optional YAML-style |
| 106 | +#' options (prefixed with `'#|'`) and file markers (prefixed with `'## file:'`). |
| 107 | +#' |
| 108 | +#' @seealso parse_code_block |
| 109 | +#' |
| 110 | +#' @examples |
| 111 | +#' \dontrun{ |
| 112 | +#' html_content <- ' |
| 113 | +#' <pre class="shinylive-r" data-engine="r"> |
| 114 | +#' #| viewerHeight: 500 |
| 115 | +#' ## file: app.R |
| 116 | +#' library(shiny) |
| 117 | +#' ui <- fluidPage() |
| 118 | +#' server <- function(input, output) {} |
| 119 | +#' shinyApp(ui, server) |
| 120 | +#' </pre> |
| 121 | +#' ' |
| 122 | +#' apps <- find_shinylive_code(html_content) |
| 123 | +#' } |
| 124 | +#' |
| 125 | +#' @keywords internal |
| 126 | +find_shinylive_code <- function(html) { |
| 127 | + # Parse HTML with rvest |
| 128 | + doc <- rvest::read_html(html) |
| 129 | + |
| 130 | + # Find all shinylive code blocks (both R and Python) |
| 131 | + code_blocks <- doc |> |
| 132 | + rvest::html_elements("pre.shinylive-r, pre.shinylive-python") |
| 133 | + |
| 134 | + # Process each code block, using the data-engine attribute to determine the type |
| 135 | + lapply(code_blocks, function(block) { |
| 136 | + engine <- block |> rvest::html_attr("data-engine") |
| 137 | + code_text <- block |> rvest::html_text() |
| 138 | + |
| 139 | + # Parse the code block with engine information |
| 140 | + parse_code_block(code_text, engine) |
| 141 | + }) |
| 142 | +} |
| 143 | + |
| 144 | + |
0 commit comments