Skip to content

Commit

Permalink
Roll the package structure out
Browse files Browse the repository at this point in the history
  • Loading branch information
coatless committed Nov 11, 2024
1 parent a25241d commit 332d41c
Show file tree
Hide file tree
Showing 32 changed files with 2,978 additions and 1 deletion.
1 change: 1 addition & 0 deletions .github/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.html
33 changes: 33 additions & 0 deletions .github/workflows/R-CMD-check.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples
# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help
on:
push:
branches: [main, master]
pull_request:

name: R-CMD-check.yaml

permissions: read-all

jobs:
R-CMD-check:
runs-on: ubuntu-latest
env:
GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
R_KEEP_PKG_SOURCE: yes
steps:
- uses: actions/checkout@v4

- uses: r-lib/actions/setup-r@v2
with:
use-public-rspm: true

- uses: r-lib/actions/setup-r-dependencies@v2
with:
extra-packages: any::rcmdcheck
needs: check

- uses: r-lib/actions/check-r-package@v2
with:
upload-snapshots: true
build_args: 'c("--no-manual","--compact-vignettes=gs+qpdf")'
49 changes: 49 additions & 0 deletions .github/workflows/pkgdown.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples
# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help
on:
push:
branches: [main, master]
pull_request:
release:
types: [published]
workflow_dispatch:

name: pkgdown.yaml

permissions: read-all

jobs:
pkgdown:
runs-on: ubuntu-latest
# Only restrict concurrency for non-PR jobs
concurrency:
group: pkgdown-${{ github.event_name != 'pull_request' || github.run_id }}
env:
GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
permissions:
contents: write
steps:
- uses: actions/checkout@v4

- uses: r-lib/actions/setup-pandoc@v2

- uses: r-lib/actions/setup-r@v2
with:
use-public-rspm: true

- uses: r-lib/actions/setup-r-dependencies@v2
with:
extra-packages: any::pkgdown, local::.
needs: website

- name: Build site
run: pkgdown::build_site_github_pages(new_process = FALSE, install = FALSE)
shell: Rscript {0}

- name: Deploy to GitHub pages 🚀
if: github.event_name != 'pull_request'
uses: JamesIves/github-pages-deploy-action@v4.5.0
with:
clean: false
branch: gh-pages
folder: docs
20 changes: 20 additions & 0 deletions DESCRIPTION
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
Package: peeky
Title: Download and Extract Shinylive Applications
Version: 0.1.0
Authors@R:
person("James Joseph", "Balamuta", , "james.balamuta@gmail.com", role = c("aut", "cre"))
Description: Peeks into Quarto documents and standalone Shinylive applications
to download and extract their Shiny application source. Handles the
extraction of application files from app.json format into a directory structure.
URL: https://r-pkg.thecoatlessprofessor.com/peeky/, https://github.com/coatless-rpkg/peeky
BugReports: https://github.com/coatless-rpkg/peeky/issues
Imports:
jsonlite,
httr,
rvest,
fs,
cli
License: AGPL (>= 3)
Encoding: UTF-8
Roxygen: list(markdown = TRUE)
RoxygenNote: 7.3.2
7 changes: 7 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Generated by roxygen2: do not edit by hand

S3method(print,quarto_shinylive_apps)
S3method(print,standalone_shinylive_app)
export(peek_quarto_shinylive_app)
export(peek_shinylive_app)
export(peek_standalone_shinylive_app)
144 changes: 144 additions & 0 deletions R/find.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
#' Find and Validate Shinylive app.json
#'
#' Attempts to locate and validate a Shinylive app.json file from a given base URL.
#' The function tries multiple possible paths and validates both the HTTP response
#' and JSON structure.
#'
#' @param base_url Character string. The base URL to search for app.json.
#'
#' @return
#' A list with three components:
#'
#' - `valid` Logical indicating if a valid app.json was found
#' - `url` Character string of the successful URL, or NULL if not found
#' - `data` List containing the parsed JSON data if valid, NULL otherwise
#'
#' @details
#' The function performs the following steps:
#'
#' 1. Generates three possible paths to check:
#' - The base URL as provided
#' - base URL + `"/app.json"``
#' - Parent directory + `"/app.json"`
#' 2. For each path:
#' - Attempts an HTTP GET request
#' - Verifies the content type is JSON
#' - Parses and validates the JSON structure
#' - Returns immediately if valid app.json is found
#'
#' @keywords internal
#'
#' @examples
#' \dontrun{
#' # Direct app.json URL
#' result <- find_shinylive_app_json("https://example.com/app.json")
#'
#' # Directory containing app.json
#' result <- find_shinylive_app_json("https://example.com/myapp/")
#'
#' # Check if valid
#' if (result$valid) {
#' cat("Found app.json at:", result$url)
#' }
#' }
find_shinylive_app_json <- function(base_url) {
# List of possible paths to try
possible_paths <- c(
base_url,
file.path(base_url, "app.json"),
file.path(dirname(base_url), "app.json")
)

# Try each path
for (path in possible_paths) {
tryCatch({
resp <- httr::GET(path)

# If this is already JSON content, verify it's a valid app.json
if (grepl("application/json", httr::headers(resp)[["content-type"]], fixed = TRUE)) {
content <- httr::content(resp, "text")
# Try to parse as JSON and validate structure
json_data <- jsonlite::fromJSON(content, simplifyDataFrame = FALSE)
if (validate_app_json(json_data)) {
return(list(
valid = TRUE,
url = path,
data = json_data # Return parsed data instead of raw content
))
}
}
}, error = function(e) NULL)
}
return(list(valid = FALSE, url = NULL, data = NULL))
}

#' Find Shinylive Code Blocks in Quarto HTML
#'
#' Parses HTML content to extract and process Shinylive code blocks for both R and Python
#' applications. This function identifies code blocks with class `'shinylive-r'` or
#' `'shinylive-python'` and processes their content into structured application data.
#'
#' @param html Character string containing HTML content. The HTML should contain
#' code blocks with class `'shinylive-r'` or `'shinylive-python'` to be processed.
#'
#' @return
#' A list of parsed Shinylive applications. Each list element contains:
#'
#' - `engine`: Character string indicating the application type (`"r"` or `"python"`)
#' - `options`: List of parsed YAML-style options from the code block
#' - `files`: List of file definitions, where each file contains:
#' - `name`: Character string of the file name
#' - `content`: Character string of the file content
#' - `type`: Character string indicating the file type
#'
#' @details
#'
#' The function performs the following steps:
#'
#' 1. Parses the HTML content using `rvest`
#' 2. Extracts code blocks with classes `'shinylive-r'` or `'shinylive-python'`
#' 3. For each code block:
#' - Determines the engine type from the 'data-engine' attribute
#' - Extracts the code text content
#' - Parses the code block structure using `parse_code_block()`
#'
#' Code blocks should follow the Shinylive format with optional YAML-style
#' options (prefixed with `'#|'`) and file markers (prefixed with `'## file:'`).
#'
#' @seealso parse_code_block
#'
#' @examples
#' \dontrun{
#' html_content <- '
#' <pre class="shinylive-r" data-engine="r">
#' #| viewerHeight: 500
#' ## file: app.R
#' library(shiny)
#' ui <- fluidPage()
#' server <- function(input, output) {}
#' shinyApp(ui, server)
#' </pre>
#' '
#' apps <- find_shinylive_code(html_content)
#' }
#'
#' @keywords internal
find_shinylive_code <- function(html) {
# Parse HTML with rvest
doc <- rvest::read_html(html)

# Find all shinylive code blocks (both R and Python)
code_blocks <- doc |>
rvest::html_elements("pre.shinylive-r, pre.shinylive-python")

# Process each code block, using the data-engine attribute to determine the type
lapply(code_blocks, function(block) {
engine <- block |> rvest::html_attr("data-engine")
code_text <- block |> rvest::html_text()

# Parse the code block with engine information
parse_code_block(code_text, engine)
})
}


Loading

0 comments on commit 332d41c

Please sign in to comment.