Skip to content

Commit 332d41c

Browse files
committed
Roll the package structure out
1 parent a25241d commit 332d41c

32 files changed

+2978
-1
lines changed

.github/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
*.html

.github/workflows/R-CMD-check.yaml

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples
2+
# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help
3+
on:
4+
push:
5+
branches: [main, master]
6+
pull_request:
7+
8+
name: R-CMD-check.yaml
9+
10+
permissions: read-all
11+
12+
jobs:
13+
R-CMD-check:
14+
runs-on: ubuntu-latest
15+
env:
16+
GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
17+
R_KEEP_PKG_SOURCE: yes
18+
steps:
19+
- uses: actions/checkout@v4
20+
21+
- uses: r-lib/actions/setup-r@v2
22+
with:
23+
use-public-rspm: true
24+
25+
- uses: r-lib/actions/setup-r-dependencies@v2
26+
with:
27+
extra-packages: any::rcmdcheck
28+
needs: check
29+
30+
- uses: r-lib/actions/check-r-package@v2
31+
with:
32+
upload-snapshots: true
33+
build_args: 'c("--no-manual","--compact-vignettes=gs+qpdf")'

.github/workflows/pkgdown.yaml

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples
2+
# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help
3+
on:
4+
push:
5+
branches: [main, master]
6+
pull_request:
7+
release:
8+
types: [published]
9+
workflow_dispatch:
10+
11+
name: pkgdown.yaml
12+
13+
permissions: read-all
14+
15+
jobs:
16+
pkgdown:
17+
runs-on: ubuntu-latest
18+
# Only restrict concurrency for non-PR jobs
19+
concurrency:
20+
group: pkgdown-${{ github.event_name != 'pull_request' || github.run_id }}
21+
env:
22+
GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
23+
permissions:
24+
contents: write
25+
steps:
26+
- uses: actions/checkout@v4
27+
28+
- uses: r-lib/actions/setup-pandoc@v2
29+
30+
- uses: r-lib/actions/setup-r@v2
31+
with:
32+
use-public-rspm: true
33+
34+
- uses: r-lib/actions/setup-r-dependencies@v2
35+
with:
36+
extra-packages: any::pkgdown, local::.
37+
needs: website
38+
39+
- name: Build site
40+
run: pkgdown::build_site_github_pages(new_process = FALSE, install = FALSE)
41+
shell: Rscript {0}
42+
43+
- name: Deploy to GitHub pages 🚀
44+
if: github.event_name != 'pull_request'
45+
uses: JamesIves/[email protected]
46+
with:
47+
clean: false
48+
branch: gh-pages
49+
folder: docs

DESCRIPTION

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
Package: peeky
2+
Title: Download and Extract Shinylive Applications
3+
Version: 0.1.0
4+
Authors@R:
5+
person("James Joseph", "Balamuta", , "[email protected]", role = c("aut", "cre"))
6+
Description: Peeks into Quarto documents and standalone Shinylive applications
7+
to download and extract their Shiny application source. Handles the
8+
extraction of application files from app.json format into a directory structure.
9+
URL: https://r-pkg.thecoatlessprofessor.com/peeky/, https://github.com/coatless-rpkg/peeky
10+
BugReports: https://github.com/coatless-rpkg/peeky/issues
11+
Imports:
12+
jsonlite,
13+
httr,
14+
rvest,
15+
fs,
16+
cli
17+
License: AGPL (>= 3)
18+
Encoding: UTF-8
19+
Roxygen: list(markdown = TRUE)
20+
RoxygenNote: 7.3.2

NAMESPACE

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Generated by roxygen2: do not edit by hand
2+
3+
S3method(print,quarto_shinylive_apps)
4+
S3method(print,standalone_shinylive_app)
5+
export(peek_quarto_shinylive_app)
6+
export(peek_shinylive_app)
7+
export(peek_standalone_shinylive_app)

R/find.R

+144
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
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

Comments
 (0)