diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 65e46ae3..76dc14e1 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -64,7 +64,8 @@ jobs: shell: Rscript {0} - name: Lint - run: lintr::lint_package() + run: | + lintr::lint_package() shell: Rscript {0} - name: Upload lint results diff --git a/.github/workflows/pkgdown.yaml b/.github/workflows/pkgdown.yaml new file mode 100644 index 00000000..8a97e7b6 --- /dev/null +++ b/.github/workflows/pkgdown.yaml @@ -0,0 +1,34 @@ +# 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 + workflow_dispatch: + +name: pkgdown + +jobs: + pkgdown: + runs-on: ubuntu-latest + env: + GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@v2 + + - 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: Deploy to gh-pages branch + run: | + git config --local user.name "$GITHUB_ACTOR" + git config --local user.email "$GITHUB_ACTOR@users.noreply.github.com" + Rscript -e 'pkgdown::deploy_to_branch(new_process = FALSE)' diff --git a/.lintr b/.lintr index e7aa9917..a91fe52e 100644 --- a/.lintr +++ b/.lintr @@ -1,3 +1,5 @@ linters: with_defaults( - line_length_linter = line_length_linter(100) + line_length_linter = line_length_linter(100), + infix_spaces_linter = NULL, + object_usage_linter = NULL ) diff --git a/DESCRIPTION b/DESCRIPTION index adffd24d..139bf320 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: rhino Title: A framework for enterprise Shiny applications -Version: 0.1.0 +Version: 0.2.0 Authors@R: c( person(given = "Kamil", family = "Zyla", role = "aut", email = "kamil@appsilon.com"), @@ -11,12 +11,17 @@ Authors@R: person(family = "Appsilon Sp. z o.o.", role = "cph") ) Description: A framework that supports creating and extending enterprise Shiny applications using best practices. -URL: https://github.com/Appsilon/rhino +URL: https://appsilon.github.io/rhino, https://github.com/Appsilon/rhino BugReports: https://github.com/Appsilon/rhino/issues License: MIT + file LICENSE Encoding: UTF-8 Roxygen: list(markdown = TRUE) RoxygenNote: 7.1.2 +Imports: + fs, + cli, + renv, + withr Suggests: lintr (>= 2.0.0), testthat (>= 3.0.0) diff --git a/NAMESPACE b/NAMESPACE index 6ae92683..1ddabeb1 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,2 +1,11 @@ # Generated by roxygen2: do not edit by hand +export(init) +importFrom(cli,cli_alert_success) +importFrom(fs,dir_copy) +importFrom(fs,dir_create) +importFrom(fs,file_copy) +importFrom(fs,path) +importFrom(fs,path_package) +importFrom(renv,init) +importFrom(withr,with_dir) diff --git a/R/.gitkeep b/R/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/R/init.R b/R/init.R new file mode 100644 index 00000000..66706e0b --- /dev/null +++ b/R/init.R @@ -0,0 +1,93 @@ +#' Create Shiny application using `{rhino}` +#' +#' @param dir Name of the directory to create application in. +#' @param github_actions_ci Should the Github Actions CI be added. +#' +#' @export +init <- function(dir = ".", github_actions_ci = TRUE) { + init_setup(dir) + + create_app_structure(dir) + + if (isTRUE(github_actions_ci)) add_github_actions_ci(dir) + + init_renv(dir) +} + +#' @importFrom fs dir_create +#' @importFrom cli cli_alert_success +init_setup <- function(dir) { + dir_create(dir) + cli_alert_success("Application directory created") +} + +#' @importFrom fs dir_copy file_copy path +#' @importFrom cli cli_alert_success +create_app_structure <- function(dir) { + file_copy( + path = path_rhino("app_structure", "app.R"), + new_path = dir + ) + + file_copy( + path = path_rhino("app_structure", "Rprofile"), + new_path = path(dir, ".Rprofile") + ) + + file_copy( + path = path_rhino("app_structure", "src.Rproj2"), + new_path = path(dir, "src.Rproj") + ) + + dir_copy( + path = path_rhino("app_structure", "app"), + new_path = dir + ) + + cli_alert_success("Application structure created") +} + +#' @importFrom fs dir_create dir_copy path +#' @importFrom cli cli_alert_success +add_github_actions_ci <- function(dir) { + github_path <- path(dir, ".github") + dir_create(github_path) + dir_copy( + path = path_rhino("github_ci", "workflows"), + new_path = github_path + ) + + cli_alert_success("Github Actions CI added") +} + +#' @importFrom fs file_copy +#' @importFrom renv init +#' @importFrom withr with_dir +#' @importFrom cli cli_alert_success +init_renv <- function(dir) { + file_copy( + path = path_rhino("renv", "renvignore"), + new_path = path(dir, ".renvignore") + ) + + file_copy( + path = path_rhino("renv", "dependencies.R"), + new_path = path(dir) + ) + + with_dir( + dir, + renv::init(restart = FALSE) + ) + + cli_alert_success("renv initiated") +} + +#' @importFrom fs path_package +path_rhino <- function(...) { + path_package( + "rhino", + "templates", + ... + ) +} diff --git a/README.md b/README.md index ad4c7ebc..e3d58cd2 100644 --- a/README.md +++ b/README.md @@ -29,3 +29,7 @@ Alternatively, the package can be installed, and then tested with `testthat::tes #### Linter Linter can be run using either `lintr::lint_package()` or `devtools::lint()`. + +#### `pkgdown` site +To create a `pkgdown` site locally run either `pkgdown::build_site()` or `devtools::build_site()`. +If built successfully, the website will be in `docs` directory. diff --git a/inst/rstudio/templates/project/init.dcf b/inst/rstudio/templates/project/init.dcf new file mode 100644 index 00000000..ab2df83c --- /dev/null +++ b/inst/rstudio/templates/project/init.dcf @@ -0,0 +1,7 @@ +Binding: init +Title: Shiny Application using rhino + +Parameter: github_actions_ci +Widget: CheckboxInput +Label: Github Actions CI +Default: On diff --git a/inst/templates/app_structure/Rprofile b/inst/templates/app_structure/Rprofile new file mode 100644 index 00000000..4d4e9578 --- /dev/null +++ b/inst/templates/app_structure/Rprofile @@ -0,0 +1,2 @@ +# Allow absolute module imports (relative to the app root). +options(box.path = file.path(getwd(), "app")) diff --git a/inst/templates/app_structure/app.R b/inst/templates/app_structure/app.R new file mode 100644 index 00000000..25d4417c --- /dev/null +++ b/inst/templates/app_structure/app.R @@ -0,0 +1,29 @@ +# Purge the box module cache, so the app can be reloaded without restarting the R session. +rm(list = ls(box:::loaded_mods), envir = box:::loaded_mods) + +box::use( + logger[appender_file, log_appender, log_threshold], + shiny[addResourcePath, shinyApp], +) +box::use( + r/main, +) + +# nolint start +LOG_FILE <- Sys.getenv("LOG_FILE") +LOG_LEVEL <- Sys.getenv("LOG_LEVEL", unset = "INFO") +# nolint end + +addResourcePath("static", "app/static") + +log_threshold(LOG_LEVEL) +if (nzchar(LOG_FILE)) { + log_appender(appender_file(LOG_FILE)) +} + +shinyApp( + ui = main$ui("app"), + server = function(input, output) { + main$server("app") + } +) diff --git a/inst/templates/app_structure/app/r/main.R b/inst/templates/app_structure/app/r/main.R new file mode 100644 index 00000000..6e1e42c8 --- /dev/null +++ b/inst/templates/app_structure/app/r/main.R @@ -0,0 +1,23 @@ +box::use( + shiny[bootstrapPage, moduleServer, NS, renderText, tags, textOutput], +) + +#' @export +ui <- function(id) { + ns <- NS(id) + bootstrapPage( + tags$head( + tags$link(rel = "icon", href = "static/favicon.ico", sizes = "any") + ), + tags$h3( + textOutput(ns("message")) + ) + ) +} + +#' @export +server <- function(id) { + moduleServer(id, function(input, output, session) { + output$message <- renderText("Hello!") + }) +} diff --git a/inst/templates/app_structure/app/static/favicon.ico b/inst/templates/app_structure/app/static/favicon.ico new file mode 100644 index 00000000..c4c7f883 Binary files /dev/null and b/inst/templates/app_structure/app/static/favicon.ico differ diff --git a/inst/templates/app_structure/src.Rproj2 b/inst/templates/app_structure/src.Rproj2 new file mode 100644 index 00000000..8e3c2ebc --- /dev/null +++ b/inst/templates/app_structure/src.Rproj2 @@ -0,0 +1,13 @@ +Version: 1.0 + +RestoreWorkspace: Default +SaveWorkspace: Default +AlwaysSaveHistory: Default + +EnableCodeIndexing: Yes +UseSpacesForTab: Yes +NumSpacesForTab: 2 +Encoding: UTF-8 + +RnwWeave: Sweave +LaTeX: pdfLaTeX diff --git a/inst/templates/github_ci/workflows/linter.yaml b/inst/templates/github_ci/workflows/linter.yaml new file mode 100644 index 00000000..5c4fe905 --- /dev/null +++ b/inst/templates/github_ci/workflows/linter.yaml @@ -0,0 +1,33 @@ +name: Linter +on: push +jobs: + main: + name: Run linter + runs-on: ubuntu-20.04 + steps: + - name: Checkout repo + uses: actions/checkout@v2 + + - name: Setup R + uses: r-lib/actions/setup-r@v1 + with: + r-version: '4.1.0' + + - name: Restore renv from cache + uses: actions/cache@v2 + with: + path: renv/library + key: renv-${{ hashFiles('renv.lock') }} + restore-keys: renv- + + - name: Sync renv with lockfile + shell: Rscript {0} + run: | + options(renv.config.cache.symlinks = FALSE) + renv::restore(clean = TRUE) + + - name: Lint R + if: always() + shell: Rscript {0} + run: | + rhino::lint_r() diff --git a/inst/templates/renv/dependencies.R b/inst/templates/renv/dependencies.R new file mode 100644 index 00000000..516f7f36 --- /dev/null +++ b/inst/templates/renv/dependencies.R @@ -0,0 +1,11 @@ +# This file allows packrat (used by rsconnect during deployment) to pick up dependencies. + +# Development +library(lintr) +library(yaml) # Used to load config for lintr. + +# Production +library(box) +library(logger) +library(shiny) +library(rhino) diff --git a/inst/templates/renv/renvignore b/inst/templates/renv/renvignore new file mode 100644 index 00000000..4f16dc6d --- /dev/null +++ b/inst/templates/renv/renvignore @@ -0,0 +1,3 @@ +# Only use `dependencies.R` to infer project dependencies. +* +!dependencies.R diff --git a/man/init.Rd b/man/init.Rd new file mode 100644 index 00000000..56f9c7cf --- /dev/null +++ b/man/init.Rd @@ -0,0 +1,16 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/init.R +\name{init} +\alias{init} +\title{Create Shiny application using \code{{rhino}}} +\usage{ +init(dir = ".", github_actions_ci = TRUE) +} +\arguments{ +\item{dir}{Name of the directory to create application in.} + +\item{github_actions_ci}{Should the Github Actions CI be added.} +} +\description{ +Create Shiny application using \code{{rhino}} +}